From 124e2f99b008aa2f75026e232a662945fcef995f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 21 Oct 2019 16:23:55 -0300 Subject: [PATCH] [core] Add method to ConversationClient for deleting a set of messages This also implements the method for MessagingManager (including integration tests) and adds no-op implementations for other clients. --- .../api/conversation/ConversationManager.java | 11 + .../introduction/IntroductionManagerImpl.java | 6 + .../briar/messaging/MessagingManagerImpl.java | 38 +++ .../GroupInvitationManagerImpl.java | 6 + .../briar/sharing/SharingManagerImpl.java | 6 + .../MessagingManagerIntegrationTest.java | 296 ++++++++++++++++++ .../briar/test/BriarIntegrationTest.java | 2 +- .../test/BriarIntegrationTestComponent.java | 6 + 8 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 briar-core/src/test/java/org/briarproject/briar/messaging/MessagingManagerIntegrationTest.java 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 8ab792c3e..e33faca9a 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 @@ -72,6 +72,17 @@ public interface ConversationManager { */ boolean deleteAllMessages(Transaction txn, ContactId c) throws DbException; + + /** + * Deletes the given set of messages associated with the given contact. + *

+ * 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, + Set messageIds) throws DbException; } } 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 3be4536ca..60997da29 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 @@ -660,6 +660,12 @@ class IntroductionManagerImpl extends ConversationClientImpl return allDeleted; } + @Override + public boolean deleteMessages(Transaction txn, ContactId c, + Set messageIds) throws DbException { + return false; + } + @Override public Set getMessageIds(Transaction txn, ContactId c) throws DbException { 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 938b67a39..f17becb04 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 @@ -435,4 +435,42 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook, return true; } + @Override + public boolean deleteMessages(Transaction txn, ContactId c, + Set messageIds) throws DbException { + for (MessageId messageId : messageIds) { + db.deleteMessage(txn, messageId); + db.deleteMessageMetadata(txn, messageId); + } + GroupId g = getContactGroup(db.getContact(txn, c)).getId(); + recalculateGroupCount(txn, g); + return true; + } + + private void recalculateGroupCount(Transaction txn, GroupId g) + throws DbException { + BdfDictionary query = BdfDictionary.of( + new BdfEntry(MSG_KEY_MSG_TYPE, PRIVATE_MESSAGE)); + Map results; + try { + results = + clientHelper.getMessageMetadataAsDictionary(txn, g, query); + } catch (FormatException e) { + throw new DbException(e); + } + int msgCount = results.size(); + int unreadCount = 0; + for (Map.Entry entry : results.entrySet()) { + BdfDictionary meta = entry.getValue(); + boolean read; + try { + read = meta.getBoolean(MSG_KEY_READ); + } catch (FormatException e) { + throw new DbException(e); + } + if (!read) unreadCount++; + } + messageTracker.resetGroupCount(txn, g, msgCount, unreadCount); + } + } 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 b8a8aed7b..78164d898 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 @@ -729,6 +729,12 @@ class GroupInvitationManagerImpl extends ConversationClientImpl return allDeleted; } + @Override + public boolean deleteMessages(Transaction txn, ContactId c, + Set messageIds) throws DbException { + return false; + } + @Override public Set getMessageIds(Transaction txn, ContactId c) throws DbException { 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 4ebadd4c6..efc6b0b9f 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 @@ -629,6 +629,12 @@ abstract class SharingManagerImpl return allDeleted; } + @Override + public boolean deleteMessages(Transaction txn, ContactId c, + Set messageIds) throws DbException { + return false; + } + @Override public Set getMessageIds(Transaction txn, ContactId c) throws DbException { 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 new file mode 100644 index 000000000..f23bd3334 --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessagingManagerIntegrationTest.java @@ -0,0 +1,296 @@ +package org.briarproject.briar.messaging; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.MessageDeletedException; +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.messaging.AttachmentHeader; +import org.briarproject.briar.api.messaging.MessagingManager; +import org.briarproject.briar.api.messaging.PrivateMessage; +import org.briarproject.briar.api.messaging.PrivateMessageFactory; +import org.briarproject.briar.api.messaging.PrivateMessageHeader; +import org.briarproject.briar.test.BriarIntegrationTest; +import org.briarproject.briar.test.BriarIntegrationTestComponent; +import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class MessagingManagerIntegrationTest + extends BriarIntegrationTest { + + private DatabaseComponent db0, db1; + private MessagingManager messagingManager0, messagingManager1; + private PrivateMessageFactory messageFactory; + private ContactId contactId; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + db0 = c0.getDatabaseComponent(); + db1 = c1.getDatabaseComponent(); + messagingManager0 = c0.getMessagingManager(); + messagingManager1 = c1.getMessagingManager(); + messageFactory = c0.getPrivateMessageFactory(); + assertEquals(contact0From1, contact1From0); + contactId = contactId0From1; + } + + @Override + protected void createComponents() { + BriarIntegrationTestComponent component = + DaggerBriarIntegrationTestComponent.builder().build(); + component.injectBriarEagerSingletons(); + component.inject(this); + + c0 = DaggerBriarIntegrationTestComponent.builder() + .testDatabaseConfigModule(new TestDatabaseConfigModule(t0Dir)) + .build(); + c0.injectBriarEagerSingletons(); + + c1 = DaggerBriarIntegrationTestComponent.builder() + .testDatabaseConfigModule(new TestDatabaseConfigModule(t1Dir)) + .build(); + c1.injectBriarEagerSingletons(); + + c2 = DaggerBriarIntegrationTestComponent.builder() + .testDatabaseConfigModule(new TestDatabaseConfigModule(t2Dir)) + .build(); + c2.injectBriarEagerSingletons(); + } + + @Test + public void testSimpleConversation() throws Exception { + // conversation start out empty + Collection messages0 = getMessages(c0); + Collection messages1 = getMessages(c1); + assertEquals(0, messages0.size()); + assertEquals(0, messages1.size()); + + // message is sent/displayed properly + String text = getRandomString(42); + sendMessage(c0, c1, text); + messages0 = getMessages(c0); + messages1 = getMessages(c1); + assertEquals(1, messages0.size()); + assertEquals(1, messages1.size()); + PrivateMessageHeader m0 = + (PrivateMessageHeader) messages0.iterator().next(); + PrivateMessageHeader m1 = + (PrivateMessageHeader) messages1.iterator().next(); + assertTrue(m0.hasText()); + assertTrue(m1.hasText()); + assertTrue(m0.isRead()); + assertFalse(m1.isRead()); + assertGroupCounts(c0, 1, 0); + assertGroupCounts(c1, 1, 1); + + // same for reply + String text2 = getRandomString(42); + sendMessage(c1, c0, text2); + messages0 = getMessages(c0); + messages1 = getMessages(c1); + assertEquals(2, messages0.size()); + assertEquals(2, messages1.size()); + assertGroupCounts(c0, 2, 1); + assertGroupCounts(c1, 2, 1); + } + + @Test + public void testAttachments() throws Exception { + // send message with attachment + AttachmentHeader h = addAttachment(c0); + sendMessage(c0, c1, null, singletonList(h)); + + // message with attachment is sent/displayed properly + Collection messages0 = getMessages(c0); + Collection messages1 = getMessages(c1); + assertEquals(1, messages0.size()); + assertEquals(1, messages1.size()); + PrivateMessageHeader m0 = + (PrivateMessageHeader) messages0.iterator().next(); + PrivateMessageHeader m1 = + (PrivateMessageHeader) messages1.iterator().next(); + assertFalse(m0.hasText()); + assertFalse(m1.hasText()); + assertEquals(1, m0.getAttachmentHeaders().size()); + assertEquals(1, m1.getAttachmentHeaders().size()); + assertGroupCounts(c0, 1, 0); + assertGroupCounts(c1, 1, 1); + } + + @Test + public void testDeleteAll() throws Exception { + // send 3 message (1 with attachment) + sendMessage(c0, c1, getRandomString(42)); + sendMessage(c0, c1, getRandomString(23)); + sendMessage(c0, c1, null, singletonList(addAttachment(c0))); + assertEquals(3, getMessages(c0).size()); + assertEquals(3, getMessages(c1).size()); + assertGroupCounts(c0, 3, 0); + assertGroupCounts(c1, 3, 3); + + // delete all messages on both sides (deletes all, because returns true) + assertTrue(db0.transactionWithResult(false, + txn -> messagingManager0.deleteAllMessages(txn, contactId))); + assertTrue(db1.transactionWithResult(false, + txn -> messagingManager1.deleteAllMessages(txn, contactId))); + + // all messages are gone + assertEquals(0, getMessages(c0).size()); + assertEquals(0, getMessages(c1).size()); + assertGroupCounts(c0, 0, 0); + assertGroupCounts(c1, 0, 0); + } + + @Test + public void testDeleteSubset() throws Exception { + // send 3 message (1 with attachment) + PrivateMessage m0 = sendMessage(c0, c1, getRandomString(42)); + PrivateMessage m1 = sendMessage(c0, c1, getRandomString(23)); + PrivateMessage m2 = + sendMessage(c0, c1, null, singletonList(addAttachment(c0))); + assertGroupCounts(c0, 3, 0); + assertGroupCounts(c1, 3, 3); + + // delete 2 messages on both sides (deletes all, because returns true) + Set toDelete = new HashSet<>(); + toDelete.add(m1.getMessage().getId()); + toDelete.add(m2.getMessage().getId()); + assertTrue(db0.transactionWithResult(false, txn -> + messagingManager0.deleteMessages(txn, contactId, toDelete))); + assertTrue(db1.transactionWithResult(false, txn -> + messagingManager1.deleteMessages(txn, contactId, toDelete))); + + // all messages except 1 are gone + assertEquals(1, getMessages(c0).size()); + assertEquals(1, getMessages(c1).size()); + assertEquals(m0.getMessage().getId(), + getMessages(c0).iterator().next().getId()); + assertEquals(m0.getMessage().getId(), + getMessages(c1).iterator().next().getId()); + assertGroupCounts(c0, 1, 0); + assertGroupCounts(c1, 1, 1); + + // remove also last message + toDelete.clear(); + toDelete.add(m0.getMessage().getId()); + assertTrue(db0.transactionWithResult(false, txn -> + messagingManager0.deleteMessages(txn, contactId, toDelete))); + assertEquals(0, getMessages(c0).size()); + assertGroupCounts(c0, 0, 0); + } + + @Test + public void testDeleteAttachment() throws Exception { + // send one message with attachment + AttachmentHeader h = addAttachment(c0); + sendMessage(c0, c1, getRandomString(42), singletonList(h)); + + // attachment exists on both devices + db0.transaction(true, txn -> db0.getMessage(txn, h.getMessageId())); + db1.transaction(true, txn -> db1.getMessage(txn, h.getMessageId())); + + // delete message on both sides (deletes all, because returns true) + assertTrue(db0.transactionWithResult(false, + txn -> messagingManager0.deleteAllMessages(txn, contactId))); + assertTrue(db1.transactionWithResult(false, + txn -> messagingManager1.deleteAllMessages(txn, contactId))); + + // attachment was deleted on both devices + try { + db0.transaction(true, txn -> db0.getMessage(txn, h.getMessageId())); + fail(); + } catch (MessageDeletedException e) { + // expected + } + try { + db1.transaction(true, txn -> db1.getMessage(txn, h.getMessageId())); + fail(); + } catch (MessageDeletedException e) { + // expected + } + } + + @Test + public void testDeletingEmptySet() throws Exception { + assertTrue(db0.transactionWithResult(false, txn -> + messagingManager0.deleteMessages(txn, contactId, emptySet()))); + } + + private PrivateMessage sendMessage(BriarIntegrationTestComponent from, + BriarIntegrationTestComponent to, String text) + throws Exception { + return sendMessage(from, to, text, emptyList()); + } + + private PrivateMessage sendMessage(BriarIntegrationTestComponent from, + BriarIntegrationTestComponent to, @Nullable String text, + List attachments) throws Exception { + GroupId g = from.getMessagingManager().getConversationId(contactId); + PrivateMessage m = messageFactory.createPrivateMessage(g, + clock.currentTimeMillis(), text, attachments); + from.getMessagingManager().addLocalMessage(m); + syncMessage(from, to, contactId, 1 + attachments.size(), true); + return m; + } + + private AttachmentHeader addAttachment(BriarIntegrationTestComponent c) + throws Exception { + GroupId g = c.getMessagingManager().getConversationId(contactId); + InputStream stream = new ByteArrayInputStream(getRandomBytes(42)); + return c.getMessagingManager().addLocalAttachment(g, + clock.currentTimeMillis(), "image/jpeg", stream); + } + + private Collection getMessages( + BriarIntegrationTestComponent c) + throws Exception { + Collection messages = + c.getDatabaseComponent().transactionWithResult(true, + txn -> c.getMessagingManager() + .getMessageHeaders(txn, contactId)); + Set ids = + c.getDatabaseComponent().transactionWithResult(true, + txn -> + c.getMessagingManager() + .getMessageIds(txn, contactId)); + assertEquals(messages.size(), ids.size()); + for (ConversationMessageHeader h : messages) { + assertTrue(ids.contains(h.getId())); + } + return messages; + } + + private void assertGroupCounts(BriarIntegrationTestComponent c, + long msgCount, long unreadCount) throws Exception { + GroupId g = c.getMessagingManager().getConversationId(contactId); + assertGroupCount(c.getMessageTracker(), g, msgCount, unreadCount); + } + + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java index fee6261db..c900410d2 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java @@ -354,7 +354,7 @@ public abstract class BriarIntegrationTest