[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.
This commit is contained in:
Torsten Grote
2019-10-21 16:23:55 -03:00
parent 190a6bff96
commit 124e2f99b0
8 changed files with 370 additions and 1 deletions

View File

@@ -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.
* <p>
* 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<MessageId> messageIds) throws DbException;
}
}

View File

@@ -660,6 +660,12 @@ class IntroductionManagerImpl extends ConversationClientImpl
return allDeleted;
}
@Override
public boolean deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException {
return false;
}
@Override
public Set<MessageId> getMessageIds(Transaction txn, ContactId c)
throws DbException {

View File

@@ -435,4 +435,42 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
return true;
}
@Override
public boolean deleteMessages(Transaction txn, ContactId c,
Set<MessageId> 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<MessageId, BdfDictionary> 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<MessageId, BdfDictionary> 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);
}
}

View File

@@ -729,6 +729,12 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
return allDeleted;
}
@Override
public boolean deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException {
return false;
}
@Override
public Set<MessageId> getMessageIds(Transaction txn, ContactId c)
throws DbException {

View File

@@ -629,6 +629,12 @@ abstract class SharingManagerImpl<S extends Shareable>
return allDeleted;
}
@Override
public boolean deleteMessages(Transaction txn, ContactId c,
Set<MessageId> messageIds) throws DbException {
return false;
}
@Override
public Set<MessageId> getMessageIds(Transaction txn, ContactId c)
throws DbException {

View File

@@ -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<BriarIntegrationTestComponent> {
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<ConversationMessageHeader> messages0 = getMessages(c0);
Collection<ConversationMessageHeader> 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<ConversationMessageHeader> messages0 = getMessages(c0);
Collection<ConversationMessageHeader> 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<MessageId> 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<AttachmentHeader> 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<ConversationMessageHeader> getMessages(
BriarIntegrationTestComponent c)
throws Exception {
Collection<ConversationMessageHeader> messages =
c.getDatabaseComponent().transactionWithResult(true,
txn -> c.getMessagingManager()
.getMessageHeaders(txn, contactId));
Set<MessageId> 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);
}
}

View File

@@ -354,7 +354,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
syncMessage(c1, c2, contactId2From1, num, valid);
}
private void syncMessage(BriarIntegrationTestComponent fromComponent,
protected void syncMessage(BriarIntegrationTestComponent fromComponent,
BriarIntegrationTestComponent toComponent, ContactId toId, int num,
boolean valid) throws Exception {

View File

@@ -19,6 +19,8 @@ import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.briar.blog.BlogModule;
@@ -103,8 +105,12 @@ public interface BriarIntegrationTestComponent
MessageTracker getMessageTracker();
MessagingManager getMessagingManager();
PrivateGroupManager getPrivateGroupManager();
PrivateMessageFactory getPrivateMessageFactory();
TransportPropertyManager getTransportPropertyManager();
AuthorFactory getAuthorFactory();