From a727a0817e0981146eff43118dfbb53c06b7f89a Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Tue, 4 Oct 2016 13:37:29 -0300 Subject: [PATCH] Store message count, unread count and timestamp of latest message in group metadata to be able to speed up group listings. Closes #584, #586, #585 --- .../BlogSharingIntegrationTest.java | 36 ++++-- .../briarproject/BriarIntegrationTest.java | 32 +++++ .../org/briarproject/ForumManagerTest.java | 26 +++- .../IntroductionIntegrationTest.java | 22 +++- .../android/contact/ConversationActivity.java | 4 +- .../android/forum/ForumControllerImpl.java | 4 +- .../api/clients/MessageTracker.java | 44 +++++++ .../api/forum/ForumConstants.java | 1 - .../briarproject/api/forum/ForumManager.java | 6 +- .../introduction/IntroductionConstants.java | 1 - .../api/introduction/IntroductionManager.java | 3 +- .../api/messaging/MessagingManager.java | 5 +- .../api/privategroup/PrivateGroupManager.java | 6 +- .../api/sharing/SharingConstants.java | 1 - .../api/sharing/SharingManager.java | 3 +- .../briarproject/blogs/BlogManagerImpl.java | 4 +- .../briarproject/clients/BdfConstants.java | 11 ++ .../clients/BdfIncomingMessageHook.java | 115 ++++++++++++++++- .../briarproject/forum/ForumManagerImpl.java | 30 ++--- .../introduction/IntroductionManagerImpl.java | 18 ++- .../messaging/MessagingManagerImpl.java | 27 ++-- .../messaging/PrivateMessageValidator.java | 3 +- .../privategroup/PrivateGroupManagerImpl.java | 26 ++-- .../sharing/SharingManagerImpl.java | 18 ++- .../IntroductionManagerImplTest.java | 116 +++++++++++++----- 25 files changed, 421 insertions(+), 141 deletions(-) create mode 100644 briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java create mode 100644 briar-api/src/org/briarproject/api/clients/MessageTracker.java create mode 100644 briar-core/src/org/briarproject/clients/BdfConstants.java diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java index 70afe99efd..bca45b25f8 100644 --- a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java @@ -8,6 +8,7 @@ import org.briarproject.api.blogs.BlogInvitationResponse; import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogPostFactory; import org.briarproject.api.blogs.BlogSharingManager; +import org.briarproject.api.clients.ContactGroupFactory; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; @@ -25,6 +26,7 @@ import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.sharing.InvitationMessage; +import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.SyncSession; import org.briarproject.api.sync.SyncSessionFactory; import org.briarproject.api.sync.ValidationManager.State; @@ -62,12 +64,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class BlogSharingIntegrationTest extends BriarTestCase { +public class BlogSharingIntegrationTest extends BriarIntegrationTest { private LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2; - private SyncSessionFactory sync0, sync1, sync2; - private BlogManager blogManager0, blogManager1, blogManager2; + private SyncSessionFactory sync0, sync1; + private BlogManager blogManager0, blogManager1; private ContactManager contactManager0, contactManager1, contactManager2; private Contact contact1, contact2, contact01, contact02; private ContactId contactId1, contactId2, contactId01, contactId02; @@ -83,6 +85,8 @@ public class BlogSharingIntegrationTest extends BriarTestCase { @Inject AuthorFactory authorFactory; @Inject + ContactGroupFactory contactGroupFactory; + @Inject BlogPostFactory blogPostFactory; @Inject CryptoComponent cryptoComponent; @@ -138,13 +142,11 @@ public class BlogSharingIntegrationTest extends BriarTestCase { contactManager2 = t2.getContactManager(); blogManager0 = t0.getBlogManager(); blogManager1 = t1.getBlogManager(); - blogManager2 = t2.getBlogManager(); blogSharingManager0 = t0.getBlogSharingManager(); blogSharingManager1 = t1.getBlogSharingManager(); blogSharingManager2 = t2.getBlogSharingManager(); sync0 = t0.getSyncSessionFactory(); sync1 = t1.getSyncSessionFactory(); - sync2 = t2.getSyncSessionFactory(); // initialize waiters fresh for each test eventWaiter = new Waiter(); @@ -187,15 +189,23 @@ public class BlogSharingIntegrationTest extends BriarTestCase { // invitee has own blog and that of the sharer assertEquals(2, blogManager1.getBlogs().size()); + // get sharing group and assert group message count + GroupId g = contactGroupFactory + .createContactGroup(blogSharingManager0.getClientId(), + contact1).getId(); + assertGroupCount(blogSharingManager0, g, 1, 0); + // sync first request message sync0To1(); eventWaiter.await(TIMEOUT, 1); assertTrue(listener1.requestReceived); + assertGroupCount(blogSharingManager1, g, 2, 1); // sync response back sync1To0(); eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.responseReceived); + assertGroupCount(blogSharingManager0, g, 2, 1); // blog was added successfully assertEquals(0, blogSharingManager0.getInvitations().size()); @@ -232,6 +242,10 @@ public class BlogSharingIntegrationTest extends BriarTestCase { assertFalse(blogSharingManager0.canBeShared(blog2.getId(), contact1)); assertFalse(blogSharingManager1.canBeShared(blog2.getId(), contact01)); + // group message count is still correct + assertGroupCount(blogSharingManager0, g, 2, 1); + assertGroupCount(blogSharingManager1, g, 2, 1); + stopLifecycles(); } @@ -510,8 +524,7 @@ public class BlogSharingIntegrationTest extends BriarTestCase { private class SharerListener implements EventListener { - volatile boolean requestReceived = false; - volatile boolean responseReceived = false; + private volatile boolean responseReceived = false; @Override public void eventOccurred(Event e) { @@ -534,7 +547,6 @@ public class BlogSharingIntegrationTest extends BriarTestCase { BlogInvitationReceivedEvent event = (BlogInvitationReceivedEvent) e; eventWaiter.assertEquals(contactId1, event.getContactId()); - requestReceived = true; Blog b = event.getBlog(); try { Contact c = contactManager0.getContact(contactId1); @@ -550,17 +562,16 @@ public class BlogSharingIntegrationTest extends BriarTestCase { private class InviteeListener implements EventListener { - volatile boolean requestReceived = false; - volatile boolean responseReceived = false; + private volatile boolean requestReceived = false; private final boolean accept, answer; - InviteeListener(boolean accept, boolean answer) { + private InviteeListener(boolean accept, boolean answer) { this.accept = accept; this.answer = answer; } - InviteeListener(boolean accept) { + private InviteeListener(boolean accept) { this(accept, true); } @@ -596,7 +607,6 @@ public class BlogSharingIntegrationTest extends BriarTestCase { BlogInvitationResponseReceivedEvent event = (BlogInvitationResponseReceivedEvent) e; eventWaiter.assertEquals(contactId01, event.getContactId()); - responseReceived = true; eventWaiter.resume(); } } diff --git a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java new file mode 100644 index 0000000000..f1b2a09143 --- /dev/null +++ b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java @@ -0,0 +1,32 @@ +package org.briarproject; + +import org.briarproject.api.clients.MessageTracker; +import org.briarproject.api.clients.MessageTracker.GroupCount; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sync.GroupId; + +import static org.junit.Assert.assertEquals; + +public abstract class BriarIntegrationTest extends BriarTestCase { + + // TODO maybe we could add uncaught exception handlers for other threads here (#670) + + protected void assertGroupCount(MessageTracker tracker, GroupId g, + long msgCount, long unreadCount, long latestMsg) + throws DbException { + + GroupCount groupCount = tracker.getGroupCount(g); + assertEquals(msgCount, groupCount.getMsgCount()); + assertEquals(unreadCount, groupCount.getUnreadCount()); + assertEquals(latestMsg, groupCount.getLatestMsgTime()); + } + + protected void assertGroupCount(MessageTracker tracker, GroupId g, + long msgCount, long unreadCount) throws DbException { + + GroupCount c1 = tracker.getGroupCount(g); + assertEquals(msgCount, c1.getMsgCount()); + assertEquals(unreadCount, c1.getUnreadCount()); + } + +} diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java index 0fa8e80c1e..64ad251f07 100644 --- a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java @@ -59,7 +59,7 @@ import static org.briarproject.api.sync.ValidationManager.State.INVALID; import static org.briarproject.api.sync.ValidationManager.State.PENDING; import static org.junit.Assert.assertTrue; -public class ForumManagerTest { +public class ForumManagerTest extends BriarIntegrationTest { private LifecycleManager lifecycleManager0, lifecycleManager1; private SyncSessionFactory sync0, sync1; @@ -150,9 +150,19 @@ public class ForumManagerTest { createForumPost(forum.getGroup().getId(), post1, body2, ms2); assertEquals(ms2, post2.getMessage().getTimestamp()); forumManager0.addLocalPost(post1); - forumManager0.setReadFlag(post1.getMessage().getId(), true); + forumManager0.setReadFlag(forum.getGroup().getId(), + post1.getMessage().getId(), true); + assertGroupCount(forumManager0, forum.getGroup().getId(), 1, 0, + post1.getMessage().getTimestamp()); forumManager0.addLocalPost(post2); - forumManager0.setReadFlag(post2.getMessage().getId(), false); + forumManager0.setReadFlag(forum.getGroup().getId(), + post2.getMessage().getId(), false); + assertGroupCount(forumManager0, forum.getGroup().getId(), 2, 1, + post2.getMessage().getTimestamp()); + forumManager0.setReadFlag(forum.getGroup().getId(), + post2.getMessage().getId(), false); + assertGroupCount(forumManager0, forum.getGroup().getId(), 2, 1, + post2.getMessage().getTimestamp()); Collection<ForumPostHeader> headers = forumManager0.getPostHeaders(forum.getGroup().getId()); assertEquals(2, headers.size()); @@ -202,23 +212,29 @@ public class ForumManagerTest { forumManager0.addLocalPost(post1); assertEquals(1, forumManager0.getPostHeaders(g).size()); assertEquals(0, forumManager1.getPostHeaders(g).size()); + assertGroupCount(forumManager0, g, 1, 0, time); + assertGroupCount(forumManager1, g, 0, 0, 0); // send post to 1 sync0To1(); deliveryWaiter.await(TIMEOUT, 1); assertEquals(1, forumManager1.getPostHeaders(g).size()); + assertGroupCount(forumManager1, g, 1, 1, time); // add another forum post - time = clock.currentTimeMillis(); - ForumPost post2 = createForumPost(g, null, "b", time); + long time2 = clock.currentTimeMillis(); + ForumPost post2 = createForumPost(g, null, "b", time2); forumManager1.addLocalPost(post2); assertEquals(1, forumManager0.getPostHeaders(g).size()); assertEquals(2, forumManager1.getPostHeaders(g).size()); + assertGroupCount(forumManager0, g, 1, 0, time); + assertGroupCount(forumManager1, g, 2, 1, time2); // send post to 0 sync1To0(); deliveryWaiter.await(TIMEOUT, 1); assertEquals(2, forumManager1.getPostHeaders(g).size()); + assertGroupCount(forumManager0, g, 2, 1, time2); stopLifecycles(); } diff --git a/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java index 364040e7ce..e9c4487a4e 100644 --- a/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java @@ -4,7 +4,7 @@ import android.support.annotation.Nullable; import net.jodah.concurrentunit.Waiter; -import org.briarproject.BriarTestCase; +import org.briarproject.BriarIntegrationTest; import org.briarproject.TestDatabaseModule; import org.briarproject.TestUtils; import org.briarproject.api.FormatException; @@ -99,7 +99,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -public class IntroductionIntegrationTest extends BriarTestCase { +public class IntroductionIntegrationTest extends BriarIntegrationTest { private LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2; @@ -200,29 +200,43 @@ public class IntroductionIntegrationTest extends BriarTestCase { introductionManager0 .makeIntroduction(introducee1, introducee2, "Hi!", time); + // check that messages are tracked properly + Group g1 = introductionGroupFactory + .createIntroductionGroup(introducee1); + Group g2 = introductionGroupFactory + .createIntroductionGroup(introducee2); + assertGroupCount(introductionManager0, g1.getId(), 1, 0, time); + assertGroupCount(introductionManager0, g2.getId(), 1, 0, time); + // sync first request message deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1"); eventWaiter.await(TIMEOUT, 1); assertTrue(listener1.requestReceived); + assertGroupCount(introductionManager1, g1.getId(), 2, 1); // sync second request message deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2"); eventWaiter.await(TIMEOUT, 1); assertTrue(listener2.requestReceived); + assertGroupCount(introductionManager2, g2.getId(), 2, 1); // sync first response deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0"); eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.response1Received); + assertGroupCount(introductionManager0, g1.getId(), 2, 1); // sync second response deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0"); eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.response2Received); + assertGroupCount(introductionManager0, g2.getId(), 2, 1); // sync forwarded responses to introducees deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1"); deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2"); + assertGroupCount(introductionManager1, g1.getId(), 3, 2); + assertGroupCount(introductionManager2, g2.getId(), 3, 2); // sync first ACK and its forward deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0"); @@ -255,6 +269,10 @@ public class IntroductionIntegrationTest extends BriarTestCase { } assertDefaultUiMessages(); + assertGroupCount(introductionManager0, g1.getId(), 2, 1); + assertGroupCount(introductionManager0, g2.getId(), 2, 1); + assertGroupCount(introductionManager1, g1.getId(), 3, 2); + assertGroupCount(introductionManager2, g2.getId(), 3, 2); } finally { stopLifecycles(); } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index 7ec442b5b1..722e6b8588 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -499,7 +499,7 @@ public class ConversationActivity extends BriarActivity for (MessageId m : unread) // not really clean, but the messaging manager can // handle introduction messages as well - messagingManager.setReadFlag(m, true); + messagingManager.setReadFlag(groupId, m, true); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Marking read took " + duration + " ms"); @@ -614,7 +614,7 @@ public class ConversationActivity extends BriarActivity @Override public void run() { try { - messagingManager.setReadFlag(m, true); + messagingManager.setReadFlag(groupId, m, true); loadMessages(); } catch (DbException e) { if (LOG.isLoggable(WARNING)) diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java index bf147491b1..98f7ac139e 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java +++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java @@ -291,13 +291,15 @@ public class ForumControllerImpl extends DbControllerImpl @Override public void entriesRead(final Collection<ForumEntry> forumEntries) { + if (forum == null) return; runOnDbThread(new Runnable() { @Override public void run() { try { long now = System.currentTimeMillis(); for (ForumEntry fe : forumEntries) { - forumManager.setReadFlag(fe.getId(), true); + forumManager + .setReadFlag(forum.getId(), fe.getId(), true); } long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) diff --git a/briar-api/src/org/briarproject/api/clients/MessageTracker.java b/briar-api/src/org/briarproject/api/clients/MessageTracker.java new file mode 100644 index 0000000000..8226a3058c --- /dev/null +++ b/briar-api/src/org/briarproject/api/clients/MessageTracker.java @@ -0,0 +1,44 @@ +package org.briarproject.api.clients; + +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; + +public interface MessageTracker { + + /** + * Gets the number of visible and unread messages in the group + * as well as the timestamp of the latest message + **/ + GroupCount getGroupCount(GroupId g) throws DbException; + + /** + * Marks a message as read or unread and updates the group counts in g. + **/ + void setReadFlag(GroupId g, MessageId m, boolean read) throws DbException; + + class GroupCount { + private final long msgCount, unreadCount, latestMsgTime; + + public GroupCount(long msgCount, long unreadCount, long latestMsgTime) { + this.msgCount = msgCount; + this.unreadCount = unreadCount; + this.latestMsgTime = latestMsgTime; + } + + public long getMsgCount() { + return msgCount; + } + + public long getUnreadCount() { + return unreadCount; + } + + public long getLatestMsgTime() { + return latestMsgTime; + } + } + +} diff --git a/briar-api/src/org/briarproject/api/forum/ForumConstants.java b/briar-api/src/org/briarproject/api/forum/ForumConstants.java index 97557d5fe4..048f6c3c71 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumConstants.java +++ b/briar-api/src/org/briarproject/api/forum/ForumConstants.java @@ -28,6 +28,5 @@ public interface ForumConstants { String KEY_PUBLIC_NAME = "publicKey"; String KEY_AUTHOR = "author"; String KEY_LOCAL = "local"; - String KEY_READ = "read"; } diff --git a/briar-api/src/org/briarproject/api/forum/ForumManager.java b/briar-api/src/org/briarproject/api/forum/ForumManager.java index 8a1c0e7c4e..8c35d05e36 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumManager.java +++ b/briar-api/src/org/briarproject/api/forum/ForumManager.java @@ -1,5 +1,6 @@ package org.briarproject.api.forum; +import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.sync.ClientId; @@ -8,7 +9,7 @@ import org.briarproject.api.sync.MessageId; import java.util.Collection; -public interface ForumManager { +public interface ForumManager extends MessageTracker { /** Returns the unique ID of the forum client. */ ClientId getClientId(); @@ -37,9 +38,6 @@ public interface ForumManager { /** Returns the headers of all posts in the given forum. */ Collection<ForumPostHeader> getPostHeaders(GroupId g) throws DbException; - /** Marks a forum post as read or unread. */ - void setReadFlag(MessageId m, boolean read) throws DbException; - /** Registers a hook to be called whenever a forum is removed. */ void registerRemoveForumHook(RemoveForumHook hook); diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java index ac8975e5c2..de74b390ff 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java @@ -45,7 +45,6 @@ public interface IntroductionConstants { String CONTACT_ID_2 = "contactId2"; String RESPONSE_1 = "response1"; String RESPONSE_2 = "response2"; - String READ = "read"; /* Introduction Request Action */ String PUBLIC_KEY1 = "publicKey1"; diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java index 9819c87580..3f2fcea12f 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java @@ -1,6 +1,7 @@ package org.briarproject.api.introduction; import org.briarproject.api.FormatException; +import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; @@ -9,7 +10,7 @@ import org.briarproject.api.sync.ClientId; import java.util.Collection; -public interface IntroductionManager { +public interface IntroductionManager extends MessageTracker { /** Returns the unique ID of the introduction client. */ ClientId getClientId(); diff --git a/briar-api/src/org/briarproject/api/messaging/MessagingManager.java b/briar-api/src/org/briarproject/api/messaging/MessagingManager.java index f22a5131af..627f5ceb6d 100644 --- a/briar-api/src/org/briarproject/api/messaging/MessagingManager.java +++ b/briar-api/src/org/briarproject/api/messaging/MessagingManager.java @@ -1,5 +1,6 @@ package org.briarproject.api.messaging; +import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DbException; import org.briarproject.api.sync.ClientId; @@ -8,7 +9,7 @@ import org.briarproject.api.sync.MessageId; import java.util.Collection; -public interface MessagingManager { +public interface MessagingManager extends MessageTracker { /** Returns the unique ID of the messaging client. */ ClientId getClientId(); @@ -31,6 +32,4 @@ public interface MessagingManager { /** Returns the body of the private message with the given ID. */ byte[] getMessageBody(MessageId m) throws DbException; - /** Marks a private message as read or unread. */ - void setReadFlag(MessageId m, boolean read) throws DbException; } diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java index 3f2a637624..ab02e86bc0 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java @@ -1,5 +1,6 @@ package org.briarproject.api.privategroup; +import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.sync.ClientId; @@ -9,7 +10,7 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; -public interface PrivateGroupManager { +public interface PrivateGroupManager extends MessageTracker { /** Returns the unique ID of the private group client. */ @NotNull @@ -40,7 +41,4 @@ public interface PrivateGroupManager { @NotNull Collection<GroupMessageHeader> getHeaders(GroupId g) throws DbException; - /** Marks a group message as read or unread. */ - void setReadFlag(MessageId m, boolean read) throws DbException; - } diff --git a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java index 43d689729b..e09b9015c5 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java @@ -16,7 +16,6 @@ public interface SharingConstants { String STATE = "state"; String LOCAL = "local"; String TIME = "time"; - String READ = "read"; String IS_SHARER = "isSharer"; String SHAREABLE_ID = "shareableId"; String INVITATION_MSG = "invitationMsg"; diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java index 0a66a54588..c633256f12 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingManager.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java @@ -1,5 +1,6 @@ package org.briarproject.api.sharing; +import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DbException; @@ -8,7 +9,7 @@ import org.briarproject.api.sync.GroupId; import java.util.Collection; -public interface SharingManager<S extends Shareable> { +public interface SharingManager<S extends Shareable> extends MessageTracker { /** Returns the unique ID of the group sharing client. */ ClientId getClientId(); diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java index 094917991b..d31023c447 100644 --- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java @@ -86,7 +86,6 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, "dafbe56f0c8971365cea4bb5f08ec9a6" + "1d686e058b943997b6ff259ba423f613")); - private final DatabaseComponent db; private final IdentityManager identityManager; private final ContactManager contactManager; private final BlogFactory blogFactory; @@ -98,9 +97,8 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, ClientHelper clientHelper, MetadataParser metadataParser, ContactManager contactManager, BlogFactory blogFactory, BlogPostFactory blogPostFactory) { - super(clientHelper, metadataParser); + super(db, clientHelper, metadataParser); - this.db = db; this.identityManager = identityManager; this.contactManager = contactManager; this.blogFactory = blogFactory; diff --git a/briar-core/src/org/briarproject/clients/BdfConstants.java b/briar-core/src/org/briarproject/clients/BdfConstants.java new file mode 100644 index 0000000000..6f81491854 --- /dev/null +++ b/briar-core/src/org/briarproject/clients/BdfConstants.java @@ -0,0 +1,11 @@ +package org.briarproject.clients; + +public interface BdfConstants { + + String GROUP_KEY_MSG_COUNT = "messageCount"; + String GROUP_KEY_UNREAD_COUNT = "unreadCount"; + String GROUP_KEY_LATEST_MSG = "latestMessageTime"; + + String MSG_KEY_READ = "read"; + +} diff --git a/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java b/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java index 3ca19d9bd5..c81293ab04 100644 --- a/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java +++ b/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java @@ -3,27 +3,38 @@ package org.briarproject.clients; import org.briarproject.api.FormatException; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.MessageQueueManager.IncomingQueueMessageHook; +import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.clients.QueueMessage; import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.MetadataParser; +import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Transaction; +import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; import static org.briarproject.api.clients.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; +import static org.briarproject.clients.BdfConstants.GROUP_KEY_LATEST_MSG; +import static org.briarproject.clients.BdfConstants.GROUP_KEY_MSG_COUNT; +import static org.briarproject.clients.BdfConstants.GROUP_KEY_UNREAD_COUNT; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; public abstract class BdfIncomingMessageHook implements IncomingMessageHook, - IncomingQueueMessageHook { + IncomingQueueMessageHook, MessageTracker { + protected final DatabaseComponent db; protected final ClientHelper clientHelper; protected final MetadataParser metadataParser; - protected BdfIncomingMessageHook(ClientHelper clientHelper, - MetadataParser metadataParser) { + protected BdfIncomingMessageHook(DatabaseComponent db, + ClientHelper clientHelper, MetadataParser metadataParser) { + this.db = db; this.clientHelper = clientHelper; this.metadataParser = metadataParser; } @@ -56,4 +67,102 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook, throw new DbException(e); } } + + protected void trackIncomingMessage(Transaction txn, Message m) + throws DbException { + trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); + } + + protected void trackOutgoingMessage(Transaction txn, Message m) + throws DbException { + trackMessage(txn, m.getGroupId(), m.getTimestamp(), true); + } + + protected void trackMessage(Transaction txn, GroupId g, long time, + boolean read) throws DbException { + GroupCount c = getGroupCount(txn, g); + long msgCount = c.getMsgCount() + 1; + long unreadCount = c.getUnreadCount() + (read ? 0 : 1); + long latestTime = + time > c.getLatestMsgTime() ? time : c.getLatestMsgTime(); + storeGroupCount(txn, g, + new GroupCount(msgCount, unreadCount, latestTime)); + } + + @Override + public GroupCount getGroupCount(GroupId g) throws DbException { + GroupCount count; + Transaction txn = db.startTransaction(true); + try { + count = getGroupCount(txn, g); + txn.setComplete(); + } + finally { + db.endTransaction(txn); + } + return count; + } + + private GroupCount getGroupCount(Transaction txn, GroupId g) + throws DbException { + GroupCount count; + try { + BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(txn, g); + count = new GroupCount( + d.getLong(GROUP_KEY_MSG_COUNT, 0L), + d.getLong(GROUP_KEY_UNREAD_COUNT, 0L), + d.getLong(GROUP_KEY_LATEST_MSG, 0L) + ); + } catch (FormatException e) { + throw new DbException(e); + } + return count; + } + + private void storeGroupCount(Transaction txn, GroupId g, GroupCount c) + throws DbException{ + try { + BdfDictionary d = BdfDictionary.of( + new BdfEntry(GROUP_KEY_MSG_COUNT, c.getMsgCount()), + new BdfEntry(GROUP_KEY_UNREAD_COUNT, c.getUnreadCount()), + new BdfEntry(GROUP_KEY_LATEST_MSG, c.getLatestMsgTime()) + ); + clientHelper.mergeGroupMetadata(txn, g, d); + } catch (FormatException e) { + throw new DbException(e); + } + } + + @Override + public void setReadFlag(GroupId g, MessageId m, boolean read) + throws DbException { + Transaction txn = db.startTransaction(false); + try { + // check current read status of message + BdfDictionary old = + clientHelper.getMessageMetadataAsDictionary(txn, m); + boolean wasRead = old.getBoolean(MSG_KEY_READ, false); + + // if status changed + if (wasRead != read) { + // mark individual message as read + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_READ, read); + clientHelper.mergeMessageMetadata(txn, m, meta); + + // update unread counter in group metadata + GroupCount c = getGroupCount(txn, g); + BdfDictionary d = new BdfDictionary(); + d.put(GROUP_KEY_UNREAD_COUNT, + c.getUnreadCount() + (read ? -1 : 1)); + clientHelper.mergeGroupMetadata(txn, g, d); + } + txn.setComplete(); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + } + } diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java index d4697b7598..77a7712e57 100644 --- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java @@ -45,9 +45,9 @@ import static org.briarproject.api.forum.ForumConstants.KEY_LOCAL; import static org.briarproject.api.forum.ForumConstants.KEY_NAME; import static org.briarproject.api.forum.ForumConstants.KEY_PARENT; import static org.briarproject.api.forum.ForumConstants.KEY_PUBLIC_NAME; -import static org.briarproject.api.forum.ForumConstants.KEY_READ; import static org.briarproject.api.forum.ForumConstants.KEY_TIMESTAMP; import static org.briarproject.api.identity.Author.Status.ANONYMOUS; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { @@ -55,7 +55,6 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { "859a7be50dca035b64bd6902fb797097" + "795af837abbf8c16d750b3c2ccc186ea")); - private final DatabaseComponent db; private final IdentityManager identityManager; private final ForumFactory forumFactory; private final List<RemoveForumHook> removeHooks; @@ -64,9 +63,8 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { ForumManagerImpl(DatabaseComponent db, IdentityManager identityManager, ClientHelper clientHelper, MetadataParser metadataParser, ForumFactory forumFactory) { + super(db, clientHelper, metadataParser); - super(clientHelper, metadataParser); - this.db = db; this.identityManager = identityManager; this.forumFactory = forumFactory; removeHooks = new CopyOnWriteArrayList<RemoveForumHook>(); @@ -76,6 +74,8 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { protected boolean incomingMessage(Transaction txn, Message m, BdfList body, BdfDictionary meta) throws DbException, FormatException { + trackIncomingMessage(txn, m); + ForumPostHeader post = getForumPostHeader(txn, m.getId(), meta); ForumPostReceivedEvent event = new ForumPostReceivedEvent(post, m.getGroupId()); @@ -119,6 +119,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { @Override public void addLocalPost(ForumPost p) throws DbException { + Transaction txn = db.startTransaction(false); try { BdfDictionary meta = new BdfDictionary(); meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); @@ -132,10 +133,14 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { meta.put(KEY_AUTHOR, authorMeta); } meta.put(KEY_LOCAL, true); - meta.put(KEY_READ, true); - clientHelper.addLocalMessage(p.getMessage(), meta, true); + meta.put(MSG_KEY_READ, true); + clientHelper.addLocalMessage(txn, p.getMessage(), meta, true); + trackOutgoingMessage(txn, p.getMessage()); + txn.setComplete(); } catch (FormatException e) { throw new RuntimeException(e); + } finally { + db.endTransaction(txn); } } @@ -230,17 +235,6 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { } } - @Override - public void setReadFlag(MessageId m, boolean read) throws DbException { - try { - BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_READ, read); - clientHelper.mergeMessageMetadata(m, meta); - } catch (FormatException e) { - throw new RuntimeException(e); - } - } - @Override public void registerRemoveForumHook(RemoveForumHook hook) { removeHooks.add(hook); @@ -281,7 +275,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { status = identityManager.getAuthorStatus(txn, author.getId()); } } - boolean read = meta.getBoolean(KEY_READ); + boolean read = meta.getBoolean(MSG_KEY_READ); return new ForumPostHeader(id, parentId, timestamp, author, status, read); diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java index b07108ed84..6c44e83e6b 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java @@ -60,7 +60,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TI import static org.briarproject.api.introduction.IntroductionConstants.MSG; import static org.briarproject.api.introduction.IntroductionConstants.NAME; import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; -import static org.briarproject.api.introduction.IntroductionConstants.READ; import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID; import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_1; @@ -75,6 +74,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; class IntroductionManagerImpl extends BdfIncomingMessageHook implements IntroductionManager, Client, AddContactHook, @@ -87,7 +87,6 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook private static final Logger LOG = Logger.getLogger(IntroductionManagerImpl.class.getName()); - private final DatabaseComponent db; private final IntroducerManager introducerManager; private final IntroduceeManager introduceeManager; private final IntroductionGroupFactory introductionGroupFactory; @@ -98,8 +97,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook IntroduceeManager introduceeManager, IntroductionGroupFactory introductionGroupFactory) { - super(clientHelper, metadataParser); - this.db = db; + super(db, clientHelper, metadataParser); this.introducerManager = introducerManager; this.introduceeManager = introduceeManager; this.introductionGroupFactory = introductionGroupFactory; @@ -208,7 +206,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook */ @Override protected boolean incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary message) throws DbException { + BdfDictionary message) throws DbException { // Get message data and type GroupId groupId = m.getGroupId(); @@ -237,6 +235,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook } try { introduceeManager.incomingMessage(txn, state, message); + trackIncomingMessage(txn, m); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); introduceeManager.abort(txn, state); @@ -270,6 +269,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook deleteMessage(txn, m.getId()); } } + if (type == TYPE_RESPONSE) trackIncomingMessage(txn, m); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (role == ROLE_INTRODUCER) introducerManager.abort(txn, state); @@ -296,6 +296,10 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook Transaction txn = db.startTransaction(false); try { introducerManager.makeIntroduction(txn, c1, c2, msg, timestamp); + Group g1 = introductionGroupFactory.createIntroductionGroup(c1); + Group g2 = introductionGroupFactory.createIntroductionGroup(c2); + trackMessage(txn, g1.getId(), timestamp, true); + trackMessage(txn, g2.getId(), timestamp, true); txn.setComplete(); } finally { db.endTransaction(txn); @@ -315,6 +319,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook getSessionState(txn, g.getId(), sessionId.getBytes()); introduceeManager.acceptIntroduction(txn, state, timestamp); + trackMessage(txn, g.getId(), timestamp, true); txn.setComplete(); } finally { db.endTransaction(txn); @@ -334,6 +339,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook getSessionState(txn, g.getId(), sessionId.getBytes()); introduceeManager.declineIntroduction(txn, state, timestamp); + trackMessage(txn, g.getId(), timestamp, true); txn.setComplete(); } finally { db.endTransaction(txn); @@ -377,7 +383,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook boolean local; long time = msg.getLong(MESSAGE_TIME); boolean accepted = msg.getBoolean(ACCEPT, false); - boolean read = msg.getBoolean(READ, false); + boolean read = msg.getBoolean(MSG_KEY_READ, false); AuthorId authorId; String name; if (type == TYPE_RESPONSE) { diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java index d2af3d8a1d..d5bc6db1d3 100644 --- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java +++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java @@ -33,6 +33,8 @@ import java.util.Map; import javax.inject.Inject; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; + class MessagingManagerImpl extends BdfIncomingMessageHook implements MessagingManager, Client, AddContactHook, RemoveContactHook { @@ -40,16 +42,13 @@ class MessagingManagerImpl extends BdfIncomingMessageHook "6bcdc006c0910b0f44e40644c3b31f1a" + "8bf9a6d6021d40d219c86b731b903070")); - private final DatabaseComponent db; private final ContactGroupFactory contactGroupFactory; @Inject MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper, MetadataParser metadataParser, ContactGroupFactory contactGroupFactory) { - super(clientHelper, metadataParser); - - this.db = db; + super(db, clientHelper, metadataParser); this.contactGroupFactory = contactGroupFactory; } @@ -100,12 +99,13 @@ class MessagingManagerImpl extends BdfIncomingMessageHook long timestamp = meta.getLong("timestamp"); String contentType = meta.getString("contentType"); boolean local = meta.getBoolean("local"); - boolean read = meta.getBoolean("read"); + boolean read = meta.getBoolean(MSG_KEY_READ); PrivateMessageHeader header = new PrivateMessageHeader( m.getId(), timestamp, contentType, local, read, false, false); PrivateMessageReceivedEvent event = new PrivateMessageReceivedEvent( header, groupId); txn.attach(event); + trackIncomingMessage(txn, m); // don't share message return false; @@ -113,6 +113,7 @@ class MessagingManagerImpl extends BdfIncomingMessageHook @Override public void addLocalMessage(PrivateMessage m) throws DbException { + Transaction txn = db.startTransaction(false); try { BdfDictionary meta = new BdfDictionary(); meta.put("timestamp", m.getMessage().getTimestamp()); @@ -120,9 +121,13 @@ class MessagingManagerImpl extends BdfIncomingMessageHook meta.put("contentType", m.getContentType()); meta.put("local", true); meta.put("read", true); - clientHelper.addLocalMessage(m.getMessage(), meta, true); + clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); + trackOutgoingMessage(txn, m.getMessage()); + txn.setComplete(); } catch (FormatException e) { throw new RuntimeException(e); + } finally { + db.endTransaction(txn); } } @@ -196,14 +201,4 @@ class MessagingManagerImpl extends BdfIncomingMessageHook } } - @Override - public void setReadFlag(MessageId m, boolean read) throws DbException { - try { - BdfDictionary meta = new BdfDictionary(); - meta.put("read", read); - clientHelper.mergeMessageMetadata(m, meta); - } catch (FormatException e) { - throw new RuntimeException(e); - } - } } diff --git a/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java b/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java index 74bd0d904e..fec178b607 100644 --- a/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java +++ b/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java @@ -14,6 +14,7 @@ import org.briarproject.clients.BdfMessageValidator; import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH; import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; class PrivateMessageValidator extends BdfMessageValidator { @@ -42,7 +43,7 @@ class PrivateMessageValidator extends BdfMessageValidator { if (parentId != null) meta.put("parent", parentId); meta.put("contentType", contentType); meta.put("local", false); - meta.put("read", false); + meta.put(MSG_KEY_READ, false); return new BdfMessageContext(meta); } } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 15256467ac..93623476d9 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -28,8 +28,6 @@ import java.util.Collections; import javax.inject.Inject; -import static org.briarproject.privategroup.Constants.KEY_READ; - public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements PrivateGroupManager { @@ -37,7 +35,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements StringUtils.fromHexString("5072697661746547726f75704d616e61" + "67657220627920546f727374656e2047")); - private final DatabaseComponent db; private final IdentityManager identityManager; private final PrivateGroupFactory privateGroupFactory; @@ -46,9 +43,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements MetadataParser metadataParser, DatabaseComponent db, IdentityManager identityManager, PrivateGroupFactory privateGroupFactory) { - super(clientHelper, metadataParser); + super(db, clientHelper, metadataParser); - this.db = db; this.identityManager = identityManager; this.privateGroupFactory = privateGroupFactory; } @@ -61,11 +57,16 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements @Override public void addLocalMessage(GroupMessage m) throws DbException { + Transaction txn = db.startTransaction(false); try { BdfDictionary meta = new BdfDictionary(); - clientHelper.addLocalMessage(m.getMessage(), meta, true); + clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); + trackOutgoingMessage(txn, m.getMessage()); + txn.setComplete(); } catch (FormatException e) { throw new DbException(e); + } finally { + db.endTransaction(txn); } } @@ -104,21 +105,12 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return Collections.emptyList(); } - @Override - public void setReadFlag(MessageId m, boolean read) throws DbException { - try { - BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_READ, read); - clientHelper.mergeMessageMetadata(m, meta); - } catch (FormatException e) { - throw new RuntimeException(e); - } - } - @Override protected boolean incomingMessage(Transaction txn, Message m, BdfList body, BdfDictionary meta) throws DbException, FormatException { + trackIncomingMessage(txn, m); + return true; } diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java index db29f569e4..805b9d0b4b 100644 --- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java @@ -58,7 +58,6 @@ import static org.briarproject.api.clients.ProtocolEngine.StateUpdate; import static org.briarproject.api.sharing.SharingConstants.CONTACT_ID; import static org.briarproject.api.sharing.SharingConstants.IS_SHARER; import static org.briarproject.api.sharing.SharingConstants.LOCAL; -import static org.briarproject.api.sharing.SharingConstants.READ; import static org.briarproject.api.sharing.SharingConstants.SESSION_ID; import static org.briarproject.api.sharing.SharingConstants.SHAREABLE_ID; import static org.briarproject.api.sharing.SharingConstants.SHARED_BY_US; @@ -83,6 +82,7 @@ import static org.briarproject.api.sharing.SharingConstants.TO_BE_SHARED_BY_US; import static org.briarproject.api.sharing.SharingConstants.TYPE; import static org.briarproject.api.sharing.SharingMessage.BaseMessage; import static org.briarproject.api.sharing.SharingMessage.Invitation; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; import static org.briarproject.sharing.InviteeSessionState.State.AWAIT_LOCAL_RESPONSE; abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS extends InviteeSessionState, SS extends SharerSessionState, IR extends InvitationReceivedEvent, IRR extends InvitationResponseReceivedEvent> @@ -93,7 +93,6 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS private static final Logger LOG = Logger.getLogger(SharingManagerImpl.class.getName()); - private final DatabaseComponent db; private final MessageQueueManager messageQueueManager; private final MetadataEncoder metadataEncoder; private final SecureRandom random; @@ -106,9 +105,8 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS MetadataParser metadataParser, MetadataEncoder metadataEncoder, SecureRandom random, ContactGroupFactory contactGroupFactory, Clock clock) { + super(db, clientHelper, metadataParser); - super(clientHelper, metadataParser); - this.db = db; this.messageQueueManager = messageQueueManager; this.metadataEncoder = metadataEncoder; this.random = random; @@ -226,6 +224,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS new InviteeEngine<IS, IR>(getIRFactory()); processInviteeStateUpdate(txn, m.getId(), engine.onMessageReceived(state, msg)); + trackIncomingMessage(txn, m); } catch (FormatException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); deleteMessage(txn, m.getId()); @@ -239,6 +238,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS getIRRFactory()); processSharerStateUpdate(txn, m.getId(), engine.onMessageReceived(state, msg)); + trackIncomingMessage(txn, m); } else if (msg.getType() == SHARE_MSG_TYPE_LEAVE || msg.getType() == SHARE_MSG_TYPE_ABORT) { // we don't know who we are, so figure it out @@ -290,6 +290,10 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS engine.onLocalAction(localState, SharerSessionState.Action.LOCAL_INVITATION)); + // track message + long time = clock.currentTimeMillis(); + trackMessage(txn, localState.getGroupId(), time, true); + txn.setComplete(); } catch (FormatException e) { throw new DbException(); @@ -321,6 +325,10 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS processInviteeStateUpdate(txn, null, engine.onLocalAction(localState, localAction)); + // track message + long time = clock.currentTimeMillis(); + trackMessage(txn, localState.getGroupId(), time, true); + txn.setComplete(); } catch (FormatException e) { throw new DbException(e); @@ -352,7 +360,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS db.getMessageStatus(txn, contactId, m.getKey()); long time = d.getLong(TIME); boolean local = d.getBoolean(LOCAL); - boolean read = d.getBoolean(READ, false); + boolean read = d.getBoolean(MSG_KEY_READ, false); boolean available = false; if (type == SHARE_MSG_TYPE_INVITATION) { diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java index 23147e045b..e6caecad6b 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java @@ -4,28 +4,24 @@ import org.briarproject.BriarTestCase; import org.briarproject.TestUtils; import org.briarproject.api.FormatException; import org.briarproject.api.clients.ClientHelper; -import org.briarproject.api.clients.MessageQueueManager; -import org.briarproject.api.clients.ContactGroupFactory; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; -import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataParser; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.AuthorId; -import org.briarproject.api.clients.SessionId; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; -import org.briarproject.api.system.Clock; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.lib.legacy.ClassImposteriser; @@ -46,30 +42,29 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; +import static org.briarproject.clients.BdfConstants.GROUP_KEY_LATEST_MSG; +import static org.briarproject.clients.BdfConstants.GROUP_KEY_MSG_COUNT; +import static org.briarproject.clients.BdfConstants.GROUP_KEY_UNREAD_COUNT; import static org.junit.Assert.assertFalse; public class IntroductionManagerImplTest extends BriarTestCase { - final Mockery context; - final IntroductionManagerImpl introductionManager; - final IntroducerManager introducerManager; - final IntroduceeManager introduceeManager; - final DatabaseComponent db; - final ContactGroupFactory contactGroupFactory; - final ClientHelper clientHelper; - final MetadataEncoder metadataEncoder; - final MessageQueueManager messageQueueManager; - final IntroductionGroupFactory introductionGroupFactory; - final Clock clock; - final SessionId sessionId = new SessionId(TestUtils.getRandomId()); - final long time = 42L; - final Contact introducee1; - final Contact introducee2; - final Group localGroup0; - final Group introductionGroup1; - final Group introductionGroup2; - final Message message1; - Transaction txn; + private final Mockery context; + private final IntroductionManagerImpl introductionManager; + private final IntroducerManager introducerManager; + private final IntroduceeManager introduceeManager; + private final DatabaseComponent db; + private final ClientHelper clientHelper; + private final IntroductionGroupFactory introductionGroupFactory; + private final SessionId sessionId = new SessionId(TestUtils.getRandomId()); + private final long time = 42L; + private final Contact introducee1; + private final Contact introducee2; + private final Group introductionGroup1; + private final Group introductionGroup2; + private final Message message1; + private Transaction txn; + private BdfDictionary metadataBefore, metadataAfter; public IntroductionManagerImplTest() { AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); @@ -89,8 +84,6 @@ public class IntroductionManagerImplTest extends BriarTestCase { new Contact(contactId2, author2, localAuthorId2, true, true); ClientId clientId = new ClientId(TestUtils.getRandomId()); - localGroup0 = new Group(new GroupId(TestUtils.getRandomId()), - clientId, new byte[0]); introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()), clientId, new byte[0]); introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()), @@ -102,6 +95,14 @@ public class IntroductionManagerImplTest extends BriarTestCase { time, TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1) ); + metadataBefore = BdfDictionary.of( + new BdfEntry(GROUP_KEY_MSG_COUNT, 41L), + new BdfEntry(GROUP_KEY_LATEST_MSG, 0L) + ); + metadataAfter = BdfDictionary.of( + new BdfEntry(GROUP_KEY_MSG_COUNT, 42L), + new BdfEntry(GROUP_KEY_LATEST_MSG, time) + ); // mock ALL THE THINGS!!! context = new Mockery(); @@ -109,15 +110,9 @@ public class IntroductionManagerImplTest extends BriarTestCase { introducerManager = context.mock(IntroducerManager.class); introduceeManager = context.mock(IntroduceeManager.class); db = context.mock(DatabaseComponent.class); - contactGroupFactory = context.mock(ContactGroupFactory.class); clientHelper = context.mock(ClientHelper.class); - metadataEncoder = - context.mock(MetadataEncoder.class); - messageQueueManager = - context.mock(MessageQueueManager.class); MetadataParser metadataParser = context.mock(MetadataParser.class); introductionGroupFactory = context.mock(IntroductionGroupFactory.class); - clock = context.mock(Clock.class); introductionManager = new IntroductionManagerImpl( db, clientHelper, metadataParser, introducerManager, @@ -135,6 +130,27 @@ public class IntroductionManagerImplTest extends BriarTestCase { oneOf(introducerManager) .makeIntroduction(txn, introducee1, introducee2, null, time); + // get both introduction groups + oneOf(introductionGroupFactory) + .createIntroductionGroup(introducee1); + will(returnValue(introductionGroup1)); + oneOf(introductionGroupFactory) + .createIntroductionGroup(introducee2); + will(returnValue(introductionGroup2)); + // track message for group 1 + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + introductionGroup1.getId()); + will(returnValue(metadataBefore)); + oneOf(clientHelper) + .mergeGroupMetadata(txn, introductionGroup1.getId(), + metadataAfter); + // track message for group 2 + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + introductionGroup2.getId()); + will(returnValue(metadataBefore)); + oneOf(clientHelper) + .mergeGroupMetadata(txn, introductionGroup2.getId(), + metadataAfter); oneOf(db).endTransaction(txn); }}); @@ -163,6 +179,13 @@ public class IntroductionManagerImplTest extends BriarTestCase { oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); will(returnValue(state)); oneOf(introduceeManager).acceptIntroduction(txn, state, time); + // track message + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + introductionGroup1.getId()); + will(returnValue(metadataBefore)); + oneOf(clientHelper) + .mergeGroupMetadata(txn, introductionGroup1.getId(), + metadataAfter); oneOf(db).endTransaction(txn); }}); @@ -191,6 +214,13 @@ public class IntroductionManagerImplTest extends BriarTestCase { oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); will(returnValue(state)); oneOf(introduceeManager).declineIntroduction(txn, state, time); + // track message + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + introductionGroup1.getId()); + will(returnValue(metadataBefore)); + oneOf(clientHelper) + .mergeGroupMetadata(txn, introductionGroup1.getId(), + metadataAfter); oneOf(db).endTransaction(txn); }}); @@ -241,12 +271,22 @@ public class IntroductionManagerImplTest extends BriarTestCase { final BdfDictionary state = new BdfDictionary(); txn = new Transaction(null, false); + metadataBefore.put(GROUP_KEY_UNREAD_COUNT, 1L); + metadataAfter.put(GROUP_KEY_UNREAD_COUNT, 2L); + context.checking(new Expectations() {{ oneOf(introduceeManager) .initialize(txn, introductionGroup1.getId(), msg); will(returnValue(state)); oneOf(introduceeManager) .incomingMessage(txn, state, msg); + // track message + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + introductionGroup1.getId()); + will(returnValue(metadataBefore)); + oneOf(clientHelper) + .mergeGroupMetadata(txn, introductionGroup1.getId(), + metadataAfter); }}); introductionManager @@ -272,10 +312,20 @@ public class IntroductionManagerImplTest extends BriarTestCase { txn = new Transaction(null, false); + metadataBefore.put(GROUP_KEY_UNREAD_COUNT, 41L); + metadataAfter.put(GROUP_KEY_UNREAD_COUNT, 42L); + context.checking(new Expectations() {{ oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); will(returnValue(state)); oneOf(introducerManager).incomingMessage(txn, state, msg); + // track message + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + introductionGroup1.getId()); + will(returnValue(metadataBefore)); + oneOf(clientHelper) + .mergeGroupMetadata(txn, introductionGroup1.getId(), + metadataAfter); }}); introductionManager -- GitLab