Better explain why messages could not be deleted

This also fixes a bug in the IntroductionManager that would allow to
delete only part of a session's visible messages.
This commit is contained in:
Torsten Grote
2019-11-08 14:14:20 -03:00
parent 11c43dc7f4
commit ae0fa351b6
13 changed files with 516 additions and 281 deletions

View File

@@ -67,6 +67,7 @@ import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.ConversationRequest; import org.briarproject.briar.api.conversation.ConversationRequest;
import org.briarproject.briar.api.conversation.ConversationResponse; import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent; import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionManager;
@@ -857,9 +858,9 @@ public class ConversationActivity extends BriarActivity
list.showProgressBar(); list.showProgressBar();
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
boolean allDeleted = DeletionResult result =
conversationManager.deleteAllMessages(contactId); conversationManager.deleteAllMessages(contactId);
reloadConversationAfterDeletingMessages(allDeleted); reloadConversationAfterDeletingMessages(result);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
runOnUiThreadUnlessDestroyed(() -> list.showData()); runOnUiThreadUnlessDestroyed(() -> list.showData());
@@ -874,9 +875,9 @@ public class ConversationActivity extends BriarActivity
if (actionMode != null) actionMode.finish(); if (actionMode != null) actionMode.finish();
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
boolean allDeleted = DeletionResult result =
conversationManager.deleteMessages(contactId, selected); conversationManager.deleteMessages(contactId, selected);
reloadConversationAfterDeletingMessages(allDeleted); reloadConversationAfterDeletingMessages(result);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
runOnUiThreadUnlessDestroyed(() -> list.showData()); runOnUiThreadUnlessDestroyed(() -> list.showData());
@@ -885,22 +886,55 @@ public class ConversationActivity extends BriarActivity
} }
private void reloadConversationAfterDeletingMessages( private void reloadConversationAfterDeletingMessages(
boolean allDeleted) { DeletionResult result) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
adapter.clear(); adapter.clear();
list.showProgressBar(); // otherwise clearing shows empty state list.showProgressBar(); // otherwise clearing shows empty state
loadMessages(); loadMessages();
if (!allDeleted) showNotAllDeletedDialog(); if (!result.allDeleted()) showNotAllDeletedDialog(result);
}); });
} }
private void showNotAllDeletedDialog() { private void showNotAllDeletedDialog(DeletionResult result) {
StringBuilder msg = new StringBuilder();
// get failures the user can not immediately prevent
StringBuilder fails = new StringBuilder();
if (result.hasSessionInProgress()) {
if (result.hasIntroduction()) {
String s = getString(
R.string.dialog_message_not_deleted_ongoing_introductions);
fails.append(s).append("\n");
}
if (result.hasInvitation()) {
String s = getString(
R.string.dialog_message_not_deleted_ongoing_invitations);
fails.append(s).append("\n");
}
}
if (result.hasNotFullyDownloaded()) {
String s = getString(
R.string.dialog_message_not_deleted_partly_downloaded);
fails.append(s).append("\n");
}
// add failures to message if there are any
if (fails.length() > 0) {
String s = getString(R.string.dialog_message_not_deleted,
fails.toString());
msg.append(s);
}
// add problems the user can resolve
if (result.hasNotAllSelected()) {
if (msg.length() > 0) msg.append("\n\n");
String s = getString(
R.string.dialog_message_not_deleted_not_all_selected);
msg.append(s);
}
// show dialog
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(this, R.style.BriarDialogTheme); new AlertDialog.Builder(this, R.style.BriarDialogTheme);
builder.setTitle( builder.setTitle(
getString(R.string.dialog_title_not_all_messages_deleted)); getString(R.string.dialog_title_not_all_messages_deleted));
builder.setMessage( builder.setMessage(msg.toString());
getString(R.string.dialog_message_not_all_messages_deleted));
builder.setPositiveButton(R.string.ok, null); builder.setPositiveButton(R.string.ok, null);
builder.show(); builder.show();
} }
@@ -912,7 +946,8 @@ public class ConversationActivity extends BriarActivity
new AlertDialog.Builder(ConversationActivity.this, new AlertDialog.Builder(ConversationActivity.this,
R.style.BriarDialogTheme); R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.dialog_title_delete_contact)); builder.setTitle(getString(R.string.dialog_title_delete_contact));
builder.setMessage(getString(R.string.dialog_message_delete_contact)); builder.setMessage(
getString(R.string.dialog_message_delete_contact));
builder.setNegativeButton(R.string.delete, okListener); builder.setNegativeButton(R.string.delete, okListener);
builder.setPositiveButton(R.string.cancel, null); builder.setPositiveButton(R.string.cancel, null);
builder.show(); builder.show();
@@ -953,7 +988,8 @@ public class ConversationActivity extends BriarActivity
private void showImageOnboarding() { private void showImageOnboarding() {
// TODO: remove cast when removing feature flag // TODO: remove cast when removing feature flag
((TextAttachmentController) sendController).showImageOnboarding(this); ((TextAttachmentController) sendController)
.showImageOnboarding(this);
} }
private void showIntroductionOnboarding(@Nullable Boolean show) { private void showIntroductionOnboarding(@Nullable Boolean show) {
@@ -973,7 +1009,8 @@ public class ConversationActivity extends BriarActivity
View target = null; View target = null;
for (int i = 0; i < toolbar.getChildCount(); i++) { for (int i = 0; i < toolbar.getChildCount(); i++) {
if (toolbar.getChildAt(i) instanceof ActionMenuView) { if (toolbar.getChildAt(i) instanceof ActionMenuView) {
ActionMenuView menu = (ActionMenuView) toolbar.getChildAt(i); ActionMenuView menu =
(ActionMenuView) toolbar.getChildAt(i);
// The overflow icon should be the last child of the menu // The overflow icon should be the last child of the menu
target = menu.getChildAt(menu.getChildCount() - 1); target = menu.getChildAt(menu.getChildCount() - 1);
// If the menu hasn't been populated yet, use the menu itself // If the menu hasn't been populated yet, use the menu itself
@@ -999,7 +1036,8 @@ public class ConversationActivity extends BriarActivity
@UiThread @UiThread
@Override @Override
public void respondToRequest(ConversationRequestItem item, boolean accept) { public void respondToRequest(ConversationRequestItem item,
boolean accept) {
item.setAnswered(); item.setAnswered();
int position = adapter.findItemPosition(item); int position = adapter.findItemPosition(item);
if (position != INVALID_POSITION) { if (position != INVALID_POSITION) {

View File

@@ -139,7 +139,11 @@
<string name="dialog_title_delete_all_messages">Confirm Message Deletion</string> <string name="dialog_title_delete_all_messages">Confirm Message Deletion</string>
<string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string> <string name="dialog_message_delete_all_messages">Are you sure that you want to delete all messages?</string>
<string name="dialog_title_not_all_messages_deleted">Could not delete all messages</string> <string name="dialog_title_not_all_messages_deleted">Could not delete all messages</string>
<string name="dialog_message_not_all_messages_deleted">Messages related to\n\n• ongoing introductions\n• ongoing (blog/forum/group) invitations\n• partly downloaded messages\n\ncannot be deleted until they conclude.</string> <string name="dialog_message_not_deleted">Messages related to\n\n%s\ncan not be deleted until they conclude.</string>
<string name="dialog_message_not_deleted_ongoing_introductions">• ongoing introductions</string>
<string name="dialog_message_not_deleted_ongoing_invitations">• ongoing (blog/forum/group) invitations</string>
<string name="dialog_message_not_deleted_partly_downloaded">• partly downloaded messages</string>
<string name="dialog_message_not_deleted_not_all_selected">To delete an invitation/introduction, you need to select the request and the response.</string>
<string name="delete_contact">Delete contact</string> <string name="delete_contact">Delete contact</string>
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string> <string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string> <string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>

View File

@@ -17,6 +17,12 @@ import java.util.Set;
@NotNullByDefault @NotNullByDefault
public interface ConversationManager { public interface ConversationManager {
int DELETE_SESSION_IS_INTRODUCTION = 1;
int DELETE_SESSION_IS_INVITATION = 1 << 1;
int DELETE_SESSION_INCOMPLETE = 1 << 2;
int DELETE_SESSION_IN_PROGRESS = 1 << 3;
int DELETE_NOT_DOWNLOADED = 1 << 4;
/** /**
* Clients that present messages in a private conversation need to * Clients that present messages in a private conversation need to
* register themselves here. * register themselves here.
@@ -39,17 +45,13 @@ public interface ConversationManager {
/** /**
* Deletes all messages exchanged with the given contact. * Deletes all messages exchanged with the given contact.
*
* @return true if all messages could be deleted, false otherwise
*/ */
boolean deleteAllMessages(ContactId c) throws DbException; DeletionResult deleteAllMessages(ContactId c) throws DbException;
/** /**
* Deletes the given set of messages associated with the given contact. * Deletes the given set of messages associated with the given contact.
*
* @return true if all given messages could be deleted, false otherwise
*/ */
boolean deleteMessages(ContactId c, Collection<MessageId> messageIds) DeletionResult deleteMessages(ContactId c, Collection<MessageId> messageIds)
throws DbException; throws DbException;
@NotNullByDefault @NotNullByDefault
@@ -75,10 +77,8 @@ public interface ConversationManager {
/** /**
* Deletes all messages associated with the given contact. * Deletes all messages associated with the given contact.
*
* @return true if all messages could be deleted, false otherwise
*/ */
boolean deleteAllMessages(Transaction txn, DeletionResult deleteAllMessages(Transaction txn,
ContactId c) throws DbException; ContactId c) throws DbException;
/** /**
@@ -86,10 +86,8 @@ public interface ConversationManager {
* <p> * <p>
* The set of message IDs must only include message IDs returned by * The set of message IDs must only include message IDs returned by
* {@link #getMessageIds}. * {@link #getMessageIds}.
*
* @return true if all messages could be deleted, false otherwise
*/ */
boolean deleteMessages(Transaction txn, ContactId c, DeletionResult deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException; Set<MessageId> messageIds) throws DbException;
} }

View File

@@ -0,0 +1,67 @@
package org.briarproject.briar.api.conversation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.briar.api.conversation.ConversationManager.DELETE_NOT_DOWNLOADED;
import static org.briarproject.briar.api.conversation.ConversationManager.DELETE_SESSION_INCOMPLETE;
import static org.briarproject.briar.api.conversation.ConversationManager.DELETE_SESSION_IN_PROGRESS;
import static org.briarproject.briar.api.conversation.ConversationManager.DELETE_SESSION_IS_INTRODUCTION;
import static org.briarproject.briar.api.conversation.ConversationManager.DELETE_SESSION_IS_INVITATION;
@NotThreadSafe
@NotNullByDefault
public class DeletionResult {
private int result = 0;
public void addDeletionResult(DeletionResult deletionResult) {
result |= deletionResult.result;
}
public void addInvitationNotAllSelected() {
result |= DELETE_SESSION_INCOMPLETE | DELETE_SESSION_IS_INVITATION;
}
public void addInvitationSessionInProgress() {
result |= DELETE_SESSION_IN_PROGRESS | DELETE_SESSION_IS_INVITATION;
}
public void addIntroductionNotAllSelected() {
result |= DELETE_SESSION_INCOMPLETE | DELETE_SESSION_IS_INTRODUCTION;
}
public void addIntroductionSessionInProgress() {
result |= DELETE_SESSION_IN_PROGRESS | DELETE_SESSION_IS_INTRODUCTION;
}
public void addNotFullyDownloaded() {
result |= DELETE_NOT_DOWNLOADED;
}
public boolean allDeleted() {
return result == 0;
}
public boolean hasIntroduction() {
return (result & DELETE_SESSION_IS_INTRODUCTION) != 0;
}
public boolean hasInvitation() {
return (result & DELETE_SESSION_IS_INVITATION) != 0;
}
public boolean hasSessionInProgress() {
return (result & DELETE_SESSION_IN_PROGRESS) != 0;
}
public boolean hasNotAllSelected() {
return (result & DELETE_SESSION_INCOMPLETE) != 0;
}
public boolean hasNotFullyDownloaded() {
return (result & DELETE_NOT_DOWNLOADED) != 0;
}
}

View File

@@ -32,6 +32,7 @@ import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVer
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
@@ -561,73 +562,45 @@ class IntroductionManagerImpl extends ConversationClientImpl
@FunctionalInterface @FunctionalInterface
private interface MessageRetriever { private interface MessageRetriever {
Map<MessageId, BdfDictionary> getMessages(Transaction txn, Set<MessageId> getMessages(Set<MessageId> allMessages);
GroupId contactGroup) throws DbException;
}
@FunctionalInterface
private interface MessageDeletionChecker {
/**
* This is called for all messages belonging to a session.
* It returns true if the given {@link MessageId} causes a problem
* so that the session can not be deleted.
*/
boolean causesProblem(MessageId messageId);
} }
@Override @Override
public boolean deleteAllMessages(Transaction txn, ContactId c) public DeletionResult deleteAllMessages(Transaction txn, ContactId c)
throws DbException { throws DbException {
return deleteMessages(txn, c, (txn1, g) -> { return deleteMessages(txn, c, allMessages -> allMessages);
// get metadata for all messages in the group
Map<MessageId, BdfDictionary> messages;
try {
messages = clientHelper.getMessageMetadataAsDictionary(txn1, g);
} catch (FormatException e) {
throw new DbException(e);
}
return messages;
}, messageId -> false);
} }
@Override @Override
public boolean deleteMessages(Transaction txn, ContactId c, public DeletionResult deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException { Set<MessageId> messageIds) throws DbException {
return deleteMessages(txn, c, (txn1, g) -> { return deleteMessages(txn, c, allMessages -> messageIds);
// get metadata for messages that shall be deleted
Map<MessageId, BdfDictionary> messages =
new HashMap<>(messageIds.size());
for (MessageId m : messageIds) {
BdfDictionary d;
try {
d = clientHelper.getMessageMetadataAsDictionary(txn1, m);
} catch (FormatException e) {
throw new DbException(e);
}
// If message metadata does not exist,
// getMessageMetadataAsDictionary(txn, m) returns empty Metadata
if (!d.isEmpty()) messages.put(m, d);
}
return messages;
// don't delete sessions if a message is not part of messageIds
}, messageId -> !messageIds.contains(messageId));
} }
private boolean deleteMessages(Transaction txn, ContactId c, private DeletionResult deleteMessages(Transaction txn, ContactId c,
MessageRetriever retriever, MessageDeletionChecker checker) MessageRetriever retriever) throws DbException {
throws DbException {
// get ID of the contact group // get ID of the contact group
GroupId g = getContactGroup(db.getContact(txn, c)).getId(); GroupId g = getContactGroup(db.getContact(txn, c)).getId();
// get messages to be deleted // get metadata for all messages in the group
Map<MessageId, BdfDictionary> messages = retriever.getMessages(txn, g); Map<MessageId, BdfDictionary> messages;
try {
messages = clientHelper.getMessageMetadataAsDictionary(txn, g);
} catch (FormatException e) {
throw new DbException(e);
}
// assign protocol messages to their sessions // get messages to be deleted
Set<MessageId> selected = retriever.getMessages(messages.keySet());
// get sessions for selected messages
Map<SessionId, DeletableSession> sessions = new HashMap<>(); Map<SessionId, DeletableSession> sessions = new HashMap<>();
for (Entry<MessageId, BdfDictionary> entry : messages.entrySet()) { for (MessageId id : selected) {
BdfDictionary d = messages.get(id);
if (d == null) continue; // throw new NoSuchMessageException()
MessageMetadata m; MessageMetadata m;
try { try {
m = messageParser.parseMetadata(entry.getValue()); m = messageParser.parseMetadata(d);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} }
@@ -644,6 +617,31 @@ class IntroductionManagerImpl extends ConversationClientImpl
session = getDeletableSession(txn, g, m.getSessionId()); session = getDeletableSession(txn, g, m.getSessionId());
sessions.put(m.getSessionId(), session); sessions.put(m.getSessionId(), session);
} }
session.messages.add(id);
}
// assign other protocol messages to their sessions
for (Entry<MessageId, BdfDictionary> entry : messages.entrySet()) {
// we handled selected messages above
if (selected.contains(entry.getKey())) continue;
MessageMetadata m;
try {
m = messageParser.parseMetadata(entry.getValue());
} catch (FormatException e) {
throw new DbException(e);
}
if (!m.isVisibleInConversation()) continue;
if (m.getSessionId() == null) {
// This can only be an unhandled REQUEST message.
// Its session is created and stored in incomingMessage(),
// and getMessageMetadata() only returns delivered messages,
// so the session ID should have been assigned.
throw new AssertionError("missing session ID");
}
// get session from map or database
DeletableSession session = sessions.get(m.getSessionId());
if (session == null) continue; // not a session of a selected msg
session.messages.add(entry.getKey()); session.messages.add(entry.getKey());
} }
@@ -652,10 +650,10 @@ class IntroductionManagerImpl extends ConversationClientImpl
for (MessageStatus status : db.getMessageStatus(txn, c, g)) { for (MessageStatus status : db.getMessageStatus(txn, c, g)) {
if (!status.isSeen()) notAcked.add(status.getMessageId()); if (!status.isSeen()) notAcked.add(status.getMessageId());
} }
boolean allDeleted = DeletionResult result =
deleteCompletedSessions(txn, sessions, notAcked, checker); deleteCompletedSessions(txn, sessions, notAcked, selected);
recalculateGroupCount(txn, g); recalculateGroupCount(txn, g);
return allDeleted; return result;
} }
private DeletableSession getDeletableSession(Transaction txn, private DeletableSession getDeletableSession(Transaction txn,
@@ -677,28 +675,31 @@ class IntroductionManagerImpl extends ConversationClientImpl
} }
} }
private boolean deleteCompletedSessions(Transaction txn, private DeletionResult deleteCompletedSessions(Transaction txn,
Map<SessionId, DeletableSession> sessions, Set<MessageId> notAcked, Map<SessionId, DeletableSession> sessions, Set<MessageId> notAcked,
MessageDeletionChecker checker) throws DbException { Set<MessageId> selected) throws DbException {
// find completed sessions to delete // find completed sessions to delete
boolean allDeleted = true; DeletionResult result = new DeletionResult();
for (DeletableSession session : sessions.values()) { for (DeletableSession session : sessions.values()) {
if (!session.state.isComplete()) { if (!session.state.isComplete()) {
allDeleted = false; result.addIntroductionSessionInProgress();
continue; continue;
} }
// we can only delete sessions // we can only delete sessions
// where delivery of all messages was confirmed (aka ACKed) // where delivery of all messages was confirmed (aka ACKed)
boolean allAcked = true; boolean sessionDeletable = true;
for (MessageId m : session.messages) { for (MessageId m : session.messages) {
if (notAcked.contains(m) || checker.causesProblem(m)) { if (notAcked.contains(m) || !selected.contains(m)) {
allAcked = false; sessionDeletable = false;
allDeleted = false; if (notAcked.contains(m))
result.addIntroductionSessionInProgress();
if (!selected.contains(m))
result.addIntroductionNotAllSelected();
break; break;
} }
} }
// delete messages of session, if all were ACKed // delete messages of session, if all were ACKed
if (allAcked) { if (sessionDeletable) {
for (MessageId m : session.messages) { for (MessageId m : session.messages) {
db.deleteMessage(txn, m); db.deleteMessage(txn, m);
db.deleteMessageMetadata(txn, m); db.deleteMessageMetadata(txn, m);
@@ -707,7 +708,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
// and then needs the previous MessageIds // and then needs the previous MessageIds
} }
} }
return allDeleted; return result;
} }
@Override @Override

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -75,27 +76,27 @@ class ConversationManagerImpl implements ConversationManager {
} }
@Override @Override
public boolean deleteAllMessages(ContactId c) throws DbException { public DeletionResult deleteAllMessages(ContactId c) throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn -> {
boolean allDeleted = true; DeletionResult result = new DeletionResult();
for (ConversationClient client : clients) { for (ConversationClient client : clients) {
allDeleted = client.deleteAllMessages(txn, c) && allDeleted; result.addDeletionResult(client.deleteAllMessages(txn, c));
} }
return allDeleted; return result;
}); });
} }
@Override @Override
public boolean deleteMessages(ContactId c, Collection<MessageId> toDelete) public DeletionResult deleteMessages(ContactId c, Collection<MessageId> toDelete)
throws DbException { throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn -> {
boolean allDeleted = true; DeletionResult result = new DeletionResult();
for (ConversationClient client : clients) { for (ConversationClient client : clients) {
Set<MessageId> idSet = client.getMessageIds(txn, c); Set<MessageId> idSet = client.getMessageIds(txn, c);
idSet.retainAll(toDelete); idSet.retainAll(toDelete);
allDeleted = client.deleteMessages(txn, c, idSet) && allDeleted; result.addDeletionResult(client.deleteMessages(txn, c, idSet));
} }
return allDeleted; return result;
}); });
} }

