diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index 43ea3ae54f875af959f546edebcc59c989620684..5d4e595a93cb3f8de23d589a1dbe6bdd720bdd51 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -171,6 +171,9 @@ public interface DatabaseComponent { /** Records the user's rating for the given author. */ void setRating(AuthorId a, Rating r) throws DbException; + /** Records the given messages as having been seen by the given contact. */ + void setSeen(ContactId c, Collection<MessageId> seen) throws DbException; + /** * Sets the configuration for the transport with the given name, replacing * any existing configuration for that transport. diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index 97e3a66fab24fd192cffd2810409f444f474367b..c43c778a2ace0c9f9cf227d51c00d9201ae5ff7f 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -963,6 +963,8 @@ DatabaseCleaner.Callback { public void receiveOffer(ContactId c, Offer o, RequestWriter r) throws DbException, IOException { + Collection<MessageId> offered; + BitSet request; contactLock.readLock().lock(); try { if(!containsContact(c)) throw new NoSuchContactException(); @@ -972,10 +974,10 @@ DatabaseCleaner.Callback { try { subscriptionLock.readLock().lock(); try { - Collection<MessageId> offered = o.getMessageIds(); - BitSet request = new BitSet(offered.size()); T txn = db.startTransaction(); try { + offered = o.getMessageIds(); + request = new BitSet(offered.size()); Iterator<MessageId> it = offered.iterator(); for(int i = 0; it.hasNext(); i++) { // If the message is not in the database, or if @@ -989,7 +991,6 @@ DatabaseCleaner.Callback { db.abortTransaction(txn); throw e; } - r.writeRequest(request, offered.size()); } finally { subscriptionLock.readLock().unlock(); } @@ -1002,6 +1003,7 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } + r.writeRequest(request, offered.size()); } public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate s) @@ -1147,6 +1149,41 @@ DatabaseCleaner.Callback { } } + public void setSeen(ContactId c, Collection<MessageId> seen) + throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + for(MessageId m : seen) { + db.setStatus(txn, c, m, Status.SEEN); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + /** * Updates the sendability of all messages written by the given author, and * the ancestors of those messages if necessary. diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index 0a0bdd5a06cebfcaf9769ed357b05b98014a9b8e..4ebfe4c726b79b9b1d20fc98c880906eb2b9a332 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -586,11 +586,11 @@ public abstract class DatabaseComponentTest extends TestCase { final TransportUpdate transportsUpdate = context.mock(TransportUpdate.class); context.checking(new Expectations() {{ // Check whether the contact is still in the DB (which it's not) - exactly(17).of(database).startTransaction(); + exactly(18).of(database).startTransaction(); will(returnValue(txn)); - exactly(17).of(database).containsContact(txn, contactId); + exactly(18).of(database).containsContact(txn, contactId); will(returnValue(false)); - exactly(17).of(database).commitTransaction(txn); + exactly(18).of(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner); @@ -680,6 +680,11 @@ public abstract class DatabaseComponentTest extends TestCase { fail(); } catch(NoSuchContactException expected) {} + try { + db.setSeen(contactId, Collections.singleton(messageId)); + fail(); + } catch(NoSuchContactException expected) {} + context.assertIsSatisfied(); } @@ -1488,4 +1493,26 @@ public abstract class DatabaseComponentTest extends TestCase { context.assertIsSatisfied(); } + + @Test + public void testSetSeen() throws Exception { + Mockery context = new Mockery(); + @SuppressWarnings("unchecked") + final Database<Object> database = context.mock(Database.class); + final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + context.checking(new Expectations() {{ + allowing(database).startTransaction(); + will(returnValue(txn)); + allowing(database).commitTransaction(txn); + allowing(database).containsContact(txn, contactId); + will(returnValue(true)); + // setSeen(contactId, Collections.singleton(messageId)) + oneOf(database).setStatus(txn, contactId, messageId, Status.SEEN); + }}); + DatabaseComponent db = createDatabaseComponent(database, cleaner); + + db.setSeen(contactId, Collections.singleton(messageId)); + + context.assertIsSatisfied(); + } }