diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 160b172e01e50c301b2bf5be207f0b7a247fb842..07fe620d161e9a37e0f169856a954fced9c2a91f 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -132,6 +132,13 @@ interface Database<T> { */ TransportIndex addTransport(T txn, TransportId t) throws DbException; + /** + * Makes the given group visible to the given contact. + * <p> + * Locking: contact read, subscription write. + */ + void addVisibility(T txn, ContactId c, GroupId g) throws DbException; + /** * Returns true if the database contains the given contact. * <p> @@ -514,6 +521,13 @@ interface Database<T> { */ void removeSubscription(T txn, GroupId g) throws DbException; + /** + * Makes the given group invisible to the given contact. + * <p> + * Locking: contact read, subscription write. + */ + void removeVisibility(T txn, ContactId c, GroupId g) throws DbException; + /** * Sets the configuration for the given transport, replacing any existing * configuration for that transport. @@ -642,13 +656,4 @@ interface Database<T> { */ void setTransportsSent(T txn, ContactId c, long timestamp) throws DbException; - - /** - * Makes the given group visible to the given set of contacts and invisible - * to any other contacts. - * <p> - * Locking: contact read, subscription write. - */ - void setVisibility(T txn, GroupId g, Collection<ContactId> visible) - throws DbException; } diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index 6318d29241be83a3b51672af5e4e054eb9ae3bdd..271f37fdc4c3e0bf3b258c2df2b6fe21ff6feb40 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -1425,7 +1425,7 @@ DatabaseCleaner.Callback { public void setVisibility(GroupId g, Collection<ContactId> visible) throws DbException { - List<ContactId> affected; + List<ContactId> affected = new ArrayList<ContactId>(); contactLock.readLock().lock(); try { subscriptionLock.writeLock().lock(); @@ -1433,23 +1433,25 @@ DatabaseCleaner.Callback { T txn = db.startTransaction(); try { // Use HashSets for O(1) lookups, O(n) overall running time - HashSet<ContactId> then, now; - // Retrieve the group's current visibility - then = new HashSet<ContactId>(db.getVisibility(txn, g)); - // Don't try to make the group visible to ex-contacts - now = new HashSet<ContactId>(visible); - now.retainAll(new HashSet<ContactId>(db.getContacts(txn))); - db.setVisibility(txn, g, now); - // Work out which contacts were affected by the change - affected = new ArrayList<ContactId>(); - for(ContactId c : then) { - if(!now.contains(c)) affected.add(c); + visible = new HashSet<ContactId>(visible); + HashSet<ContactId> oldVisible = + new HashSet<ContactId>(db.getVisibility(txn, g)); + // Set the group's visibility for each current contact + for(ContactId c : db.getContacts(txn)) { + boolean then = oldVisible.contains(c); + boolean now = visible.contains(c); + if(!then && now) { + db.addVisibility(txn, c, g); + affected.add(c); + } else if(then && !now) { + db.removeVisibility(txn, c, g); + affected.add(c); + } } - for(ContactId c : now) { - if(!then.contains(c)) affected.add(c); + if(!affected.isEmpty()) { + db.setSubscriptionsModified(txn, affected, + System.currentTimeMillis()); } - db.setSubscriptionsModified(txn, affected, - System.currentTimeMillis()); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index c2ec4eeb0106e520bb07187911b01b976035de2c..916c30ed5c4612b79e2f1579e6ac76d9f3552e03 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -59,12 +59,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " start BIGINT NOT NULL," + " PRIMARY KEY (groupId))"; - private static final String CREATE_SUBSCRIPTION_IDS = - "CREATE TABLE subscriptionIds" - + " (groupId HASH," // Null for the head of the list - + " nextId HASH," // Null for the tail of the list - + " deleted BIGINT NOT NULL)"; - private static final String CREATE_CONTACTS = "CREATE TABLE contacts" + " (contactId COUNTER," @@ -104,12 +98,13 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String CREATE_VISIBILITIES = "CREATE TABLE visibilities" - + " (groupId HASH NOT NULL," - + " contactId INT NOT NULL," - + " PRIMARY KEY (groupId, contactId)," - + " FOREIGN KEY (groupId) REFERENCES subscriptions (groupId)" - + " ON DELETE CASCADE," + + " (contactId INT NOT NULL," + + " groupId HASH," // Null for the head of the linked list + + " nextId HASH," // Null for the tail of the linked list + + " deleted BIGINT NOT NULL," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (groupId) REFERENCES subscriptions (groupId)" + " ON DELETE CASCADE)"; private static final String INDEX_VISIBILITIES_BY_GROUP = @@ -333,7 +328,6 @@ abstract class JdbcDatabase implements Database<Connection> { try { s = txn.createStatement(); s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTIONS)); - s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTION_IDS)); s.executeUpdate(insertTypeNames(CREATE_CONTACTS)); s.executeUpdate(insertTypeNames(CREATE_MESSAGES)); s.executeUpdate(INDEX_MESSAGES_BY_PARENT); @@ -729,7 +723,6 @@ abstract class JdbcDatabase implements Database<Connection> { public void addSubscription(Connection txn, Group g) throws DbException { PreparedStatement ps = null; - ResultSet rs = null; try { // Add the group to the subscriptions table String sql = "INSERT INTO subscriptions" @@ -743,11 +736,62 @@ abstract class JdbcDatabase implements Database<Connection> { int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); + } catch(SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + + public TransportIndex addTransport(Connection txn, TransportId t) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + // Allocate a new index + String sql = "INSERT INTO transports (transportId) VALUES (?)"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, t.getBytes()); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + ps.close(); + // If the new index is in range, return it + sql = "SELECT index FROM transports WHERE transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, t.getBytes()); + rs = ps.executeQuery(); + if(!rs.next()) throw new DbStateException(); + int i = rs.getInt(1); + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + if(i < ProtocolConstants.MAX_TRANSPORTS) + return new TransportIndex(i); + // Too many transports - delete the new index and return null + sql = "DELETE FROM transports WHERE transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, t.getBytes()); + affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + return null; + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + + public void addVisibility(Connection txn, ContactId c, GroupId g) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { // Insert the group ID into the linked list - byte[] id = g.getId().getBytes(); - sql = "SELECT groupId, nextId, deleted FROM subscriptionIds" + byte[] id = g.getBytes(); + String sql = "SELECT groupId, nextId, deleted FROM visibilities" + + " WHERE contactId = ?" + " ORDER BY groupId"; ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); rs = ps.executeQuery(); if(rs.next()) { // The head pointer of the list exists @@ -768,29 +812,33 @@ abstract class JdbcDatabase implements Database<Connection> { // Update the previous element if(groupId == null) { // Inserting at the head of the list - sql = "UPDATE subscriptionIds SET nextId = ?" - + " WHERE groupId IS NULL"; + sql = "UPDATE visibilities SET nextId = ?" + + " WHERE contactId = ? AND groupId IS NULL"; ps = txn.prepareStatement(sql); ps.setBytes(1, id); + ps.setInt(2, c.getInt()); } else { // Inserting in the middle or at the tail of the list - sql = "UPDATE subscriptionIds SET nextId = ?" - + " WHERE groupId = ?"; + sql = "UPDATE visibilities SET nextId = ?" + + " WHERE contactId = ? AND groupId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, id); - ps.setBytes(2, groupId); + ps.setInt(2, c.getInt()); + ps.setBytes(3, groupId); } - affected = ps.executeUpdate(); + int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); // Insert the new element - sql = "INSERT INTO subscriptionIds (groupId, nextId, deleted)" - + " VALUES (?, ?, ?)"; + sql = "INSERT INTO visibilities" + + " (contactId, groupId, nextId, deleted)" + + " VALUES (?, ?, ?, ?)"; ps = txn.prepareStatement(sql); - ps.setBytes(1, id); - if(nextId == null) ps.setNull(2, Types.BINARY); // At the tail - else ps.setBytes(2, nextId); // In the middle - ps.setLong(3, deleted); + ps.setInt(1, c.getInt()); + ps.setBytes(2, id); + if(nextId == null) ps.setNull(3, Types.BINARY); // At the tail + else ps.setBytes(3, nextId); // In the middle + ps.setLong(4, deleted); affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); @@ -798,17 +846,19 @@ abstract class JdbcDatabase implements Database<Connection> { // The head pointer of the list does not exist rs.close(); ps.close(); - sql = "INSERT INTO subscriptionIds (nextId, deleted)" - + " VALUES (?, ZERO())"; + sql = "INSERT INTO visibilities (contactId, nextId, deleted)" + + " VALUES (?, ?, ZERO())"; ps = txn.prepareStatement(sql); - ps.setBytes(1, id); - affected = ps.executeUpdate(); + ps.setInt(1, c.getInt()); + ps.setBytes(2, id); + int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); - sql = "INSERT INTO subscriptionIds (groupId, deleted)" - + " VALUES (?, ZERO())"; + sql = "INSERT INTO visibilities (contactId, groupId, deleted)" + + " VALUES (?, ?, ZERO())"; ps = txn.prepareStatement(sql); - ps.setBytes(1, id); + ps.setInt(1, c.getInt()); + ps.setBytes(2, id); affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); @@ -820,44 +870,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public TransportIndex addTransport(Connection txn, TransportId t) - throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - // Allocate a new index - String sql = "INSERT INTO transports (transportId) VALUES (?)"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, t.getBytes()); - int affected = ps.executeUpdate(); - if(affected != 1) throw new DbStateException(); - ps.close(); - // If the new index is in range, return it - sql = "SELECT index FROM transports WHERE transportId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, t.getBytes()); - rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - int i = rs.getInt(1); - if(rs.next()) throw new DbStateException(); - rs.close(); - ps.close(); - if(i < ProtocolConstants.MAX_TRANSPORTS) - return new TransportIndex(i); - // Too many transports - delete the new index and return null - sql = "DELETE FROM transports WHERE transportId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, t.getBytes()); - affected = ps.executeUpdate(); - if(affected != 1) throw new DbStateException(); - return null; - } catch(SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - public boolean containsContact(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; @@ -2176,39 +2188,82 @@ abstract class JdbcDatabase implements Database<Connection> { public void removeSubscription(Connection txn, GroupId g) throws DbException { - PreparedStatement ps = null; + PreparedStatement ps = null, ps1 = null; ResultSet rs = null; try { + // Remove the group ID from the visibility lists + long now = System.currentTimeMillis(); + String sql = "SELECT contactId, nextId FROM visibilities" + + " WHERE groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, g.getBytes()); + rs = ps.executeQuery(); + while(rs.next()) { + int contactId = rs.getInt(1); + byte[] nextId = rs.getBytes(2); + sql = "UPDATE visibilities SET nextId = ?, deleted = ?" + + " WHERE contactId = ? AND nextId = ?"; + ps1 = txn.prepareStatement(sql); + if(nextId == null) ps1.setNull(1, Types.BINARY); // At the tail + else ps1.setBytes(1, nextId); // At the head or in the middle + ps1.setLong(2, now); + ps1.setInt(3, contactId); + ps1.setBytes(4, g.getBytes()); + int affected = ps1.executeUpdate(); + if(affected != 1) throw new DbStateException(); + ps1.close(); + } + rs.close(); + ps.close(); // Remove the group from the subscriptions table - String sql = "DELETE FROM subscriptions WHERE groupId = ?"; + sql = "DELETE FROM subscriptions WHERE groupId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getBytes()); int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); + } catch(SQLException e) { + e.printStackTrace(); + tryToClose(rs); + tryToClose(ps); + tryToClose(ps1); + throw new DbException(e); + } + } + + public void removeVisibility(Connection txn, ContactId c, GroupId g) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { // Remove the group ID from the linked list - sql = "SELECT nextId FROM subscriptionIds WHERE groupId = ?"; + String sql = "SELECT nextId FROM visibilities" + + " WHERE contactId = ? AND groupId = ?"; ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); + ps.setInt(1, c.getInt()); + ps.setBytes(2, g.getBytes()); rs = ps.executeQuery(); if(!rs.next()) throw new DbStateException(); byte[] nextId = rs.getBytes(1); if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - sql = "DELETE FROM subscriptionIds WHERE groupId = ?"; + sql = "DELETE FROM visibilities" + + " WHERE contactId = ? AND groupId = ?"; ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - affected = ps.executeUpdate(); + ps.setInt(1, c.getInt()); + ps.setBytes(2, g.getBytes()); + int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); - sql = "UPDATE subscriptionIds SET nextId = ?, deleted = ?" - + " WHERE nextId = ?"; + sql = "UPDATE visibilities SET nextId = ?, deleted = ?" + + " WHERE contactId = ? AND nextId = ?"; ps = txn.prepareStatement(sql); if(nextId == null) ps.setNull(1, Types.BINARY); // At the tail else ps.setBytes(1, nextId); // At the head or in the middle ps.setLong(2, System.currentTimeMillis()); - ps.setBytes(3, g.getBytes()); + ps.setInt(3, c.getInt()); + ps.setBytes(4, g.getBytes()); affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); @@ -2800,36 +2855,4 @@ abstract class JdbcDatabase implements Database<Connection> { throw new DbException(e); } } - - public void setVisibility(Connection txn, GroupId g, - Collection<ContactId> visible) throws DbException { - PreparedStatement ps = null; - try { - // Delete any existing visibilities - String sql = "DELETE FROM visibilities where groupId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - ps.executeUpdate(); - ps.close(); - // Store the new visibilities - sql = "INSERT INTO visibilities (groupId, contactId)" - + " VALUES (?, ?)"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - for(ContactId c : visible) { - ps.setInt(2, c.getInt()); - ps.addBatch(); - } - int[] batchAffected = ps.executeBatch(); - if(batchAffected.length != visible.size()) - throw new DbStateException(); - for(int i = 0; i < batchAffected.length; i++) { - if(batchAffected[i] != 1) throw new DbStateException(); - } - ps.close(); - } catch(SQLException e) { - tryToClose(ps); - throw new DbException(e); - } - } } diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index 9b86bf2a3abf49398c6c35b542e16a1e146a39ff..f10d3cc74b4815620454c86568f513c8642ce117 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -22,6 +22,7 @@ import net.sf.briar.api.db.event.ContactRemovedEvent; import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.MessagesAddedEvent; import net.sf.briar.api.db.event.RatingChangedEvent; +import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent; import net.sf.briar.api.db.event.TransportAddedEvent; import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.api.protocol.Ack; @@ -679,14 +680,10 @@ public abstract class DatabaseComponentTest extends BriarTestCase { public void testGenerateBatch() throws Exception { final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); final byte[] raw1 = new byte[size]; - final Collection<MessageId> sendable = Arrays.asList(new MessageId[] { - messageId, - messageId1 - }); - final Collection<byte[]> messages = Arrays.asList(new byte[][] { - raw, - raw1 - }); + final Collection<MessageId> sendable = + Arrays.asList(new MessageId[] {messageId, messageId1}); + final Collection<byte[]> messages = + Arrays.asList(new byte[][] {raw, raw1}); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); @@ -733,9 +730,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { requested.add(messageId); requested.add(messageId1); requested.add(messageId2); - final Collection<byte[]> msgs = Arrays.asList(new byte[][] { - raw1 - }); + final Collection<byte[]> msgs = Arrays.asList(new byte[][] {raw1}); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); @@ -1541,4 +1536,70 @@ public abstract class DatabaseComponentTest extends BriarTestCase { context.assertIsSatisfied(); } + + @Test + public void testVisibilityChangedCallsListeners() throws Exception { + final ContactId contactId1 = new ContactId(234); + final Collection<ContactId> both = + Arrays.asList(new ContactId[] {contactId, contactId1}); + Mockery context = new Mockery(); + @SuppressWarnings("unchecked") + final Database<Object> database = context.mock(Database.class); + final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); + final PacketFactory packetFactory = context.mock(PacketFactory.class); + final DatabaseListener listener = context.mock(DatabaseListener.class); + context.checking(new Expectations() {{ + oneOf(database).startTransaction(); + will(returnValue(txn)); + oneOf(database).getVisibility(txn, groupId); + will(returnValue(both)); + oneOf(database).getContacts(txn); + will(returnValue(both)); + oneOf(database).removeVisibility(txn, contactId1, groupId); + oneOf(database).setSubscriptionsModified(with(txn), + with(Collections.singletonList(contactId1)), + with(any(long.class))); + oneOf(database).commitTransaction(txn); + oneOf(listener).eventOccurred(with(any( + SubscriptionsUpdatedEvent.class))); + }}); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown, packetFactory); + + db.addListener(listener); + db.setVisibility(groupId, Collections.singletonList(contactId)); + + context.assertIsSatisfied(); + } + + @Test + public void testVisibilityUnchangedDoesNotCallListeners() throws Exception { + final ContactId contactId1 = new ContactId(234); + final Collection<ContactId> both = + Arrays.asList(new ContactId[] {contactId, contactId1}); + Mockery context = new Mockery(); + @SuppressWarnings("unchecked") + final Database<Object> database = context.mock(Database.class); + final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); + final PacketFactory packetFactory = context.mock(PacketFactory.class); + final DatabaseListener listener = context.mock(DatabaseListener.class); + context.checking(new Expectations() {{ + oneOf(database).startTransaction(); + will(returnValue(txn)); + oneOf(database).getVisibility(txn, groupId); + will(returnValue(both)); + oneOf(database).getContacts(txn); + will(returnValue(both)); + oneOf(database).commitTransaction(txn); + }}); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown, packetFactory); + + db.addListener(listener); + db.setVisibility(groupId, both); + + context.assertIsSatisfied(); + } } diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index 6cdad47d48ccf04dd0c7dcae33e26680a9f0ef36..310c3219430994267840a129afce2a12850a7c93 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -355,7 +355,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); db.setStatus(txn, contactId, messageId, Status.NEW); @@ -393,7 +393,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); db.setSendability(txn, messageId, 1); @@ -435,7 +435,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.addGroupMessage(txn, message); db.setSendability(txn, messageId, 1); db.setStatus(txn, contactId, messageId, Status.NEW); @@ -474,7 +474,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.addGroupMessage(txn, message); db.setSendability(txn, messageId, 1); db.setStatus(txn, contactId, messageId, Status.NEW); @@ -509,7 +509,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); db.setSendability(txn, messageId, 1); @@ -553,7 +553,7 @@ public class H2DatabaseTest extends BriarTestCase { assertFalse(it.hasNext()); // Making the subscription visible should make the message sendable - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); assertTrue(db.hasSendableMessages(txn, contactId)); it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); assertTrue(it.hasNext()); @@ -674,7 +674,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); db.setSendability(txn, messageId, 1); @@ -713,7 +713,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); db.setSendability(txn, messageId, 1); @@ -1274,7 +1274,7 @@ public class H2DatabaseTest extends BriarTestCase { // the message is older than the contact's subscription assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); Map<Group, Long> subs = Collections.singletonMap(group, timestamp + 1); db.setSubscriptions(txn, contactId, subs, 1); db.addGroupMessage(txn, message); @@ -1298,7 +1298,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); @@ -1323,7 +1323,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact and subscribe to a group assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); // The message is not in the database @@ -1398,7 +1398,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); @@ -1420,7 +1420,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a contact, subscribe to a group and store a message assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); db.addSubscription(txn, group); - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); db.setSubscriptions(txn, contactId, subscriptions, 1); db.addGroupMessage(txn, message); @@ -1444,11 +1444,11 @@ public class H2DatabaseTest extends BriarTestCase { // The group should not be visible to the contact assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId)); // Make the group visible to the contact - db.setVisibility(txn, groupId, Collections.singletonList(contactId)); + db.addVisibility(txn, contactId, groupId); assertEquals(Collections.singletonList(contactId), db.getVisibility(txn, groupId)); // Make the group invisible again - db.setVisibility(txn, groupId, Collections.<ContactId>emptyList()); + db.removeVisibility(txn, contactId, groupId); assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId)); db.commitTransaction(txn); @@ -1895,12 +1895,20 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add the groups to the database + // Subscribe to the groups and add a contact for(Group g : groups) db.addSubscription(txn, g); + assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase)); + + // Make the groups visible to the contact + Collections.shuffle(groups); + for(Group g : groups) db.addVisibility(txn, contactId, g.getId()); - // Remove the groups in a different order + // Make the groups invisible to the contact and remove them Collections.shuffle(groups); - for(Group g : groups) db.removeSubscription(txn, g.getId()); + for(Group g : groups) { + db.removeVisibility(txn, contactId, g.getId()); + db.removeSubscription(txn, g.getId()); + } db.commitTransaction(txn); db.close();