View File

@@ -31,6 +31,7 @@ import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient; import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException; import org.briarproject.briar.api.messaging.FileTooBigException;
@@ -430,7 +431,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
} }
@Override @Override
public boolean deleteAllMessages(Transaction txn, ContactId c) public DeletionResult deleteAllMessages(Transaction txn, ContactId c)
throws DbException { throws DbException {
GroupId g = getContactGroup(db.getContact(txn, c)).getId(); GroupId g = getContactGroup(db.getContact(txn, c)).getId();
// this indiscriminately deletes all raw messages in this group // this indiscriminately deletes all raw messages in this group
@@ -440,13 +441,13 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
db.deleteMessageMetadata(txn, messageId); db.deleteMessageMetadata(txn, messageId);
} }
messageTracker.initializeGroupCount(txn, g); messageTracker.initializeGroupCount(txn, g);
return true; return new DeletionResult();
} }
@Override @Override
public boolean deleteMessages(Transaction txn, ContactId c, public DeletionResult deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException { Set<MessageId> messageIds) throws DbException {
boolean allDeleted = true; DeletionResult result = new DeletionResult();
for (MessageId m : messageIds) { for (MessageId m : messageIds) {
// get attachment headers // get attachment headers
List<AttachmentHeader> headers; List<AttachmentHeader> headers;
@@ -480,12 +481,12 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
db.deleteMessage(txn, m); db.deleteMessage(txn, m);
db.deleteMessageMetadata(txn, m); db.deleteMessageMetadata(txn, m);
} else { } else {
allDeleted = false; result.addNotFullyDownloaded();
} }
} }
GroupId g = getContactGroup(db.getContact(txn, c)).getId(); GroupId g = getContactGroup(db.getContact(txn, c)).getId();
recalculateGroupCount(txn, g); recalculateGroupCount(txn, g);
return allDeleted; return result;
} }
private void recalculateGroupCount(Transaction txn, GroupId g) private void recalculateGroupCount(Transaction txn, GroupId g)

