diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java index e269178b9ef739aa90e219d7ce65952fbd2dfaad..aa46a20bf58abc3310c28ae83949b086e59fbd6d 100644 --- a/briar-core/src/org/briarproject/db/Database.java +++ b/briar-core/src/org/briarproject/db/Database.java @@ -703,7 +703,7 @@ interface Database<T> { * true, unless an update with an equal or higher version number has * already been received from the contact. * <p> - * Locking: subscription write. + * Locking: message write, subscription write. */ boolean setGroups(T txn, ContactId c, Collection<Group> groups, long version) throws DbException; diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index c7f375fe6b7fe81285774c7bf0aa77eadbc4cc52..a963367c957006523e9df03173865e37e4b841c7 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -1500,22 +1500,27 @@ DatabaseCleaner.Callback { boolean updated; contactLock.readLock().lock(); try { - subscriptionLock.writeLock().lock(); + messageLock.writeLock().lock(); try { - T txn = db.startTransaction(); + subscriptionLock.writeLock().lock(); try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - Collection<Group> groups = u.getGroups(); - long version = u.getVersion(); - updated = db.setGroups(txn, c, groups, version); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + Collection<Group> groups = u.getGroups(); + long version = u.getVersion(); + updated = db.setGroups(txn, c, groups, version); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); } } finally { - subscriptionLock.writeLock().unlock(); + messageLock.writeLock().unlock(); } } finally { contactLock.readLock().unlock(); diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index 2d6a267498015b2c2d0b0d011872f41857f58134..d8624dc56c4a3499274afb828a49045a97da881e 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -21,10 +21,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -2896,8 +2898,7 @@ abstract class JdbcDatabase implements Database<Connection> { throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE statuses" - + " SET expiry = 0, txCount = 0" + String sql = "UPDATE statuses SET expiry = 0, txCount = 0" + " WHERE messageId = ? AND contactId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); @@ -2949,6 +2950,43 @@ abstract class JdbcDatabase implements Database<Connection> { ps.close(); // Return false if the update is obsolete if(affected == 0) return false; + // Find any messages in groups that are being removed + Set<GroupId> newIds = new HashSet<GroupId>(); + for(Group g : groups) newIds.add(g.getId()); + sql = "SELECT messageId, m.groupId" + + " FROM messages AS m" + + " JOIN contactGroups AS cg" + + " ON m.groupId = cg.groupId" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + rs = ps.executeQuery(); + List<MessageId> removed = new ArrayList<MessageId>(); + while(rs.next()) { + if(!newIds.contains(new GroupId(rs.getBytes(2)))) + removed.add(new MessageId(rs.getBytes(1))); + } + rs.close(); + ps.close(); + // Reset any statuses for messages in groups that are being removed + if(!removed.isEmpty()) { + sql = "UPDATE statuses SET ack = FALSE, seen = FALSE," + + " requested = FALSE, expiry = 0, txCount = 0" + + " WHERE contactId = ? AND messageId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + for(MessageId m : removed) { + ps.setBytes(2, m.getBytes()); + ps.addBatch(); + } + int[] batchAffected = ps.executeBatch(); + if(batchAffected.length != removed.size()) + throw new DbStateException(); + for(int i = 0; i < batchAffected.length; i++) { + if(batchAffected[i] < 0) throw new DbStateException(); + } + ps.close(); + } // Delete the existing subscriptions, if any sql = "DELETE FROM contactGroups WHERE contactId = ?"; ps = txn.prepareStatement(sql); diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java index 3b4e344cb2f38f9bfb9b33746c926e037e216806..361e9ff3ef87de13e643e82767bc2ea99ac33372 100644 --- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java +++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java @@ -1577,6 +1577,46 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } + @Test + public void testContactUnsubscribingResetsMessageStatus() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a contact who subscribes to a group + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthorId)); + db.setGroups(txn, contactId, Arrays.asList(group), 1); + + // Subscribe to the group and make it visible to the contact + db.addGroup(txn, group); + db.addVisibility(txn, contactId, groupId); + + // Add a message - it should be sendable to the contact + db.addMessage(txn, message, true); + db.addStatus(txn, contactId, messageId, false, false); + Collection<MessageId> sendable = db.getMessagesToSend(txn, contactId, + ONE_MEGABYTE); + assertEquals(Arrays.asList(messageId), sendable); + + // Mark the message as seen - it should no longer be sendable + db.raiseSeenFlag(txn, contactId, messageId); + sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertEquals(Collections.emptyList(), sendable); + + // The contact unsubscribes - the message should not be sendable + db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2); + sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertEquals(Collections.emptyList(), sendable); + + // The contact resubscribes - the message should be sendable again + db.setGroups(txn, contactId, Arrays.asList(group), 3); + sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertEquals(Arrays.asList(messageId), sendable); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testExceptionHandling() throws Exception { Database<Connection> db = open(false);