diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index dc48c59f9..990508c26 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -67,6 +67,7 @@ import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationRequest; 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.forum.ForumSharingManager; import org.briarproject.briar.api.introduction.IntroductionManager; @@ -857,9 +858,9 @@ public class ConversationActivity extends BriarActivity list.showProgressBar(); runOnDbThread(() -> { try { - boolean allDeleted = + DeletionResult result = conversationManager.deleteAllMessages(contactId); - reloadConversationAfterDeletingMessages(allDeleted); + reloadConversationAfterDeletingMessages(result); } catch (DbException e) { logException(LOG, WARNING, e); runOnUiThreadUnlessDestroyed(() -> list.showData()); @@ -874,9 +875,9 @@ public class ConversationActivity extends BriarActivity if (actionMode != null) actionMode.finish(); runOnDbThread(() -> { try { - boolean allDeleted = + DeletionResult result = conversationManager.deleteMessages(contactId, selected); - reloadConversationAfterDeletingMessages(allDeleted); + reloadConversationAfterDeletingMessages(result); } catch (DbException e) { logException(LOG, WARNING, e); runOnUiThreadUnlessDestroyed(() -> list.showData()); @@ -885,22 +886,55 @@ public class ConversationActivity extends BriarActivity } private void reloadConversationAfterDeletingMessages( - boolean allDeleted) { + DeletionResult result) { runOnUiThreadUnlessDestroyed(() -> { adapter.clear(); list.showProgressBar(); // otherwise clearing shows empty state 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 = new AlertDialog.Builder(this, R.style.BriarDialogTheme); builder.setTitle( getString(R.string.dialog_title_not_all_messages_deleted)); - builder.setMessage( - getString(R.string.dialog_message_not_all_messages_deleted)); + builder.setMessage(msg.toString()); builder.setPositiveButton(R.string.ok, null); builder.show(); } @@ -912,7 +946,8 @@ public class ConversationActivity extends BriarActivity new AlertDialog.Builder(ConversationActivity.this, R.style.BriarDialogTheme); 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.setPositiveButton(R.string.cancel, null); builder.show(); @@ -953,7 +988,8 @@ public class ConversationActivity extends BriarActivity private void showImageOnboarding() { // TODO: remove cast when removing feature flag - ((TextAttachmentController) sendController).showImageOnboarding(this); + ((TextAttachmentController) sendController) + .showImageOnboarding(this); } private void showIntroductionOnboarding(@Nullable Boolean show) { @@ -973,7 +1009,8 @@ public class ConversationActivity extends BriarActivity View target = null; for (int i = 0; i < toolbar.getChildCount(); i++) { 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 target = menu.getChildAt(menu.getChildCount() - 1); // If the menu hasn't been populated yet, use the menu itself @@ -999,7 +1036,8 @@ public class ConversationActivity extends BriarActivity @UiThread @Override - public void respondToRequest(ConversationRequestItem item, boolean accept) { + public void respondToRequest(ConversationRequestItem item, + boolean accept) { item.setAnswered(); int position = adapter.findItemPosition(item); if (position != INVALID_POSITION) { diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 61c21bdd6..7baae09d8 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -139,7 +139,11 @@ Confirm Message Deletion Are you sure that you want to delete all messages? Could not delete all messages - Messages related to\n\n• ongoing introductions\n• ongoing (blog/forum/group) invitations\n• partly downloaded messages\n\ncannot be deleted until they conclude. + Messages related to\n\n%s\ncan not be deleted until they conclude. + • ongoing introductions + • ongoing (blog/forum/group) invitations + • partly downloaded messages + To delete an invitation/introduction, you need to select the request and the response. Delete contact Confirm Contact Deletion Are you sure that you want to remove this contact and all messages exchanged with this contact? diff --git a/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java b/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java index fee34cd6b..4eeb6a7fb 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/conversation/ConversationManager.java @@ -17,6 +17,12 @@ import java.util.Set; @NotNullByDefault 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 * register themselves here. @@ -39,17 +45,13 @@ public interface ConversationManager { /** * 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. - * - * @return true if all given messages could be deleted, false otherwise */ - boolean deleteMessages(ContactId c, Collection messageIds) + DeletionResult deleteMessages(ContactId c, Collection messageIds) throws DbException; @NotNullByDefault @@ -75,10 +77,8 @@ public interface ConversationManager { /** * 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; /** @@ -86,10 +86,8 @@ public interface ConversationManager { *

* The set of message IDs must only include message IDs returned by * {@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 messageIds) throws DbException; } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/conversation/DeletionResult.java b/briar-api/src/main/java/org/briarproject/briar/api/conversation/DeletionResult.java new file mode 100644 index 000000000..214024cd1 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/conversation/DeletionResult.java @@ -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; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 0fdb774ca..cc3bfb2c9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -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.SessionId; 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.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionResponse; @@ -561,73 +562,45 @@ class IntroductionManagerImpl extends ConversationClientImpl @FunctionalInterface private interface MessageRetriever { - Map getMessages(Transaction txn, - 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); + Set getMessages(Set allMessages); } @Override - public boolean deleteAllMessages(Transaction txn, ContactId c) + public DeletionResult deleteAllMessages(Transaction txn, ContactId c) throws DbException { - return deleteMessages(txn, c, (txn1, g) -> { - // get metadata for all messages in the group - Map messages; - try { - messages = clientHelper.getMessageMetadataAsDictionary(txn1, g); - } catch (FormatException e) { - throw new DbException(e); - } - return messages; - }, messageId -> false); + return deleteMessages(txn, c, allMessages -> allMessages); } @Override - public boolean deleteMessages(Transaction txn, ContactId c, + public DeletionResult deleteMessages(Transaction txn, ContactId c, Set messageIds) throws DbException { - return deleteMessages(txn, c, (txn1, g) -> { - // get metadata for messages that shall be deleted - Map 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)); + return deleteMessages(txn, c, allMessages -> messageIds); } - private boolean deleteMessages(Transaction txn, ContactId c, - MessageRetriever retriever, MessageDeletionChecker checker) - throws DbException { + private DeletionResult deleteMessages(Transaction txn, ContactId c, + MessageRetriever retriever) throws DbException { // get ID of the contact group GroupId g = getContactGroup(db.getContact(txn, c)).getId(); - // get messages to be deleted - Map messages = retriever.getMessages(txn, g); + // get metadata for all messages in the group + Map 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 selected = retriever.getMessages(messages.keySet()); + + // get sessions for selected messages Map sessions = new HashMap<>(); - for (Entry entry : messages.entrySet()) { + for (MessageId id : selected) { + BdfDictionary d = messages.get(id); + if (d == null) continue; // throw new NoSuchMessageException() MessageMetadata m; try { - m = messageParser.parseMetadata(entry.getValue()); + m = messageParser.parseMetadata(d); } catch (FormatException e) { throw new DbException(e); } @@ -644,6 +617,31 @@ class IntroductionManagerImpl extends ConversationClientImpl session = getDeletableSession(txn, g, m.getSessionId()); sessions.put(m.getSessionId(), session); } + session.messages.add(id); + } + + // assign other protocol messages to their sessions + for (Entry 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()); } @@ -652,10 +650,10 @@ class IntroductionManagerImpl extends ConversationClientImpl for (MessageStatus status : db.getMessageStatus(txn, c, g)) { if (!status.isSeen()) notAcked.add(status.getMessageId()); } - boolean allDeleted = - deleteCompletedSessions(txn, sessions, notAcked, checker); + DeletionResult result = + deleteCompletedSessions(txn, sessions, notAcked, selected); recalculateGroupCount(txn, g); - return allDeleted; + return result; } private DeletableSession getDeletableSession(Transaction txn, @@ -677,28 +675,31 @@ class IntroductionManagerImpl extends ConversationClientImpl } } - private boolean deleteCompletedSessions(Transaction txn, + private DeletionResult deleteCompletedSessions(Transaction txn, Map sessions, Set notAcked, - MessageDeletionChecker checker) throws DbException { + Set selected) throws DbException { // find completed sessions to delete - boolean allDeleted = true; + DeletionResult result = new DeletionResult(); for (DeletableSession session : sessions.values()) { if (!session.state.isComplete()) { - allDeleted = false; + result.addIntroductionSessionInProgress(); continue; } // we can only delete sessions // where delivery of all messages was confirmed (aka ACKed) - boolean allAcked = true; + boolean sessionDeletable = true; for (MessageId m : session.messages) { - if (notAcked.contains(m) || checker.causesProblem(m)) { - allAcked = false; - allDeleted = false; + if (notAcked.contains(m) || !selected.contains(m)) { + sessionDeletable = false; + if (notAcked.contains(m)) + result.addIntroductionSessionInProgress(); + if (!selected.contains(m)) + result.addIntroductionNotAllSelected(); break; } } // delete messages of session, if all were ACKed - if (allAcked) { + if (sessionDeletable) { for (MessageId m : session.messages) { db.deleteMessage(txn, m); db.deleteMessageMetadata(txn, m); @@ -707,7 +708,7 @@ class IntroductionManagerImpl extends ConversationClientImpl // and then needs the previous MessageIds } } - return allDeleted; + return result; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java index 7163e4c80..7b991f3de 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/ConversationManagerImpl.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationMessageHeader; +import org.briarproject.briar.api.conversation.DeletionResult; import java.util.ArrayList; import java.util.Collection; @@ -75,27 +76,27 @@ class ConversationManagerImpl implements ConversationManager { } @Override - public boolean deleteAllMessages(ContactId c) throws DbException { + public DeletionResult deleteAllMessages(ContactId c) throws DbException { return db.transactionWithResult(false, txn -> { - boolean allDeleted = true; + DeletionResult result = new DeletionResult(); for (ConversationClient client : clients) { - allDeleted = client.deleteAllMessages(txn, c) && allDeleted; + result.addDeletionResult(client.deleteAllMessages(txn, c)); } - return allDeleted; + return result; }); } @Override - public boolean deleteMessages(ContactId c, Collection toDelete) + public DeletionResult deleteMessages(ContactId c, Collection toDelete) throws DbException { return db.transactionWithResult(false, txn -> { - boolean allDeleted = true; + DeletionResult result = new DeletionResult(); for (ConversationClient client : clients) { Set idSet = client.getMessageIds(txn, c); idSet.retainAll(toDelete); - allDeleted = client.deleteMessages(txn, c, idSet) && allDeleted; + result.addDeletionResult(client.deleteMessages(txn, c, idSet)); } - return allDeleted; + return result; }); } diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java index 36072a4d7..c048e0034 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java @@ -31,6 +31,7 @@ import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.conversation.ConversationManager.ConversationClient; 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.AttachmentHeader; import org.briarproject.briar.api.messaging.FileTooBigException; @@ -430,7 +431,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook, } @Override - public boolean deleteAllMessages(Transaction txn, ContactId c) + public DeletionResult deleteAllMessages(Transaction txn, ContactId c) throws DbException { GroupId g = getContactGroup(db.getContact(txn, c)).getId(); // this indiscriminately deletes all raw messages in this group @@ -440,13 +441,13 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook, db.deleteMessageMetadata(txn, messageId); } messageTracker.initializeGroupCount(txn, g); - return true; + return new DeletionResult(); } @Override - public boolean deleteMessages(Transaction txn, ContactId c, + public DeletionResult deleteMessages(Transaction txn, ContactId c, Set messageIds) throws DbException { - boolean allDeleted = true; + DeletionResult result = new DeletionResult(); for (MessageId m : messageIds) { // get attachment headers List headers; @@ -480,12 +481,12 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook, db.deleteMessage(txn, m); db.deleteMessageMetadata(txn, m); } else { - allDeleted = false; + result.addNotFullyDownloaded(); } } GroupId g = getContactGroup(db.getContact(txn, c)).getId(); recalculateGroupCount(txn, g); - return allDeleted; + return result; } private void recalculateGroupCount(Transaction txn, GroupId g) diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java index fdacd865e..f3c18e328 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java @@ -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.SessionId; 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.PrivateGroupFactory; import org.briarproject.briar.api.privategroup.PrivateGroupManager; @@ -636,7 +637,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl } @Override - public boolean deleteAllMessages(Transaction txn, ContactId c) + public DeletionResult deleteAllMessages(Transaction txn, ContactId c) throws DbException { return deleteMessages(txn, c, (txn1, g, metadata) -> { // get all sessions and their states @@ -657,7 +658,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl } @Override - public boolean deleteMessages(Transaction txn, ContactId c, + public DeletionResult deleteMessages(Transaction txn, ContactId c, Set messageIds) throws DbException { return deleteMessages(txn, c, (txn1, g, metadata) -> { // get only sessions from given messageIds @@ -685,7 +686,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl }, messageId -> !messageIds.contains(messageId)); } - private boolean deleteMessages(Transaction txn, ContactId c, + private DeletionResult deleteMessages(Transaction txn, ContactId c, DeletableSessionRetriever retriever, MessageDeletionChecker checker) throws DbException { // get ID of the contact group @@ -729,40 +730,43 @@ class GroupInvitationManagerImpl extends ConversationClientImpl for (MessageStatus status : db.getMessageStatus(txn, c, g)) { if (!status.isSeen()) notAcked.add(status.getMessageId()); } - boolean allDeleted = deleteCompletedSessions(txn, sessions.values(), + DeletionResult result = deleteCompletedSessions(txn, sessions.values(), notAcked, checker); recalculateGroupCount(txn, g); - return allDeleted; + return result; } - private boolean deleteCompletedSessions(Transaction txn, + private DeletionResult deleteCompletedSessions(Transaction txn, Collection sessions, Set notAcked, MessageDeletionChecker checker) throws DbException { // find completed sessions to delete - boolean allDeleted = true; + DeletionResult result = new DeletionResult(); for (DeletableSession session : sessions) { if (session.state.isAwaitingResponse()) { - allDeleted = false; + result.addInvitationSessionInProgress(); continue; } // we can only delete sessions // where delivery of all messages was confirmed (aka ACKed) - boolean allAcked = true; + boolean sessionDeletable = true; for (MessageId m : session.messages) { if (notAcked.contains(m) || checker.causesProblem(m)) { - allAcked = false; - allDeleted = false; + sessionDeletable = false; + if (notAcked.contains(m)) + result.addInvitationSessionInProgress(); + if (checker.causesProblem(m)) + result.addInvitationNotAllSelected(); break; } } - if (allAcked) { + if (sessionDeletable) { for (MessageId m : session.messages) { db.deleteMessage(txn, m); db.deleteMessageMetadata(txn, m); } } } - return allDeleted; + return result; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java index 4f7ce604a..0e3aba42b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java @@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.conversation.ConversationMessageHeader; 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.Shareable; import org.briarproject.briar.api.sharing.SharingInvitationItem; @@ -557,7 +558,7 @@ abstract class SharingManagerImpl } @Override - public boolean deleteAllMessages(Transaction txn, ContactId c) + public DeletionResult deleteAllMessages(Transaction txn, ContactId c) throws DbException { return deleteMessages(txn, c, (txn1, contactGroup, metadata) -> { // get all sessions and their states @@ -578,7 +579,7 @@ abstract class SharingManagerImpl } @Override - public boolean deleteMessages(Transaction txn, ContactId c, + public DeletionResult deleteMessages(Transaction txn, ContactId c, Set messageIds) throws DbException { return deleteMessages(txn, c, (txn1, g, metadata) -> { // get only sessions from given messageIds @@ -606,7 +607,7 @@ abstract class SharingManagerImpl }, messageId -> !messageIds.contains(messageId)); } - private boolean deleteMessages(Transaction txn, ContactId c, + private DeletionResult deleteMessages(Transaction txn, ContactId c, DeletableSessionRetriever retriever, MessageDeletionChecker checker) throws DbException { // get ID of the contact group @@ -621,7 +622,7 @@ abstract class SharingManagerImpl throw new DbException(e); } - // get all sessions and their states + // get sessions and their states Map sessions = retriever.getDeletableSessions(txn, g, metadata); @@ -650,40 +651,43 @@ abstract class SharingManagerImpl for (MessageStatus status : db.getMessageStatus(txn, c, g)) { if (!status.isSeen()) notAcked.add(status.getMessageId()); } - boolean allDeleted = deleteCompletedSessions(txn, sessions.values(), + DeletionResult result = deleteCompletedSessions(txn, sessions.values(), notAcked, checker); recalculateGroupCount(txn, g); - return allDeleted; + return result; } - private boolean deleteCompletedSessions(Transaction txn, + private DeletionResult deleteCompletedSessions(Transaction txn, Collection sessions, Set notAcked, MessageDeletionChecker checker) throws DbException { // find completed sessions to delete - boolean allDeleted = true; + DeletionResult result = new DeletionResult(); for (DeletableSession session : sessions) { if (session.state.isAwaitingResponse()) { - allDeleted = false; + result.addInvitationSessionInProgress(); continue; } // we can only delete sessions // where delivery of all messages was confirmed (aka ACKed) - boolean allAcked = true; + boolean sessionDeletable = true; for (MessageId m : session.messages) { if (notAcked.contains(m) || checker.causesProblem(m)) { - allAcked = false; - allDeleted = false; + sessionDeletable = false; + if (notAcked.contains(m)) + result.addInvitationSessionInProgress(); + if (checker.causesProblem(m)) + result.addInvitationNotAllSelected(); break; } } - if (allAcked) { + if (sessionDeletable) { for (MessageId m : session.messages) { db.deleteMessage(txn, m); db.deleteMessageMetadata(txn, m); } } } - return allDeleted; + return result; } @Override diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index b53b6465d..720bf8219 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.conversation.ConversationMessageHeader; 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.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionResponse; @@ -1152,61 +1153,77 @@ public class IntroductionIntegrationTest eventWaiter.await(TIMEOUT, 1); // introducer can not yet remove messages - assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasIntroduction()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); // introducee1 can not yet remove messages - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasIntroduction()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); // sync second REQUEST message sync0To2(1, true); eventWaiter.await(TIMEOUT, 1); // introducer can not yet remove messages - assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages2From0().allDeleted()); + assertTrue(deleteAllMessages2From0().hasSessionInProgress()); // introducee2 can not yet remove messages - assertFalse(deleteAllMessages0From2()); + assertFalse(deleteAllMessages0From2().allDeleted()); + assertTrue(deleteAllMessages0From2().hasSessionInProgress()); // sync first ACCEPT message sync1To0(1, true); eventWaiter.await(TIMEOUT, 1); // introducer can not yet remove messages - assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); // sync second ACCEPT message sync2To0(1, true); eventWaiter.await(TIMEOUT, 1); // introducer can not yet remove messages - assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages2From0().allDeleted()); + assertTrue(deleteAllMessages2From0().hasSessionInProgress()); // sync forwarded ACCEPT messages to introducees sync0To1(1, true); sync0To2(1, true); // introducee1 can not yet remove messages - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); // introducee2 can not yet remove messages - assertFalse(deleteAllMessages0From2()); + assertFalse(deleteAllMessages0From2().allDeleted()); + assertTrue(deleteAllMessages0From2().hasSessionInProgress()); // sync first AUTH and its forward sync1To0(1, true); sync0To2(1, true); // introducer can not yet remove messages - assertFalse(deleteAllMessages1From0()); - assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); + assertFalse(deleteAllMessages2From0().allDeleted()); + assertTrue(deleteAllMessages2From0().hasSessionInProgress()); // 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 sync2To0(2, true); sync0To1(2, true); // introducer can not yet remove messages - assertFalse(deleteAllMessages1From0()); - assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); + assertFalse(deleteAllMessages2From0().allDeleted()); + assertTrue(deleteAllMessages2From0().hasSessionInProgress()); // introducee1 can not yet remove messages - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); // sync second ACTIVATE and its forward sync1To0(1, true); @@ -1221,13 +1238,15 @@ public class IntroductionIntegrationTest assertGroupCount(messageTracker0, g1.getId(), 2, 1); // introducer can now remove messages - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); 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); // introducee1 can not yet remove messages, because last not ACKed - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); assertEquals(2, getMessages0From1().size()); // check that introducee1 messages are tracked properly @@ -1237,18 +1256,20 @@ public class IntroductionIntegrationTest sendAcks(c0, c1, contactId1From0, 1); // introducee1 can now remove messages - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages0From1().allDeleted()); 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); // check that introducee2 messages are tracked properly assertGroupCount(messageTracker2, g2.getId(), 2, 1); // introducee2 can remove messages (last message was incoming) - assertTrue(deleteAllMessages0From2()); + assertTrue(deleteAllMessages0From2().allDeleted()); 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); // a new introduction is still possible @@ -1271,10 +1292,14 @@ public class IntroductionIntegrationTest assertFalse(listener2.aborted); // nobody can delete anything again - assertFalse(deleteAllMessages1From0()); - assertFalse(deleteAllMessages2From0()); - assertFalse(deleteAllMessages0From1()); - assertFalse(deleteAllMessages0From2()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); + assertFalse(deleteAllMessages2From0().allDeleted()); + assertTrue(deleteAllMessages2From0().hasSessionInProgress()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); + assertFalse(deleteAllMessages0From2().allDeleted()); + assertTrue(deleteAllMessages0From2().hasSessionInProgress()); // group counts get counted up again correctly assertGroupCount(messageTracker0, g1.getId(), 2, 1); @@ -1302,50 +1327,54 @@ public class IntroductionIntegrationTest eventWaiter.await(TIMEOUT, 1); // introducer can not yet remove messages - assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); // introducee1 can not yet remove messages - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); // sync second DECLINE message sync2To0(1, true); eventWaiter.await(TIMEOUT, 1); // introducer can not yet remove messages - assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages2From0().allDeleted()); // introducee2 can not yet remove messages - assertFalse(deleteAllMessages0From2()); + assertFalse(deleteAllMessages0From2().allDeleted()); // forward first DECLINE message sync0To2(1, true); // introducee2 can now remove messages - assertTrue(deleteAllMessages0From2()); + assertTrue(deleteAllMessages0From2().allDeleted()); assertEquals(0, getMessages0From2().size()); - assertTrue(deleteAllMessages0From2()); // a second time nothing happens + // a second time nothing happens + assertTrue(deleteAllMessages0From2().allDeleted()); // forward second DECLINE message sync0To1(1, true); // introducee1 can now remove messages - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages0From1().allDeleted()); 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 - assertFalse(deleteAllMessages1From0()); - assertFalse(deleteAllMessages2From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertFalse(deleteAllMessages2From0().allDeleted()); // introducer can remove messages after getting ACK from introducee1 sendAcks(c1, c0, contactId0From1, 1); - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); 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 sendAcks(c2, c0, contactId0From2, 1); - assertTrue(deleteAllMessages2From0()); + assertTrue(deleteAllMessages2From0().allDeleted()); 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 assertTrue(introductionManager0 @@ -1368,10 +1397,10 @@ public class IntroductionIntegrationTest assertFalse(listener2.aborted); // nobody can delete anything again - assertFalse(deleteAllMessages1From0()); - assertFalse(deleteAllMessages2From0()); - assertFalse(deleteAllMessages0From1()); - assertFalse(deleteAllMessages0From2()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertFalse(deleteAllMessages2From0().allDeleted()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertFalse(deleteAllMessages0From2().allDeleted()); } @Test @@ -1401,9 +1430,10 @@ public class IntroductionIntegrationTest // introducer can remove messages after getting ACK from introducee1 sendAcks(c1, c0, contactId0From1, 1); - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); 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 assertTrue(introductionManager0 @@ -1422,16 +1452,16 @@ public class IntroductionIntegrationTest // introducer can remove messages after getting ACK from introducee1 sendAcks(c1, c0, contactId0From1, 1); - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); 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 // if this succeeds, we still had the session object after delete above sendAcks(c2, c0, contactId0From2, 1); - assertTrue(deleteAllMessages2From0()); + assertTrue(deleteAllMessages2From0().allDeleted()); assertEquals(0, getMessages2From0().size()); - assertTrue(deleteAllMessages2From0()); // a second time nothing happens + assertTrue(deleteAllMessages2From0().allDeleted()); // a second time nothing happens // no one should have aborted assertFalse(listener0.aborted); @@ -1454,7 +1484,9 @@ public class IntroductionIntegrationTest MessageId messageId1 = m1From0.iterator().next().getId(); Set toDelete1 = new HashSet<>(); 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 Collection m2From0 = getMessages2From0(); @@ -1462,7 +1494,9 @@ public class IntroductionIntegrationTest MessageId messageId2 = m2From0.iterator().next().getId(); Set toDelete2 = new HashSet<>(); toDelete2.add(messageId2); - assertFalse(deleteMessages2From0(toDelete2)); + assertFalse(deleteMessages2From0(toDelete2).allDeleted()); + assertTrue(deleteMessages2From0(toDelete2).hasIntroduction()); + assertTrue(deleteMessages2From0(toDelete2).hasSessionInProgress()); // sync REQUEST messages sync0To1(1, true); @@ -1470,33 +1504,38 @@ public class IntroductionIntegrationTest sync0To2(1, true); eventWaiter.await(TIMEOUT, 1); - // deleting introduction fails for introducees, - // because response is not yet selected for deletion - assertFalse(deleteMessages0From1(toDelete1)); - assertFalse(deleteMessages0From2(toDelete2)); + // deleting introduction fails, because responses did not arrive + assertFalse(deleteMessages0From1(toDelete1).allDeleted()); + assertTrue(deleteMessages0From1(toDelete1).hasSessionInProgress()); + assertFalse(deleteMessages0From2(toDelete2).allDeleted()); + assertTrue(deleteMessages0From2(toDelete2).hasSessionInProgress()); - // add response of introducee1 for deletion + // remember response of introducee1 for future deletion Collection m0From1 = getMessages0From1(); assertEquals(2, m0From1.size()); + MessageId response1 = null; 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 m0From2 = getMessages0From2(); assertEquals(2, m0From2.size()); + MessageId response2 = null; 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 sync1To0(1, true); eventWaiter.await(TIMEOUT, 1); // introducer can not yet remove messages - assertFalse(deleteMessages1From0(toDelete1)); + assertFalse(deleteMessages1From0(toDelete1).allDeleted()); // introducee1 can not yet remove messages - assertFalse(deleteMessages0From1(toDelete1)); + assertFalse(deleteMessages0From1(toDelete1).allDeleted()); // sync second DECLINE message sync2To0(1, true); @@ -1508,77 +1547,104 @@ public class IntroductionIntegrationTest assertGroupCount(messageTracker1, g1.getId(), 2, 1); assertGroupCount(messageTracker2, g2.getId(), 2, 1); - // introducer can now remove messages with both introducees - assertTrue(deleteMessages1From0(toDelete1)); - assertTrue(deleteMessages2From0(toDelete2)); + // introducer can now remove messages with both introducees, + // if the responses are also selected + Set toDelete1From0 = new HashSet<>(toDelete1); + toDelete1From0.add(response1); + DeletionResult result = deleteMessages1From0(toDelete1From0); + assertTrue(result.allDeleted()); + Set toDelete2From0 = new HashSet<>(toDelete2); + toDelete2From0.add(response2); + assertTrue(deleteMessages2From0(toDelete2From0).allDeleted()); assertGroupCount(messageTracker0, g1.getId(), 0, 0); assertGroupCount(messageTracker0, g2.getId(), 0, 0); + // introducee2 can not yet remove messages, missing the other response - assertFalse(deleteMessages0From1(toDelete1)); + assertFalse(deleteMessages0From1(toDelete1).allDeleted()); // forward first DECLINE message 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 - assertTrue(deleteMessages0From2(toDelete2)); + assertTrue(deleteMessages0From2(toDelete2).allDeleted()); 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); // forward second DECLINE message 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 - assertTrue(deleteMessages0From1(toDelete1)); + assertTrue(deleteMessages0From1(toDelete1).allDeleted()); 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); } @Test 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 .deleteAllMessages(txn, contactId1From0)); } - private boolean deleteAllMessages2From0() throws DbException { + private DeletionResult deleteAllMessages2From0() throws DbException { return db0.transactionWithResult(false, txn -> introductionManager0 .deleteAllMessages(txn, contactId2From0)); } - private boolean deleteAllMessages0From1() throws DbException { + private DeletionResult deleteAllMessages0From1() throws DbException { return db1.transactionWithResult(false, txn -> introductionManager1 .deleteAllMessages(txn, contactId0From1)); } - private boolean deleteAllMessages0From2() throws DbException { + private DeletionResult deleteAllMessages0From2() throws DbException { return db2.transactionWithResult(false, txn -> introductionManager2 .deleteAllMessages(txn, contactId0From2)); } - private boolean deleteMessages1From0(Set toDelete) + private DeletionResult deleteMessages1From0(Set toDelete) throws DbException { return db0.transactionWithResult(false, txn -> introductionManager0 .deleteMessages(txn, contactId1From0, toDelete)); } - private boolean deleteMessages2From0(Set toDelete) + private DeletionResult deleteMessages2From0(Set toDelete) throws DbException { return db0.transactionWithResult(false, txn -> introductionManager0 .deleteMessages(txn, contactId2From0, toDelete)); } - private boolean deleteMessages0From1(Set toDelete) + private DeletionResult deleteMessages0From1(Set toDelete) throws DbException { return db1.transactionWithResult(false, txn -> introductionManager1 .deleteMessages(txn, contactId0From1, toDelete)); } - private boolean deleteMessages0From2(Set toDelete) + private DeletionResult deleteMessages0From2(Set toDelete) throws DbException { return db2.transactionWithResult(false, txn -> introductionManager2 .deleteMessages(txn, contactId0From2, toDelete)); diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessagingManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessagingManagerIntegrationTest.java index f5cf206f9..44333fb6d 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessagingManagerIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessagingManagerIntegrationTest.java @@ -7,6 +7,7 @@ import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.TestDatabaseConfigModule; 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.MessagingManager; 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) assertTrue(db0.transactionWithResult(false, - txn -> messagingManager0.deleteAllMessages(txn, contactId))); + txn -> messagingManager0.deleteAllMessages(txn, contactId)) + .allDeleted()); assertTrue(db1.transactionWithResult(false, - txn -> messagingManager1.deleteAllMessages(txn, contactId))); + txn -> messagingManager1.deleteAllMessages(txn, contactId)) + .allDeleted()); // all messages are gone assertEquals(0, getMessages(c0).size()); @@ -185,9 +188,11 @@ public class MessagingManagerIntegrationTest toDelete.add(m1.getMessage().getId()); toDelete.add(m2.getMessage().getId()); assertTrue(db0.transactionWithResult(false, txn -> - messagingManager0.deleteMessages(txn, contactId, toDelete))); + messagingManager0.deleteMessages(txn, contactId, toDelete)) + .allDeleted()); assertTrue(db1.transactionWithResult(false, txn -> - messagingManager1.deleteMessages(txn, contactId, toDelete))); + messagingManager1.deleteMessages(txn, contactId, toDelete)) + .allDeleted()); // all messages except 1 are gone assertEquals(1, getMessages(c0).size()); @@ -203,7 +208,8 @@ public class MessagingManagerIntegrationTest toDelete.clear(); toDelete.add(m0.getMessage().getId()); assertTrue(db0.transactionWithResult(false, txn -> - messagingManager0.deleteMessages(txn, contactId, toDelete))); + messagingManager0.deleteMessages(txn, contactId, toDelete)) + .allDeleted()); assertEquals(0, getMessages(c0).size()); assertGroupCounts(c0, 0, 0); } @@ -225,9 +231,9 @@ public class MessagingManagerIntegrationTest Set toDelete = new HashSet<>(); toDelete.add(m0.getMessage().getId()); assertTrue(c0.getConversationManager() - .deleteMessages(contactId, toDelete)); + .deleteMessages(contactId, toDelete).allDeleted()); assertTrue(c1.getConversationManager() - .deleteMessages(contactId, toDelete)); + .deleteMessages(contactId, toDelete).allDeleted()); // message was deleted assertEquals(0, getMessages(c0).size()); @@ -246,9 +252,11 @@ public class MessagingManagerIntegrationTest // delete message on both sides (deletes all, because returns true) assertTrue(db0.transactionWithResult(false, - txn -> messagingManager0.deleteAllMessages(txn, contactId))); + txn -> messagingManager0.deleteAllMessages(txn, contactId)) + .allDeleted()); assertTrue(db1.transactionWithResult(false, - txn -> messagingManager1.deleteAllMessages(txn, contactId))); + txn -> messagingManager1.deleteAllMessages(txn, contactId)) + .allDeleted()); // attachment was deleted on both devices try { @@ -285,10 +293,14 @@ public class MessagingManagerIntegrationTest // deleting message fails (on both sides), // because attachment is not yet delivered Set toDelete = singleton(m.getMessage().getId()); - assertFalse(db0.transactionWithResult(false, txn -> - messagingManager0.deleteMessages(txn, contactId, toDelete))); - assertFalse(db1.transactionWithResult(false, txn -> - messagingManager1.deleteMessages(txn, contactId, toDelete))); + DeletionResult result0 = db0.transactionWithResult(false, txn -> + messagingManager0.deleteMessages(txn, contactId, toDelete)); + assertFalse(result0.allDeleted()); + assertTrue(result0.hasNotFullyDownloaded()); + DeletionResult result1 = db1.transactionWithResult(false, txn -> + messagingManager1.deleteMessages(txn, contactId, toDelete)); + assertFalse(result1.allDeleted()); + assertTrue(result1.hasNotFullyDownloaded()); // deliver attachment db0.transaction(false, @@ -298,9 +310,11 @@ public class MessagingManagerIntegrationTest // deleting message and attachment on both sides works now assertTrue(db0.transactionWithResult(false, txn -> - messagingManager0.deleteMessages(txn, contactId, toDelete))); + messagingManager0.deleteMessages(txn, contactId, toDelete)) + .allDeleted()); assertTrue(db1.transactionWithResult(false, txn -> - messagingManager1.deleteMessages(txn, contactId, toDelete))); + messagingManager1.deleteMessages(txn, contactId, toDelete)) + .allDeleted()); // attachment was deleted on both devices try { @@ -320,7 +334,8 @@ public class MessagingManagerIntegrationTest @Test public void testDeletingEmptySet() throws Exception { assertTrue(db0.transactionWithResult(false, txn -> - messagingManager0.deleteMessages(txn, contactId, emptySet()))); + messagingManager0.deleteMessages(txn, contactId, emptySet())) + .allDeleted()); } private PrivateMessage sendMessage(BriarIntegrationTestComponent from, diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java index 70406c7eb..b89448c2f 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.briar.api.client.ProtocolStateException; 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.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroupManager; @@ -463,9 +464,13 @@ public class GroupInvitationIntegrationTest sync0To1(1, true); // messages can not be deleted - assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasInvitation()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); assertEquals(1, getMessages1From0().size()); - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasInvitation()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); assertEquals(1, getMessages0From1().size()); // respond @@ -478,13 +483,15 @@ public class GroupInvitationIntegrationTest assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); // messages can be deleted now by creator, invitee needs to wait for ACK - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); 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); // trying to delete fails for invitee - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasInvitation()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); assertEquals(2, getMessages0From1().size()); // creator sends two JOIN messages (one sharing + one in private group) @@ -492,9 +499,10 @@ public class GroupInvitationIntegrationTest sync0To1(2, true); // now invitee can also delete messages - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages0From1().allDeleted()); 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); // invitee now leaves @@ -524,21 +532,25 @@ public class GroupInvitationIntegrationTest assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); // messages can be deleted now by creator, invitee needs to wait for ACK - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); 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 - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasInvitation()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); assertEquals(2, getMessages0From1().size()); // creator sends ACK sendAcks(c0, c1, contactId1From0, 1); // now invitee can also delete messages - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages0From1().allDeleted()); 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); // creator can re-invite @@ -546,8 +558,10 @@ public class GroupInvitationIntegrationTest sync0To1(1, true); // now new messages can not be deleted anymore - assertFalse(deleteAllMessages1From0()); - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); // responding again groupInvitationManager1 @@ -562,8 +576,8 @@ public class GroupInvitationIntegrationTest assertGroupCount(messageTracker0, g1From0.getId(), 2, 1); // deleting is possible again - assertTrue(deleteAllMessages1From0()); - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages0From1().allDeleted()); assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); assertGroupCount(messageTracker0, g1From0.getId(), 0, 0); } @@ -580,8 +594,10 @@ public class GroupInvitationIntegrationTest MessageId messageId = m0.iterator().next().getId(); Set toDelete = new HashSet<>(); toDelete.add(messageId); - assertFalse(deleteMessages1From0(toDelete)); - assertFalse(deleteMessages0From1(toDelete)); + assertFalse(deleteMessages1From0(toDelete).allDeleted()); + assertTrue(deleteMessages1From0(toDelete).hasSessionInProgress()); + assertFalse(deleteMessages0From1(toDelete).allDeleted()); + assertTrue(deleteMessages0From1(toDelete).hasSessionInProgress()); // respond groupInvitationManager1 @@ -590,8 +606,10 @@ public class GroupInvitationIntegrationTest // both can still not delete the invitation, // because the response was not selected for deletion as well - assertFalse(deleteMessages1From0(toDelete)); - assertFalse(deleteMessages0From1(toDelete)); + assertFalse(deleteMessages1From0(toDelete).allDeleted()); + assertTrue(deleteMessages1From0(toDelete).hasNotAllSelected()); + assertFalse(deleteMessages0From1(toDelete).allDeleted()); + assertTrue(deleteMessages0From1(toDelete).hasNotAllSelected()); // after selecting response, both messages can be deleted by creator m0 = getMessages1From0(); @@ -600,14 +618,15 @@ public class GroupInvitationIntegrationTest if (!h.getId().equals(messageId)) toDelete.add(h.getId()); } assertGroupCount(messageTracker0, g1From0.getId(), 2, 1); - assertTrue(deleteMessages1From0(toDelete)); + assertTrue(deleteMessages1From0(toDelete).allDeleted()); assertEquals(0, getMessages1From0().size()); // a second time nothing happens - assertTrue(deleteMessages1From0(toDelete)); + assertTrue(deleteMessages1From0(toDelete).allDeleted()); assertGroupCount(messageTracker0, g1From0.getId(), 0, 0); // 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()); assertGroupCount(messageTracker1, g0From1.getId(), 2, 1); @@ -615,16 +634,16 @@ public class GroupInvitationIntegrationTest sendAcks(c0, c1, contactId1From0, 1); // 1 can now delete all messages, as last one has been ACKed - assertTrue(deleteMessages0From1(toDelete)); + assertTrue(deleteMessages0From1(toDelete).allDeleted()); assertEquals(0, getMessages0From1().size()); assertGroupCount(messageTracker1, g0From1.getId(), 0, 0); // a second time nothing happens - assertTrue(deleteMessages0From1(toDelete)); + assertTrue(deleteMessages0From1(toDelete).allDeleted()); } @Test public void testDeletingEmptySet() throws Exception { - assertTrue(deleteMessages0From1(emptySet())); + assertTrue(deleteMessages0From1(emptySet()).allDeleted()); } private Collection getMessages1From0() @@ -639,23 +658,23 @@ public class GroupInvitationIntegrationTest .getMessageHeaders(txn, contactId0From1)); } - private boolean deleteAllMessages1From0() throws DbException { + private DeletionResult deleteAllMessages1From0() throws DbException { return db0.transactionWithResult(false, txn -> groupInvitationManager0 .deleteAllMessages(txn, contactId1From0)); } - private boolean deleteAllMessages0From1() throws DbException { + private DeletionResult deleteAllMessages0From1() throws DbException { return db1.transactionWithResult(false, txn -> groupInvitationManager1 .deleteAllMessages(txn, contactId0From1)); } - private boolean deleteMessages1From0(Set toDelete) + private DeletionResult deleteMessages1From0(Set toDelete) throws DbException { return db0.transactionWithResult(false, txn -> groupInvitationManager0 .deleteMessages(txn, contactId1From0, toDelete)); } - private boolean deleteMessages0From1(Set toDelete) + private DeletionResult deleteMessages0From1(Set toDelete) throws DbException { return db1.transactionWithResult(false, txn -> groupInvitationManager1 .deleteMessages(txn, contactId0From1, toDelete)); diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java index 20084eb98..17ce546f6 100644 --- a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java @@ -16,6 +16,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.briar.api.conversation.ConversationMessageHeader; 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.ForumInvitationRequest; import org.briarproject.briar.api.forum.ForumInvitationResponse; @@ -864,8 +865,12 @@ public class ForumSharingIntegrationTest eventWaiter.await(TIMEOUT, 1); // messages can not be deleted - assertFalse(deleteAllMessages1From0()); - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasInvitation()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasInvitation()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); // accept invitation respondToRequest(contactId0From1, true); @@ -881,19 +886,20 @@ public class ForumSharingIntegrationTest assertGroupCount(messageTracker1, g0From1, 2, 1); // 0 deletes all messages - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); assertEquals(0, getMessages1From0().size()); assertGroupCount(messageTracker0, g1From0, 0, 0); // 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); // 0 sends an ACK to their last message sendAcks(c0, c1, contactId1From0, 1); // 1 can now delete all messages, as last one has been ACKed - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages0From1().allDeleted()); assertEquals(0, getMessages0From1().size()); assertGroupCount(messageTracker1, g0From1, 0, 0); @@ -911,10 +917,12 @@ public class ForumSharingIntegrationTest eventWaiter.await(TIMEOUT, 1); // messages can not be deleted anymore - assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); + assertTrue(deleteAllMessages1From0().hasSessionInProgress()); assertEquals(1, getMessages1From0().size()); assertGroupCount(messageTracker0, g1From0, 1, 1); - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); + assertTrue(deleteAllMessages0From1().hasSessionInProgress()); assertEquals(1, getMessages0From1().size()); assertGroupCount(messageTracker1, g0From1, 1, 0); @@ -926,10 +934,10 @@ public class ForumSharingIntegrationTest sendAcks(c1, c0, contactId0From1, 1); // messages can now get deleted again - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); assertEquals(0, getMessages1From0().size()); assertGroupCount(messageTracker0, g1From0, 0, 0); - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages0From1().allDeleted()); assertEquals(0, getMessages0From1().size()); assertGroupCount(messageTracker1, g0From1, 0, 0); } @@ -949,17 +957,17 @@ public class ForumSharingIntegrationTest eventWaiter.await(TIMEOUT, 1); // 0 deletes all messages - assertTrue(deleteAllMessages1From0()); + assertTrue(deleteAllMessages1From0().allDeleted()); assertEquals(0, getMessages1From0().size()); // 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 sendAcks(c0, c1, contactId1From0, 1); // 1 can now delete all messages, as last one has been ACKed - assertTrue(deleteAllMessages0From1()); + assertTrue(deleteAllMessages0From1().allDeleted()); assertEquals(0, getMessages0From1().size()); // re-sending invitation is possible @@ -969,9 +977,9 @@ public class ForumSharingIntegrationTest eventWaiter.await(TIMEOUT, 1); // messages can not be deleted anymore - assertFalse(deleteAllMessages1From0()); + assertFalse(deleteAllMessages1From0().allDeleted()); assertEquals(1, getMessages1From0().size()); - assertFalse(deleteAllMessages0From1()); + assertFalse(deleteAllMessages0From1().allDeleted()); assertEquals(1, getMessages0From1().size()); } @@ -989,7 +997,9 @@ public class ForumSharingIntegrationTest MessageId messageId = m0.iterator().next().getId(); Set toDelete = new HashSet<>(); toDelete.add(messageId); - assertFalse(deleteMessages1From0(toDelete)); + assertFalse(deleteMessages1From0(toDelete).allDeleted()); + assertTrue(deleteMessages1From0(toDelete).hasInvitation()); + assertTrue(deleteMessages1From0(toDelete).hasSessionInProgress()); // decline invitation respondToRequest(contactId0From1, true); @@ -998,8 +1008,12 @@ public class ForumSharingIntegrationTest // both can still not delete the invitation, // because the response was not selected for deletion as well - assertFalse(deleteMessages1From0(toDelete)); - assertFalse(deleteMessages0From1(toDelete)); + assertFalse(deleteMessages1From0(toDelete).allDeleted()); + 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 m0 = getMessages1From0(); @@ -1007,27 +1021,30 @@ public class ForumSharingIntegrationTest for (ConversationMessageHeader h : m0) { if (!h.getId().equals(messageId)) toDelete.add(h.getId()); } - assertTrue(deleteMessages1From0(toDelete)); + assertTrue(deleteMessages1From0(toDelete).allDeleted()); assertEquals(0, getMessages1From0().size()); // 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 - 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 sendAcks(c0, c1, contactId1From0, 1); // 1 can now delete all messages, as last one has been ACKed - assertTrue(deleteMessages0From1(toDelete)); + assertTrue(deleteMessages0From1(toDelete).allDeleted()); assertEquals(0, getMessages0From1().size()); // a second time nothing happens - assertTrue(deleteMessages0From1(toDelete)); + assertTrue(deleteMessages0From1(toDelete).allDeleted()); } @Test public void testDeletingEmptySet() throws Exception { - assertTrue(deleteMessages0From1(emptySet())); + assertTrue(deleteMessages0From1(emptySet()).allDeleted()); } private Collection getMessages1From0() @@ -1042,23 +1059,23 @@ public class ForumSharingIntegrationTest .getMessageHeaders(txn, contactId0From1)); } - private boolean deleteAllMessages1From0() throws DbException { + private DeletionResult deleteAllMessages1From0() throws DbException { return db0.transactionWithResult(false, txn -> forumSharingManager0 .deleteAllMessages(txn, contactId1From0)); } - private boolean deleteAllMessages0From1() throws DbException { + private DeletionResult deleteAllMessages0From1() throws DbException { return db1.transactionWithResult(false, txn -> forumSharingManager1 .deleteAllMessages(txn, contactId0From1)); } - private boolean deleteMessages1From0(Set toDelete) + private DeletionResult deleteMessages1From0(Set toDelete) throws DbException { return db0.transactionWithResult(false, txn -> forumSharingManager0 .deleteMessages(txn, contactId1From0, toDelete)); } - private boolean deleteMessages0From1(Set toDelete) + private DeletionResult deleteMessages0From1(Set toDelete) throws DbException { return db1.transactionWithResult(false, txn -> forumSharingManager1 .deleteMessages(txn, contactId0From1, toDelete));