View File

@@ -27,6 +27,7 @@ import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVer
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory; import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
@@ -636,7 +637,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
@Override @Override
public boolean deleteAllMessages(Transaction txn, ContactId c) public DeletionResult deleteAllMessages(Transaction txn, ContactId c)
throws DbException { throws DbException {
return deleteMessages(txn, c, (txn1, g, metadata) -> { return deleteMessages(txn, c, (txn1, g, metadata) -> {
// get all sessions and their states // get all sessions and their states
@@ -657,7 +658,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
} }
@Override @Override
public boolean deleteMessages(Transaction txn, ContactId c, public DeletionResult deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException { Set<MessageId> messageIds) throws DbException {
return deleteMessages(txn, c, (txn1, g, metadata) -> { return deleteMessages(txn, c, (txn1, g, metadata) -> {
// get only sessions from given messageIds // get only sessions from given messageIds
@@ -685,7 +686,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
}, messageId -> !messageIds.contains(messageId)); }, messageId -> !messageIds.contains(messageId));
} }
private boolean deleteMessages(Transaction txn, ContactId c, private DeletionResult deleteMessages(Transaction txn, ContactId c,
DeletableSessionRetriever retriever, MessageDeletionChecker checker) DeletableSessionRetriever retriever, MessageDeletionChecker checker)
throws DbException { throws DbException {
// get ID of the contact group // get ID of the contact group
@@ -729,40 +730,43 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
for (MessageStatus status : db.getMessageStatus(txn, c, g)) { for (MessageStatus status : db.getMessageStatus(txn, c, g)) {
if (!status.isSeen()) notAcked.add(status.getMessageId()); if (!status.isSeen()) notAcked.add(status.getMessageId());
} }
boolean allDeleted = deleteCompletedSessions(txn, sessions.values(), DeletionResult result = deleteCompletedSessions(txn, sessions.values(),
notAcked, checker); notAcked, checker);
recalculateGroupCount(txn, g); recalculateGroupCount(txn, g);
return allDeleted; return result;
} }
private boolean deleteCompletedSessions(Transaction txn, private DeletionResult deleteCompletedSessions(Transaction txn,
Collection<DeletableSession> sessions, Set<MessageId> notAcked, Collection<DeletableSession> sessions, Set<MessageId> notAcked,
MessageDeletionChecker checker) throws DbException { MessageDeletionChecker checker) throws DbException {
// find completed sessions to delete // find completed sessions to delete
boolean allDeleted = true; DeletionResult result = new DeletionResult();
for (DeletableSession session : sessions) { for (DeletableSession session : sessions) {
if (session.state.isAwaitingResponse()) { if (session.state.isAwaitingResponse()) {
allDeleted = false; result.addInvitationSessionInProgress();
continue; continue;
} }
// we can only delete sessions // we can only delete sessions
// where delivery of all messages was confirmed (aka ACKed) // where delivery of all messages was confirmed (aka ACKed)
boolean allAcked = true; boolean sessionDeletable = true;
for (MessageId m : session.messages) { for (MessageId m : session.messages) {
if (notAcked.contains(m) || checker.causesProblem(m)) { if (notAcked.contains(m) || checker.causesProblem(m)) {
allAcked = false; sessionDeletable = false;
allDeleted = false; if (notAcked.contains(m))
result.addInvitationSessionInProgress();
if (checker.causesProblem(m))
result.addInvitationNotAllSelected();
break; break;
} }
} }
if (allAcked) { if (sessionDeletable) {
for (MessageId m : session.messages) { for (MessageId m : session.messages) {
db.deleteMessage(txn, m); db.deleteMessage(txn, m);
db.deleteMessageMetadata(txn, m); db.deleteMessageMetadata(txn, m);
} }
} }
} }
return allDeleted; return result;
} }
@Override @Override

View File

@@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.ConversationRequest; import org.briarproject.briar.api.conversation.ConversationRequest;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.sharing.InvitationResponse; import org.briarproject.briar.api.sharing.InvitationResponse;
import org.briarproject.briar.api.sharing.Shareable; import org.briarproject.briar.api.sharing.Shareable;
import org.briarproject.briar.api.sharing.SharingInvitationItem; import org.briarproject.briar.api.sharing.SharingInvitationItem;
@@ -557,7 +558,7 @@ abstract class SharingManagerImpl<S extends Shareable>
} }
@Override @Override
public boolean deleteAllMessages(Transaction txn, ContactId c) public DeletionResult deleteAllMessages(Transaction txn, ContactId c)
throws DbException { throws DbException {
return deleteMessages(txn, c, (txn1, contactGroup, metadata) -> { return deleteMessages(txn, c, (txn1, contactGroup, metadata) -> {
// get all sessions and their states // get all sessions and their states
@@ -578,7 +579,7 @@ abstract class SharingManagerImpl<S extends Shareable>
} }
@Override @Override
public boolean deleteMessages(Transaction txn, ContactId c, public DeletionResult deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException { Set<MessageId> messageIds) throws DbException {
return deleteMessages(txn, c, (txn1, g, metadata) -> { return deleteMessages(txn, c, (txn1, g, metadata) -> {
// get only sessions from given messageIds // get only sessions from given messageIds
@@ -606,7 +607,7 @@ abstract class SharingManagerImpl<S extends Shareable>
}, messageId -> !messageIds.contains(messageId)); }, messageId -> !messageIds.contains(messageId));
} }
private boolean deleteMessages(Transaction txn, ContactId c, private DeletionResult deleteMessages(Transaction txn, ContactId c,
DeletableSessionRetriever retriever, MessageDeletionChecker checker) DeletableSessionRetriever retriever, MessageDeletionChecker checker)
throws DbException { throws DbException {
// get ID of the contact group // get ID of the contact group
@@ -621,7 +622,7 @@ abstract class SharingManagerImpl<S extends Shareable>
throw new DbException(e); throw new DbException(e);
} }
// get all sessions and their states // get sessions and their states
Map<GroupId, DeletableSession> sessions = Map<GroupId, DeletableSession> sessions =
retriever.getDeletableSessions(txn, g, metadata); retriever.getDeletableSessions(txn, g, metadata);
@@ -650,40 +651,43 @@ abstract class SharingManagerImpl<S extends Shareable>
for (MessageStatus status : db.getMessageStatus(txn, c, g)) { for (MessageStatus status : db.getMessageStatus(txn, c, g)) {
if (!status.isSeen()) notAcked.add(status.getMessageId()); if (!status.isSeen()) notAcked.add(status.getMessageId());
} }
boolean allDeleted = deleteCompletedSessions(txn, sessions.values(), DeletionResult result = deleteCompletedSessions(txn, sessions.values(),
notAcked, checker); notAcked, checker);
recalculateGroupCount(txn, g); recalculateGroupCount(txn, g);
return allDeleted; return result;
} }
private boolean deleteCompletedSessions(Transaction txn, private DeletionResult deleteCompletedSessions(Transaction txn,
Collection<DeletableSession> sessions, Set<MessageId> notAcked, Collection<DeletableSession> sessions, Set<MessageId> notAcked,
MessageDeletionChecker checker) throws DbException { MessageDeletionChecker checker) throws DbException {
// find completed sessions to delete // find completed sessions to delete
boolean allDeleted = true; DeletionResult result = new DeletionResult();
for (DeletableSession session : sessions) { for (DeletableSession session : sessions) {
if (session.state.isAwaitingResponse()) { if (session.state.isAwaitingResponse()) {
allDeleted = false; result.addInvitationSessionInProgress();
continue; continue;
} }
// we can only delete sessions // we can only delete sessions
// where delivery of all messages was confirmed (aka ACKed) // where delivery of all messages was confirmed (aka ACKed)
boolean allAcked = true; boolean sessionDeletable = true;
for (MessageId m : session.messages) { for (MessageId m : session.messages) {
if (notAcked.contains(m) || checker.causesProblem(m)) { if (notAcked.contains(m) || checker.causesProblem(m)) {
allAcked = false; sessionDeletable = false;
allDeleted = false; if (notAcked.contains(m))
result.addInvitationSessionInProgress();
if (checker.causesProblem(m))
result.addInvitationNotAllSelected();
break; break;
} }
} }
if (allAcked) { if (sessionDeletable) {
for (MessageId m : session.messages) { for (MessageId m : session.messages) {
db.deleteMessage(txn, m); db.deleteMessage(txn, m);
db.deleteMessageMetadata(txn, m); db.deleteMessageMetadata(txn, m);
} }
} }
} }
return allDeleted; return result;
} }
@Override @Override

View File

@@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.ConversationResponse; import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
@@ -1152,61 +1153,77 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertTrue(deleteAllMessages1From0().hasIntroduction());
assertTrue(deleteAllMessages1From0().hasSessionInProgress());
// introducee1 can not yet remove messages // introducee1 can not yet remove messages
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasIntroduction());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
// sync second REQUEST message // sync second REQUEST message
sync0To2(1, true); sync0To2(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages2From0()); assertFalse(deleteAllMessages2From0().allDeleted());
assertTrue(deleteAllMessages2From0().hasSessionInProgress());
// introducee2 can not yet remove messages // introducee2 can not yet remove messages
assertFalse(deleteAllMessages0From2()); assertFalse(deleteAllMessages0From2().allDeleted());
assertTrue(deleteAllMessages0From2().hasSessionInProgress());
// sync first ACCEPT message // sync first ACCEPT message
sync1To0(1, true); sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertTrue(deleteAllMessages1From0().hasSessionInProgress());
// sync second ACCEPT message // sync second ACCEPT message
sync2To0(1, true); sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages2From0()); assertFalse(deleteAllMessages2From0().allDeleted());
assertTrue(deleteAllMessages2From0().hasSessionInProgress());
// sync forwarded ACCEPT messages to introducees // sync forwarded ACCEPT messages to introducees
sync0To1(1, true); sync0To1(1, true);
sync0To2(1, true); sync0To2(1, true);
// introducee1 can not yet remove messages // introducee1 can not yet remove messages
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
// introducee2 can not yet remove messages // introducee2 can not yet remove messages
assertFalse(deleteAllMessages0From2()); assertFalse(deleteAllMessages0From2().allDeleted());
assertTrue(deleteAllMessages0From2().hasSessionInProgress());
// sync first AUTH and its forward // sync first AUTH and its forward
sync1To0(1, true); sync1To0(1, true);
sync0To2(1, true); sync0To2(1, true);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertFalse(deleteAllMessages2From0()); assertTrue(deleteAllMessages1From0().hasSessionInProgress());
assertFalse(deleteAllMessages2From0().allDeleted());
assertTrue(deleteAllMessages2From0().hasSessionInProgress());
// introducee2 can not yet remove messages // introducee2 can not yet remove messages
assertFalse(deleteAllMessages0From2()); assertFalse(deleteAllMessages0From2().allDeleted());
assertTrue(deleteAllMessages0From2().hasSessionInProgress());
// sync second AUTH and its forward as well as the following ACTIVATE // sync second AUTH and its forward as well as the following ACTIVATE
sync2To0(2, true); sync2To0(2, true);
sync0To1(2, true); sync0To1(2, true);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertFalse(deleteAllMessages2From0()); assertTrue(deleteAllMessages1From0().hasSessionInProgress());
assertFalse(deleteAllMessages2From0().allDeleted());
assertTrue(deleteAllMessages2From0().hasSessionInProgress());
// introducee1 can not yet remove messages // introducee1 can not yet remove messages
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
// sync second ACTIVATE and its forward // sync second ACTIVATE and its forward
sync1To0(1, true); sync1To0(1, true);
@@ -1221,13 +1238,15 @@ public class IntroductionIntegrationTest
assertGroupCount(messageTracker0, g1.getId(), 2, 1); assertGroupCount(messageTracker0, g1.getId(), 2, 1);
// introducer can now remove messages // introducer can now remove messages
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertTrue(deleteAllMessages1From0()); // a second time returns true // a second time returns true
assertTrue(deleteAllMessages1From0().allDeleted());
assertGroupCount(messageTracker0, g1.getId(), 0, 0); assertGroupCount(messageTracker0, g1.getId(), 0, 0);
// introducee1 can not yet remove messages, because last not ACKed // introducee1 can not yet remove messages, because last not ACKed
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
assertEquals(2, getMessages0From1().size()); assertEquals(2, getMessages0From1().size());
// check that introducee1 messages are tracked properly // check that introducee1 messages are tracked properly
@@ -1237,18 +1256,20 @@ public class IntroductionIntegrationTest
sendAcks(c0, c1, contactId1From0, 1); sendAcks(c0, c1, contactId1From0, 1);
// introducee1 can now remove messages // introducee1 can now remove messages
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertTrue(deleteAllMessages0From1()); // a second time returns true // a second time returns true
assertTrue(deleteAllMessages0From1().allDeleted());
assertGroupCount(messageTracker1, g1.getId(), 0, 0); assertGroupCount(messageTracker1, g1.getId(), 0, 0);
// check that introducee2 messages are tracked properly // check that introducee2 messages are tracked properly
assertGroupCount(messageTracker2, g2.getId(), 2, 1); assertGroupCount(messageTracker2, g2.getId(), 2, 1);
// introducee2 can remove messages (last message was incoming) // introducee2 can remove messages (last message was incoming)
assertTrue(deleteAllMessages0From2()); assertTrue(deleteAllMessages0From2().allDeleted());
assertEquals(0, getMessages0From2().size()); assertEquals(0, getMessages0From2().size());
assertTrue(deleteAllMessages0From2()); // a second time returns true // a second time returns true
assertTrue(deleteAllMessages0From2().allDeleted());
assertGroupCount(messageTracker2, g2.getId(), 0, 0); assertGroupCount(messageTracker2, g2.getId(), 0, 0);
// a new introduction is still possible // a new introduction is still possible
@@ -1271,10 +1292,14 @@ public class IntroductionIntegrationTest
assertFalse(listener2.aborted); assertFalse(listener2.aborted);
// nobody can delete anything again // nobody can delete anything again
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertFalse(deleteAllMessages2From0()); assertTrue(deleteAllMessages1From0().hasSessionInProgress());
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages2From0().allDeleted());
assertFalse(deleteAllMessages0From2()); assertTrue(deleteAllMessages2From0().hasSessionInProgress());
assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
assertFalse(deleteAllMessages0From2().allDeleted());
assertTrue(deleteAllMessages0From2().hasSessionInProgress());
// group counts get counted up again correctly // group counts get counted up again correctly
assertGroupCount(messageTracker0, g1.getId(), 2, 1); assertGroupCount(messageTracker0, g1.getId(), 2, 1);
@@ -1302,50 +1327,54 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
// introducee1 can not yet remove messages // introducee1 can not yet remove messages
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
// sync second DECLINE message // sync second DECLINE message
sync2To0(1, true); sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages2From0()); assertFalse(deleteAllMessages2From0().allDeleted());
// introducee2 can not yet remove messages // introducee2 can not yet remove messages
assertFalse(deleteAllMessages0From2()); assertFalse(deleteAllMessages0From2().allDeleted());
// forward first DECLINE message // forward first DECLINE message
sync0To2(1, true); sync0To2(1, true);
// introducee2 can now remove messages // introducee2 can now remove messages
assertTrue(deleteAllMessages0From2()); assertTrue(deleteAllMessages0From2().allDeleted());
assertEquals(0, getMessages0From2().size()); assertEquals(0, getMessages0From2().size());
assertTrue(deleteAllMessages0From2()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages0From2().allDeleted());
// forward second DECLINE message // forward second DECLINE message
sync0To1(1, true); sync0To1(1, true);
// introducee1 can now remove messages // introducee1 can now remove messages
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertTrue(deleteAllMessages0From1()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages0From1().allDeleted());
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertFalse(deleteAllMessages2From0()); assertFalse(deleteAllMessages2From0().allDeleted());
// introducer can remove messages after getting ACK from introducee1 // introducer can remove messages after getting ACK from introducee1
sendAcks(c1, c0, contactId0From1, 1); sendAcks(c1, c0, contactId0From1, 1);
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertTrue(deleteAllMessages1From0()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages1From0().allDeleted());
// introducer can remove messages after getting ACK from introducee2 // introducer can remove messages after getting ACK from introducee2
sendAcks(c2, c0, contactId0From2, 1); sendAcks(c2, c0, contactId0From2, 1);
assertTrue(deleteAllMessages2From0()); assertTrue(deleteAllMessages2From0().allDeleted());
assertEquals(0, getMessages2From0().size()); assertEquals(0, getMessages2From0().size());
assertTrue(deleteAllMessages2From0()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages2From0().allDeleted());
// a new introduction is still possible // a new introduction is still possible
assertTrue(introductionManager0 assertTrue(introductionManager0
@@ -1368,10 +1397,10 @@ public class IntroductionIntegrationTest
assertFalse(listener2.aborted); assertFalse(listener2.aborted);
// nobody can delete anything again // nobody can delete anything again
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertFalse(deleteAllMessages2From0()); assertFalse(deleteAllMessages2From0().allDeleted());
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertFalse(deleteAllMessages0From2()); assertFalse(deleteAllMessages0From2().allDeleted());
} }
@Test @Test
@@ -1401,9 +1430,10 @@ public class IntroductionIntegrationTest
// introducer can remove messages after getting ACK from introducee1 // introducer can remove messages after getting ACK from introducee1
sendAcks(c1, c0, contactId0From1, 1); sendAcks(c1, c0, contactId0From1, 1);
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertTrue(deleteAllMessages1From0()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages1From0().allDeleted());
// a new introduction is still possible // a new introduction is still possible
assertTrue(introductionManager0 assertTrue(introductionManager0
@@ -1422,16 +1452,16 @@ public class IntroductionIntegrationTest
// introducer can remove messages after getting ACK from introducee1 // introducer can remove messages after getting ACK from introducee1
sendAcks(c1, c0, contactId0From1, 1); sendAcks(c1, c0, contactId0From1, 1);
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertTrue(deleteAllMessages1From0()); // a second time nothing happens assertTrue(deleteAllMessages1From0().allDeleted()); // a second time nothing happens
// introducer can remove messages after getting ACK from introducee2 // introducer can remove messages after getting ACK from introducee2
// if this succeeds, we still had the session object after delete above // if this succeeds, we still had the session object after delete above
sendAcks(c2, c0, contactId0From2, 1); sendAcks(c2, c0, contactId0From2, 1);
assertTrue(deleteAllMessages2From0()); assertTrue(deleteAllMessages2From0().allDeleted());
assertEquals(0, getMessages2From0().size()); assertEquals(0, getMessages2From0().size());
assertTrue(deleteAllMessages2From0()); // a second time nothing happens assertTrue(deleteAllMessages2From0().allDeleted()); // a second time nothing happens
// no one should have aborted // no one should have aborted
assertFalse(listener0.aborted); assertFalse(listener0.aborted);
@@ -1454,7 +1484,9 @@ public class IntroductionIntegrationTest
MessageId messageId1 = m1From0.iterator().next().getId(); MessageId messageId1 = m1From0.iterator().next().getId();
Set<MessageId> toDelete1 = new HashSet<>(); Set<MessageId> toDelete1 = new HashSet<>();
toDelete1.add(messageId1); toDelete1.add(messageId1);
assertFalse(deleteMessages1From0(toDelete1)); assertFalse(deleteMessages1From0(toDelete1).allDeleted());
assertTrue(deleteMessages1From0(toDelete1).hasIntroduction());
assertTrue(deleteMessages1From0(toDelete1).hasSessionInProgress());
// deleting the introduction for introducee2 will fail as well // deleting the introduction for introducee2 will fail as well
Collection<ConversationMessageHeader> m2From0 = getMessages2From0(); Collection<ConversationMessageHeader> m2From0 = getMessages2From0();
@@ -1462,7 +1494,9 @@ public class IntroductionIntegrationTest
MessageId messageId2 = m2From0.iterator().next().getId(); MessageId messageId2 = m2From0.iterator().next().getId();
Set<MessageId> toDelete2 = new HashSet<>(); Set<MessageId> toDelete2 = new HashSet<>();
toDelete2.add(messageId2); toDelete2.add(messageId2);
assertFalse(deleteMessages2From0(toDelete2)); assertFalse(deleteMessages2From0(toDelete2).allDeleted());
assertTrue(deleteMessages2From0(toDelete2).hasIntroduction());
assertTrue(deleteMessages2From0(toDelete2).hasSessionInProgress());
// sync REQUEST messages // sync REQUEST messages
sync0To1(1, true); sync0To1(1, true);
@@ -1470,33 +1504,38 @@ public class IntroductionIntegrationTest
sync0To2(1, true); sync0To2(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// deleting introduction fails for introducees, // deleting introduction fails, because responses did not arrive
// because response is not yet selected for deletion assertFalse(deleteMessages0From1(toDelete1).allDeleted());
assertFalse(deleteMessages0From1(toDelete1)); assertTrue(deleteMessages0From1(toDelete1).hasSessionInProgress());
assertFalse(deleteMessages0From2(toDelete2)); assertFalse(deleteMessages0From2(toDelete2).allDeleted());
assertTrue(deleteMessages0From2(toDelete2).hasSessionInProgress());
// add response of introducee1 for deletion // remember response of introducee1 for future deletion
Collection<ConversationMessageHeader> m0From1 = getMessages0From1(); Collection<ConversationMessageHeader> m0From1 = getMessages0From1();
assertEquals(2, m0From1.size()); assertEquals(2, m0From1.size());
MessageId response1 = null;
for (ConversationMessageHeader h : m0From1) { for (ConversationMessageHeader h : m0From1) {
if (!h.getId().equals(messageId1)) toDelete1.add(h.getId()); if (!h.getId().equals(messageId1)) response1 = h.getId();
} }
assertNotNull(response1);
// add response of introducee2 for deletion // remember response of introducee2 for future deletion
Collection<ConversationMessageHeader> m0From2 = getMessages0From2(); Collection<ConversationMessageHeader> m0From2 = getMessages0From2();
assertEquals(2, m0From2.size()); assertEquals(2, m0From2.size());
MessageId response2 = null;
for (ConversationMessageHeader h : m0From2) { for (ConversationMessageHeader h : m0From2) {
if (!h.getId().equals(messageId2)) toDelete2.add(h.getId()); if (!h.getId().equals(messageId2)) response2 = h.getId();
} }
assertNotNull(response2);
// sync first DECLINE message // sync first DECLINE message
sync1To0(1, true); sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// introducer can not yet remove messages // introducer can not yet remove messages
assertFalse(deleteMessages1From0(toDelete1)); assertFalse(deleteMessages1From0(toDelete1).allDeleted());
// introducee1 can not yet remove messages // introducee1 can not yet remove messages
assertFalse(deleteMessages0From1(toDelete1)); assertFalse(deleteMessages0From1(toDelete1).allDeleted());
// sync second DECLINE message // sync second DECLINE message
sync2To0(1, true); sync2To0(1, true);
@@ -1508,77 +1547,104 @@ public class IntroductionIntegrationTest
assertGroupCount(messageTracker1, g1.getId(), 2, 1); assertGroupCount(messageTracker1, g1.getId(), 2, 1);
assertGroupCount(messageTracker2, g2.getId(), 2, 1); assertGroupCount(messageTracker2, g2.getId(), 2, 1);
// introducer can now remove messages with both introducees // introducer can now remove messages with both introducees,
assertTrue(deleteMessages1From0(toDelete1)); // if the responses are also selected
assertTrue(deleteMessages2From0(toDelete2)); Set<MessageId> toDelete1From0 = new HashSet<>(toDelete1);
toDelete1From0.add(response1);
DeletionResult result = deleteMessages1From0(toDelete1From0);
assertTrue(result.allDeleted());
Set<MessageId> toDelete2From0 = new HashSet<>(toDelete2);
toDelete2From0.add(response2);
assertTrue(deleteMessages2From0(toDelete2From0).allDeleted());
assertGroupCount(messageTracker0, g1.getId(), 0, 0); assertGroupCount(messageTracker0, g1.getId(), 0, 0);
assertGroupCount(messageTracker0, g2.getId(), 0, 0); assertGroupCount(messageTracker0, g2.getId(), 0, 0);
// introducee2 can not yet remove messages, missing the other response // introducee2 can not yet remove messages, missing the other response
assertFalse(deleteMessages0From1(toDelete1)); assertFalse(deleteMessages0From1(toDelete1).allDeleted());
// forward first DECLINE message // forward first DECLINE message
sync0To2(1, true); sync0To2(1, true);
// deleting introduction fails for introducee 2,
// because response is not yet selected for deletion
assertFalse(deleteMessages0From2(toDelete2).allDeleted());
assertTrue(deleteMessages0From2(toDelete2).hasIntroduction());
assertTrue(deleteMessages0From2(toDelete2).hasNotAllSelected());
// add response to be deleted as well
toDelete2.add(response2);
// introducee2 can now remove messages // introducee2 can now remove messages
assertTrue(deleteMessages0From2(toDelete2)); assertTrue(deleteMessages0From2(toDelete2).allDeleted());
assertEquals(0, getMessages0From2().size()); assertEquals(0, getMessages0From2().size());
assertTrue(deleteMessages0From2(toDelete2)); // a second time nothing happens // a second time nothing happens
assertTrue(deleteMessages0From2(toDelete2).allDeleted());
assertGroupCount(messageTracker2, g2.getId(), 0, 0); assertGroupCount(messageTracker2, g2.getId(), 0, 0);
// forward second DECLINE message // forward second DECLINE message
sync0To1(1, true); sync0To1(1, true);
// deleting introduction fails for introducee 1,
// because response is not yet selected for deletion
assertFalse(deleteMessages0From1(toDelete1).allDeleted());
assertTrue(deleteMessages0From1(toDelete1).hasIntroduction());
assertTrue(deleteMessages0From1(toDelete1).hasNotAllSelected());
// add response to be deleted as well
toDelete1.add(response1);
// introducee1 can now also remove messages // introducee1 can now also remove messages
assertTrue(deleteMessages0From1(toDelete1)); assertTrue(deleteMessages0From1(toDelete1).allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertTrue(deleteMessages0From1(toDelete1)); // a second time nothing happens // a second time nothing happens
assertTrue(deleteMessages0From1(toDelete1).allDeleted());
assertGroupCount(messageTracker1, g1.getId(), 0, 0); assertGroupCount(messageTracker1, g1.getId(), 0, 0);
} }
@Test @Test
public void testDeletingEmptySet() throws Exception { public void testDeletingEmptySet() throws Exception {
assertTrue(deleteMessages0From1(emptySet())); assertTrue(deleteMessages0From1(emptySet()).allDeleted());
} }
private boolean deleteAllMessages1From0() throws DbException { private DeletionResult deleteAllMessages1From0() throws DbException {
return db0.transactionWithResult(false, txn -> introductionManager0 return db0.transactionWithResult(false, txn -> introductionManager0
.deleteAllMessages(txn, contactId1From0)); .deleteAllMessages(txn, contactId1From0));
} }
private boolean deleteAllMessages2From0() throws DbException { private DeletionResult deleteAllMessages2From0() throws DbException {
return db0.transactionWithResult(false, txn -> introductionManager0 return db0.transactionWithResult(false, txn -> introductionManager0
.deleteAllMessages(txn, contactId2From0)); .deleteAllMessages(txn, contactId2From0));
} }
private boolean deleteAllMessages0From1() throws DbException { private DeletionResult deleteAllMessages0From1() throws DbException {
return db1.transactionWithResult(false, txn -> introductionManager1 return db1.transactionWithResult(false, txn -> introductionManager1
.deleteAllMessages(txn, contactId0From1)); .deleteAllMessages(txn, contactId0From1));
} }
private boolean deleteAllMessages0From2() throws DbException { private DeletionResult deleteAllMessages0From2() throws DbException {
return db2.transactionWithResult(false, txn -> introductionManager2 return db2.transactionWithResult(false, txn -> introductionManager2
.deleteAllMessages(txn, contactId0From2)); .deleteAllMessages(txn, contactId0From2));
} }
private boolean deleteMessages1From0(Set<MessageId> toDelete) private DeletionResult deleteMessages1From0(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db0.transactionWithResult(false, txn -> introductionManager0 return db0.transactionWithResult(false, txn -> introductionManager0
.deleteMessages(txn, contactId1From0, toDelete)); .deleteMessages(txn, contactId1From0, toDelete));
} }
private boolean deleteMessages2From0(Set<MessageId> toDelete) private DeletionResult deleteMessages2From0(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db0.transactionWithResult(false, txn -> introductionManager0 return db0.transactionWithResult(false, txn -> introductionManager0
.deleteMessages(txn, contactId2From0, toDelete)); .deleteMessages(txn, contactId2From0, toDelete));
} }
private boolean deleteMessages0From1(Set<MessageId> toDelete) private DeletionResult deleteMessages0From1(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db1.transactionWithResult(false, txn -> introductionManager1 return db1.transactionWithResult(false, txn -> introductionManager1
.deleteMessages(txn, contactId0From1, toDelete)); .deleteMessages(txn, contactId0From1, toDelete));
} }
private boolean deleteMessages0From2(Set<MessageId> toDelete) private DeletionResult deleteMessages0From2(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db2.transactionWithResult(false, txn -> introductionManager2 return db2.transactionWithResult(false, txn -> introductionManager2
.deleteMessages(txn, contactId0From2, toDelete)); .deleteMessages(txn, contactId0From2, toDelete));

View File

@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessage;
@@ -159,9 +160,11 @@ public class MessagingManagerIntegrationTest
// delete all messages on both sides (deletes all, because returns true) // delete all messages on both sides (deletes all, because returns true)
assertTrue(db0.transactionWithResult(false, assertTrue(db0.transactionWithResult(false,
txn -> messagingManager0.deleteAllMessages(txn, contactId))); txn -> messagingManager0.deleteAllMessages(txn, contactId))
.allDeleted());
assertTrue(db1.transactionWithResult(false, assertTrue(db1.transactionWithResult(false,
txn -> messagingManager1.deleteAllMessages(txn, contactId))); txn -> messagingManager1.deleteAllMessages(txn, contactId))
.allDeleted());
// all messages are gone // all messages are gone
assertEquals(0, getMessages(c0).size()); assertEquals(0, getMessages(c0).size());
@@ -185,9 +188,11 @@ public class MessagingManagerIntegrationTest
toDelete.add(m1.getMessage().getId()); toDelete.add(m1.getMessage().getId());
toDelete.add(m2.getMessage().getId()); toDelete.add(m2.getMessage().getId());
assertTrue(db0.transactionWithResult(false, txn -> assertTrue(db0.transactionWithResult(false, txn ->
messagingManager0.deleteMessages(txn, contactId, toDelete))); messagingManager0.deleteMessages(txn, contactId, toDelete))
.allDeleted());
assertTrue(db1.transactionWithResult(false, txn -> assertTrue(db1.transactionWithResult(false, txn ->
messagingManager1.deleteMessages(txn, contactId, toDelete))); messagingManager1.deleteMessages(txn, contactId, toDelete))
.allDeleted());
// all messages except 1 are gone // all messages except 1 are gone
assertEquals(1, getMessages(c0).size()); assertEquals(1, getMessages(c0).size());
@@ -203,7 +208,8 @@ public class MessagingManagerIntegrationTest
toDelete.clear(); toDelete.clear();
toDelete.add(m0.getMessage().getId()); toDelete.add(m0.getMessage().getId());
assertTrue(db0.transactionWithResult(false, txn -> assertTrue(db0.transactionWithResult(false, txn ->
messagingManager0.deleteMessages(txn, contactId, toDelete))); messagingManager0.deleteMessages(txn, contactId, toDelete))
.allDeleted());
assertEquals(0, getMessages(c0).size()); assertEquals(0, getMessages(c0).size());
assertGroupCounts(c0, 0, 0); assertGroupCounts(c0, 0, 0);
} }
@@ -225,9 +231,9 @@ public class MessagingManagerIntegrationTest
Set<MessageId> toDelete = new HashSet<>(); Set<MessageId> toDelete = new HashSet<>();
toDelete.add(m0.getMessage().getId()); toDelete.add(m0.getMessage().getId());
assertTrue(c0.getConversationManager() assertTrue(c0.getConversationManager()
.deleteMessages(contactId, toDelete)); .deleteMessages(contactId, toDelete).allDeleted());
assertTrue(c1.getConversationManager() assertTrue(c1.getConversationManager()
.deleteMessages(contactId, toDelete)); .deleteMessages(contactId, toDelete).allDeleted());
// message was deleted // message was deleted
assertEquals(0, getMessages(c0).size()); assertEquals(0, getMessages(c0).size());
@@ -246,9 +252,11 @@ public class MessagingManagerIntegrationTest
// delete message on both sides (deletes all, because returns true) // delete message on both sides (deletes all, because returns true)
assertTrue(db0.transactionWithResult(false, assertTrue(db0.transactionWithResult(false,
txn -> messagingManager0.deleteAllMessages(txn, contactId))); txn -> messagingManager0.deleteAllMessages(txn, contactId))
.allDeleted());
assertTrue(db1.transactionWithResult(false, assertTrue(db1.transactionWithResult(false,
txn -> messagingManager1.deleteAllMessages(txn, contactId))); txn -> messagingManager1.deleteAllMessages(txn, contactId))
.allDeleted());
// attachment was deleted on both devices // attachment was deleted on both devices
try { try {
@@ -285,10 +293,14 @@ public class MessagingManagerIntegrationTest
// deleting message fails (on both sides), // deleting message fails (on both sides),
// because attachment is not yet delivered // because attachment is not yet delivered
Set<MessageId> toDelete = singleton(m.getMessage().getId()); Set<MessageId> toDelete = singleton(m.getMessage().getId());
assertFalse(db0.transactionWithResult(false, txn -> DeletionResult result0 = db0.transactionWithResult(false, txn ->
messagingManager0.deleteMessages(txn, contactId, toDelete))); messagingManager0.deleteMessages(txn, contactId, toDelete));
assertFalse(db1.transactionWithResult(false, txn -> assertFalse(result0.allDeleted());
messagingManager1.deleteMessages(txn, contactId, toDelete))); assertTrue(result0.hasNotFullyDownloaded());
DeletionResult result1 = db1.transactionWithResult(false, txn ->
messagingManager1.deleteMessages(txn, contactId, toDelete));
assertFalse(result1.allDeleted());
assertTrue(result1.hasNotFullyDownloaded());
// deliver attachment // deliver attachment
db0.transaction(false, db0.transaction(false,
@@ -298,9 +310,11 @@ public class MessagingManagerIntegrationTest
// deleting message and attachment on both sides works now // deleting message and attachment on both sides works now
assertTrue(db0.transactionWithResult(false, txn -> assertTrue(db0.transactionWithResult(false, txn ->
messagingManager0.deleteMessages(txn, contactId, toDelete))); messagingManager0.deleteMessages(txn, contactId, toDelete))
.allDeleted());
assertTrue(db1.transactionWithResult(false, txn -> assertTrue(db1.transactionWithResult(false, txn ->
messagingManager1.deleteMessages(txn, contactId, toDelete))); messagingManager1.deleteMessages(txn, contactId, toDelete))
.allDeleted());
// attachment was deleted on both devices // attachment was deleted on both devices
try { try {
@@ -320,7 +334,8 @@ public class MessagingManagerIntegrationTest
@Test @Test
public void testDeletingEmptySet() throws Exception { public void testDeletingEmptySet() throws Exception {
assertTrue(db0.transactionWithResult(false, txn -> assertTrue(db0.transactionWithResult(false, txn ->
messagingManager0.deleteMessages(txn, contactId, emptySet()))); messagingManager0.deleteMessages(txn, contactId, emptySet()))
.allDeleted());
} }
private PrivateMessage sendMessage(BriarIntegrationTestComponent from, private PrivateMessage sendMessage(BriarIntegrationTestComponent from,

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
@@ -463,9 +464,13 @@ public class GroupInvitationIntegrationTest
sync0To1(1, true); sync0To1(1, true);
// messages can not be deleted // messages can not be deleted
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertTrue(deleteAllMessages1From0().hasInvitation());
assertTrue(deleteAllMessages1From0().hasSessionInProgress());
assertEquals(1, getMessages1From0().size()); assertEquals(1, getMessages1From0().size());
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasInvitation());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
assertEquals(1, getMessages0From1().size()); assertEquals(1, getMessages0From1().size());
// respond // respond
@@ -478,13 +483,15 @@ public class GroupInvitationIntegrationTest
assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); assertGroupCount(messageTracker1, g0From1.getId(), 2, 1);
// messages can be deleted now by creator, invitee needs to wait for ACK // messages can be deleted now by creator, invitee needs to wait for ACK
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertTrue(deleteAllMessages1From0()); // a second time nothing happens assertTrue(deleteAllMessages1From0().allDeleted()); // a second time nothing happens
assertGroupCount(messageTracker0, g1From0.getId(), 0, 0); assertGroupCount(messageTracker0, g1From0.getId(), 0, 0);
// trying to delete fails for invitee // trying to delete fails for invitee
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasInvitation());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
assertEquals(2, getMessages0From1().size()); assertEquals(2, getMessages0From1().size());
// creator sends two JOIN messages (one sharing + one in private group) // creator sends two JOIN messages (one sharing + one in private group)
@@ -492,9 +499,10 @@ public class GroupInvitationIntegrationTest
sync0To1(2, true); sync0To1(2, true);
// now invitee can also delete messages // now invitee can also delete messages
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertTrue(deleteAllMessages0From1()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages0From1().allDeleted());
assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); assertGroupCount(messageTracker1, g0From1.getId(), 0, 0);
// invitee now leaves // invitee now leaves
@@ -524,21 +532,25 @@ public class GroupInvitationIntegrationTest
assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); assertGroupCount(messageTracker1, g0From1.getId(), 2, 1);
// messages can be deleted now by creator, invitee needs to wait for ACK // messages can be deleted now by creator, invitee needs to wait for ACK
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertTrue(deleteAllMessages1From0()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages1From0().allDeleted());
// trying to delete fails for invitee // trying to delete fails for invitee
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasInvitation());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
assertEquals(2, getMessages0From1().size()); assertEquals(2, getMessages0From1().size());
// creator sends ACK // creator sends ACK
sendAcks(c0, c1, contactId1From0, 1); sendAcks(c0, c1, contactId1From0, 1);
// now invitee can also delete messages // now invitee can also delete messages
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertTrue(deleteAllMessages0From1()); // a second time nothing happens // a second time nothing happens
assertTrue(deleteAllMessages0From1().allDeleted());
assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); assertGroupCount(messageTracker1, g0From1.getId(), 0, 0);
// creator can re-invite // creator can re-invite
@@ -546,8 +558,10 @@ public class GroupInvitationIntegrationTest
sync0To1(1, true); sync0To1(1, true);
// now new messages can not be deleted anymore // now new messages can not be deleted anymore
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertFalse(deleteAllMessages0From1()); assertTrue(deleteAllMessages1From0().hasSessionInProgress());
assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
// responding again // responding again
groupInvitationManager1 groupInvitationManager1
@@ -562,8 +576,8 @@ public class GroupInvitationIntegrationTest
assertGroupCount(messageTracker0, g1From0.getId(), 2, 1); assertGroupCount(messageTracker0, g1From0.getId(), 2, 1);
// deleting is possible again // deleting is possible again
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); assertGroupCount(messageTracker1, g0From1.getId(), 0, 0);
assertGroupCount(messageTracker0, g1From0.getId(), 0, 0); assertGroupCount(messageTracker0, g1From0.getId(), 0, 0);
} }
@@ -580,8 +594,10 @@ public class GroupInvitationIntegrationTest
MessageId messageId = m0.iterator().next().getId(); MessageId messageId = m0.iterator().next().getId();
Set<MessageId> toDelete = new HashSet<>(); Set<MessageId> toDelete = new HashSet<>();
toDelete.add(messageId); toDelete.add(messageId);
assertFalse(deleteMessages1From0(toDelete)); assertFalse(deleteMessages1From0(toDelete).allDeleted());
assertFalse(deleteMessages0From1(toDelete)); assertTrue(deleteMessages1From0(toDelete).hasSessionInProgress());
assertFalse(deleteMessages0From1(toDelete).allDeleted());
assertTrue(deleteMessages0From1(toDelete).hasSessionInProgress());
// respond // respond
groupInvitationManager1 groupInvitationManager1
@@ -590,8 +606,10 @@ public class GroupInvitationIntegrationTest
// both can still not delete the invitation, // both can still not delete the invitation,
// because the response was not selected for deletion as well // because the response was not selected for deletion as well
assertFalse(deleteMessages1From0(toDelete)); assertFalse(deleteMessages1From0(toDelete).allDeleted());
assertFalse(deleteMessages0From1(toDelete)); assertTrue(deleteMessages1From0(toDelete).hasNotAllSelected());
assertFalse(deleteMessages0From1(toDelete).allDeleted());
assertTrue(deleteMessages0From1(toDelete).hasNotAllSelected());
// after selecting response, both messages can be deleted by creator // after selecting response, both messages can be deleted by creator
m0 = getMessages1From0(); m0 = getMessages1From0();
@@ -600,14 +618,15 @@ public class GroupInvitationIntegrationTest
if (!h.getId().equals(messageId)) toDelete.add(h.getId()); if (!h.getId().equals(messageId)) toDelete.add(h.getId());
} }
assertGroupCount(messageTracker0, g1From0.getId(), 2, 1); assertGroupCount(messageTracker0, g1From0.getId(), 2, 1);
assertTrue(deleteMessages1From0(toDelete)); assertTrue(deleteMessages1From0(toDelete).allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
// a second time nothing happens // a second time nothing happens
assertTrue(deleteMessages1From0(toDelete)); assertTrue(deleteMessages1From0(toDelete).allDeleted());
assertGroupCount(messageTracker0, g1From0.getId(), 0, 0); assertGroupCount(messageTracker0, g1From0.getId(), 0, 0);
// 1 can still not delete the messages, as last one has not been ACKed // 1 can still not delete the messages, as last one has not been ACKed
assertFalse(deleteMessages0From1(toDelete)); assertFalse(deleteMessages0From1(toDelete).allDeleted());
assertTrue(deleteMessages0From1(toDelete).hasSessionInProgress());
assertEquals(2, getMessages0From1().size()); assertEquals(2, getMessages0From1().size());
assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); assertGroupCount(messageTracker1, g0From1.getId(), 2, 1);
@@ -615,16 +634,16 @@ public class GroupInvitationIntegrationTest
sendAcks(c0, c1, contactId1From0, 1); sendAcks(c0, c1, contactId1From0, 1);
// 1 can now delete all messages, as last one has been ACKed // 1 can now delete all messages, as last one has been ACKed
assertTrue(deleteMessages0From1(toDelete)); assertTrue(deleteMessages0From1(toDelete).allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); assertGroupCount(messageTracker1, g0From1.getId(), 0, 0);
// a second time nothing happens // a second time nothing happens
assertTrue(deleteMessages0From1(toDelete)); assertTrue(deleteMessages0From1(toDelete).allDeleted());
} }
@Test @Test
public void testDeletingEmptySet() throws Exception { public void testDeletingEmptySet() throws Exception {
assertTrue(deleteMessages0From1(emptySet())); assertTrue(deleteMessages0From1(emptySet()).allDeleted());
} }
private Collection<ConversationMessageHeader> getMessages1From0() private Collection<ConversationMessageHeader> getMessages1From0()
@@ -639,23 +658,23 @@ public class GroupInvitationIntegrationTest
.getMessageHeaders(txn, contactId0From1)); .getMessageHeaders(txn, contactId0From1));
} }
private boolean deleteAllMessages1From0() throws DbException { private DeletionResult deleteAllMessages1From0() throws DbException {
return db0.transactionWithResult(false, txn -> groupInvitationManager0 return db0.transactionWithResult(false, txn -> groupInvitationManager0
.deleteAllMessages(txn, contactId1From0)); .deleteAllMessages(txn, contactId1From0));
} }
private boolean deleteAllMessages0From1() throws DbException { private DeletionResult deleteAllMessages0From1() throws DbException {
return db1.transactionWithResult(false, txn -> groupInvitationManager1 return db1.transactionWithResult(false, txn -> groupInvitationManager1
.deleteAllMessages(txn, contactId0From1)); .deleteAllMessages(txn, contactId0From1));
} }
private boolean deleteMessages1From0(Set<MessageId> toDelete) private DeletionResult deleteMessages1From0(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db0.transactionWithResult(false, txn -> groupInvitationManager0 return db0.transactionWithResult(false, txn -> groupInvitationManager0
.deleteMessages(txn, contactId1From0, toDelete)); .deleteMessages(txn, contactId1From0, toDelete));
} }
private boolean deleteMessages0From1(Set<MessageId> toDelete) private DeletionResult deleteMessages0From1(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db1.transactionWithResult(false, txn -> groupInvitationManager1 return db1.transactionWithResult(false, txn -> groupInvitationManager1
.deleteMessages(txn, contactId0From1, toDelete)); .deleteMessages(txn, contactId0From1, toDelete));

View File

@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.ConversationResponse; import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationRequest; import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumInvitationResponse;
@@ -864,8 +865,12 @@ public class ForumSharingIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// messages can not be deleted // messages can not be deleted
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertFalse(deleteAllMessages0From1()); assertTrue(deleteAllMessages1From0().hasInvitation());
assertTrue(deleteAllMessages1From0().hasSessionInProgress());
assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasInvitation());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
// accept invitation // accept invitation
respondToRequest(contactId0From1, true); respondToRequest(contactId0From1, true);
@@ -881,19 +886,20 @@ public class ForumSharingIntegrationTest
assertGroupCount(messageTracker1, g0From1, 2, 1); assertGroupCount(messageTracker1, g0From1, 2, 1);
// 0 deletes all messages // 0 deletes all messages
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertGroupCount(messageTracker0, g1From0, 0, 0); assertGroupCount(messageTracker0, g1From0, 0, 0);
// 1 can not delete all messages, as last one has not been ACKed // 1 can not delete all messages, as last one has not been ACKed
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
assertGroupCount(messageTracker1, g0From1, 2, 1); assertGroupCount(messageTracker1, g0From1, 2, 1);
// 0 sends an ACK to their last message // 0 sends an ACK to their last message
sendAcks(c0, c1, contactId1From0, 1); sendAcks(c0, c1, contactId1From0, 1);
// 1 can now delete all messages, as last one has been ACKed // 1 can now delete all messages, as last one has been ACKed
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertGroupCount(messageTracker1, g0From1, 0, 0); assertGroupCount(messageTracker1, g0From1, 0, 0);
@@ -911,10 +917,12 @@ public class ForumSharingIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// messages can not be deleted anymore // messages can not be deleted anymore
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertTrue(deleteAllMessages1From0().hasSessionInProgress());
assertEquals(1, getMessages1From0().size()); assertEquals(1, getMessages1From0().size());
assertGroupCount(messageTracker0, g1From0, 1, 1); assertGroupCount(messageTracker0, g1From0, 1, 1);
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertTrue(deleteAllMessages0From1().hasSessionInProgress());
assertEquals(1, getMessages0From1().size()); assertEquals(1, getMessages0From1().size());
assertGroupCount(messageTracker1, g0From1, 1, 0); assertGroupCount(messageTracker1, g0From1, 1, 0);
@@ -926,10 +934,10 @@ public class ForumSharingIntegrationTest
sendAcks(c1, c0, contactId0From1, 1); sendAcks(c1, c0, contactId0From1, 1);
// messages can now get deleted again // messages can now get deleted again
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
assertGroupCount(messageTracker0, g1From0, 0, 0); assertGroupCount(messageTracker0, g1From0, 0, 0);
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
assertGroupCount(messageTracker1, g0From1, 0, 0); assertGroupCount(messageTracker1, g0From1, 0, 0);
} }
@@ -949,17 +957,17 @@ public class ForumSharingIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// 0 deletes all messages // 0 deletes all messages
assertTrue(deleteAllMessages1From0()); assertTrue(deleteAllMessages1From0().allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
// 1 can not delete all messages, as last one has not been ACKed // 1 can not delete all messages, as last one has not been ACKed
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
// 0 sends an ACK to their last message // 0 sends an ACK to their last message
sendAcks(c0, c1, contactId1From0, 1); sendAcks(c0, c1, contactId1From0, 1);
// 1 can now delete all messages, as last one has been ACKed // 1 can now delete all messages, as last one has been ACKed
assertTrue(deleteAllMessages0From1()); assertTrue(deleteAllMessages0From1().allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
// re-sending invitation is possible // re-sending invitation is possible
@@ -969,9 +977,9 @@ public class ForumSharingIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// messages can not be deleted anymore // messages can not be deleted anymore
assertFalse(deleteAllMessages1From0()); assertFalse(deleteAllMessages1From0().allDeleted());
assertEquals(1, getMessages1From0().size()); assertEquals(1, getMessages1From0().size());
assertFalse(deleteAllMessages0From1()); assertFalse(deleteAllMessages0From1().allDeleted());
assertEquals(1, getMessages0From1().size()); assertEquals(1, getMessages0From1().size());
} }
@@ -989,7 +997,9 @@ public class ForumSharingIntegrationTest
MessageId messageId = m0.iterator().next().getId(); MessageId messageId = m0.iterator().next().getId();
Set<MessageId> toDelete = new HashSet<>(); Set<MessageId> toDelete = new HashSet<>();
toDelete.add(messageId); toDelete.add(messageId);
assertFalse(deleteMessages1From0(toDelete)); assertFalse(deleteMessages1From0(toDelete).allDeleted());
assertTrue(deleteMessages1From0(toDelete).hasInvitation());
assertTrue(deleteMessages1From0(toDelete).hasSessionInProgress());
// decline invitation // decline invitation
respondToRequest(contactId0From1, true); respondToRequest(contactId0From1, true);
@@ -998,8 +1008,12 @@ public class ForumSharingIntegrationTest
// both can still not delete the invitation, // both can still not delete the invitation,
// because the response was not selected for deletion as well // because the response was not selected for deletion as well
assertFalse(deleteMessages1From0(toDelete)); assertFalse(deleteMessages1From0(toDelete).allDeleted());
assertFalse(deleteMessages0From1(toDelete)); assertTrue(deleteMessages1From0(toDelete).hasInvitation());
assertTrue(deleteMessages1From0(toDelete).hasNotAllSelected());
assertFalse(deleteMessages0From1(toDelete).allDeleted());
assertTrue(deleteMessages0From1(toDelete).hasInvitation());
assertTrue(deleteMessages0From1(toDelete).hasNotAllSelected());
// after selecting response, both messages can be deleted // after selecting response, both messages can be deleted
m0 = getMessages1From0(); m0 = getMessages1From0();
@@ -1007,27 +1021,30 @@ public class ForumSharingIntegrationTest
for (ConversationMessageHeader h : m0) { for (ConversationMessageHeader h : m0) {
if (!h.getId().equals(messageId)) toDelete.add(h.getId()); if (!h.getId().equals(messageId)) toDelete.add(h.getId());
} }
assertTrue(deleteMessages1From0(toDelete)); assertTrue(deleteMessages1From0(toDelete).allDeleted());
assertEquals(0, getMessages1From0().size()); assertEquals(0, getMessages1From0().size());
// a second time nothing happens // a second time nothing happens
assertTrue(deleteMessages1From0(toDelete)); assertTrue(deleteMessages1From0(toDelete).allDeleted());
// 1 can still not delete the messages, as last one has not been ACKed // 1 can still not delete the messages, as last one has not been ACKed
assertFalse(deleteMessages0From1(toDelete)); assertFalse(deleteMessages0From1(toDelete).allDeleted());
assertFalse(deleteMessages0From1(toDelete).hasNotAllSelected());
assertTrue(deleteMessages0From1(toDelete).hasInvitation());
assertTrue(deleteMessages0From1(toDelete).hasSessionInProgress());
// 0 sends an ACK to their last message // 0 sends an ACK to their last message
sendAcks(c0, c1, contactId1From0, 1); sendAcks(c0, c1, contactId1From0, 1);
// 1 can now delete all messages, as last one has been ACKed // 1 can now delete all messages, as last one has been ACKed
assertTrue(deleteMessages0From1(toDelete)); assertTrue(deleteMessages0From1(toDelete).allDeleted());
assertEquals(0, getMessages0From1().size()); assertEquals(0, getMessages0From1().size());
// a second time nothing happens // a second time nothing happens
assertTrue(deleteMessages0From1(toDelete)); assertTrue(deleteMessages0From1(toDelete).allDeleted());
} }
@Test @Test
public void testDeletingEmptySet() throws Exception { public void testDeletingEmptySet() throws Exception {
assertTrue(deleteMessages0From1(emptySet())); assertTrue(deleteMessages0From1(emptySet()).allDeleted());
} }
private Collection<ConversationMessageHeader> getMessages1From0() private Collection<ConversationMessageHeader> getMessages1From0()
@@ -1042,23 +1059,23 @@ public class ForumSharingIntegrationTest
.getMessageHeaders(txn, contactId0From1)); .getMessageHeaders(txn, contactId0From1));
} }
private boolean deleteAllMessages1From0() throws DbException { private DeletionResult deleteAllMessages1From0() throws DbException {
return db0.transactionWithResult(false, txn -> forumSharingManager0 return db0.transactionWithResult(false, txn -> forumSharingManager0
.deleteAllMessages(txn, contactId1From0)); .deleteAllMessages(txn, contactId1From0));
} }
private boolean deleteAllMessages0From1() throws DbException { private DeletionResult deleteAllMessages0From1() throws DbException {
return db1.transactionWithResult(false, txn -> forumSharingManager1 return db1.transactionWithResult(false, txn -> forumSharingManager1
.deleteAllMessages(txn, contactId0From1)); .deleteAllMessages(txn, contactId0From1));
} }
private boolean deleteMessages1From0(Set<MessageId> toDelete) private DeletionResult deleteMessages1From0(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db0.transactionWithResult(false, txn -> forumSharingManager0 return db0.transactionWithResult(false, txn -> forumSharingManager0
.deleteMessages(txn, contactId1From0, toDelete)); .deleteMessages(txn, contactId1From0, toDelete));
} }
private boolean deleteMessages0From1(Set<MessageId> toDelete) private DeletionResult deleteMessages0From1(Set<MessageId> toDelete)
throws DbException { throws DbException {
return db1.transactionWithResult(false, txn -> forumSharingManager1 return db1.transactionWithResult(false, txn -> forumSharingManager1
.deleteMessages(txn, contactId0From1, toDelete)); .deleteMessages(txn, contactId0From1, toDelete));