diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index 2f5c3321f0d2a8af8abe3db1410a46e67209a896..4d50c4cc5c656ff93c1b684e53306575e1e4c969 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -144,6 +144,9 @@ public interface DatabaseComponent { /** Returns the set of groups to which the user subscribes. */ Collection<Group> getSubscriptions() throws DbException; + /** Returns the number of unread messages in each subscribed group. */ + Map<GroupId, Integer> getUnreadMessageCounts() throws DbException; + /** Returns the contacts to which the given group is visible. */ Collection<ContactId> getVisibility(GroupId g) throws DbException; diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index b65f99dbee593d7d27744541e59f620d0fb1e79a..e8c9d05bd8e5874e260a6c40721dc4c5c5f8ddf3 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -406,6 +406,13 @@ interface Database<T> { */ long getTransportsSent(T txn, ContactId c) throws DbException; + /** + * Returns the number of unread messages in each subscribed group. + * <p> + * Locking: message read, messageFlag read, subscription read. + */ + Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException; + /** * Returns the contacts to which the given group is visible. * <p> diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index 881a2c4bccdc7ade16e3227c263e324fdeba82ef..2457bd98bbd6051b72ca447b3cf46d51a0f8c256 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -885,6 +885,34 @@ DatabaseCleaner.Callback { } } + public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException { + messageLock.readLock().lock(); + try { + messageFlagLock.readLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<GroupId, Integer> counts = + db.getUnreadMessageCounts(txn); + db.commitTransaction(txn); + return counts; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageFlagLock.readLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } + public Collection<ContactId> getVisibility(GroupId g) throws DbException { contactLock.readLock().lock(); try { diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index fe8e757bfc4cc04767b0514e2013d89fcb368230..1a4b57ca3e54889ee5c3f7a9b2b903cab1273665 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -1709,6 +1709,33 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public Map<GroupId, Integer> getUnreadMessageCounts(Connection txn) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT groupId, COUNT(*)" + + " FROM messages LEFT OUTER JOIN flags" + + " ON messages.messageId = flags.messageId" + + " WHERE (NOT read) OR (read IS NULL)" + + " GROUP BY groupId"; + ps = txn.prepareStatement(sql); + rs = ps.executeQuery(); + Map<GroupId, Integer> counts = new HashMap<GroupId, Integer>(); + while(rs.next()) { + GroupId g = new GroupId(rs.getBytes(1)); + counts.put(g, rs.getInt(2)); + } + rs.close(); + ps.close(); + return counts; + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public Collection<ContactId> getVisibility(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index 270085085ccfaad507e8396fa1cecb1bf899f149..e2ac13f2c9bc5fefb59373e926726368d50ded24 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -1776,6 +1776,62 @@ public class H2DatabaseTest extends TestCase { db.close(); } + @Test + public void testGetUnreadMessageCounts() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Subscribe to a couple of groups + db.addSubscription(txn, group); + GroupId groupId1 = new GroupId(TestUtils.getRandomId()); + Group group1 = groupFactory.createGroup(groupId1, "Another group", + null); + db.addSubscription(txn, group1); + + // Store two messages in the first group + db.addGroupMessage(txn, message); + MessageId messageId1 = new MessageId(TestUtils.getRandomId()); + Message message1 = new TestMessage(messageId1, null, groupId, + authorId, subject, timestamp, raw); + db.addGroupMessage(txn, message1); + + // Store one message in the second group + MessageId messageId2 = new MessageId(TestUtils.getRandomId()); + Message message2 = new TestMessage(messageId2, null, groupId1, + authorId, subject, timestamp, raw); + db.addGroupMessage(txn, message2); + + // Mark one of the messages in the first group read + assertFalse(db.setRead(txn, messageId, true)); + + // There should be one unread message in each group + Map<GroupId, Integer> counts = db.getUnreadMessageCounts(txn); + assertEquals(2, counts.size()); + Integer count = counts.get(groupId); + assertNotNull(count); + assertEquals(1, count.intValue()); + count = counts.get(groupId1); + assertNotNull(count); + assertEquals(1, count.intValue()); + + // Mark the read message unread (it will now be false rather than null) + assertTrue(db.setRead(txn, messageId, false)); + + // Mark the message in the second group read + assertFalse(db.setRead(txn, messageId2, true)); + + // There should be two unread messages in the first group, none in + // the second group + counts = db.getUnreadMessageCounts(txn); + assertEquals(1, counts.size()); + count = counts.get(groupId); + assertNotNull(count); + assertEquals(2, count.intValue()); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testExceptionHandling() throws Exception { Database<Connection> db = open(false);