Added a flag for making groups visible to future contacts.

This commit is contained in:
akwizgran
2013-04-13 19:27:39 +01:00
parent f2e01d88a8
commit bbdfe30e78
6 changed files with 162 additions and 13 deletions

View File

@@ -344,11 +344,18 @@ public interface DatabaseComponent {
/**
* Makes the given group visible to the given set of contacts and invisible
* to any other contacts.
* to any other current or future contacts.
*/
void setVisibility(GroupId g, Collection<ContactId> visible)
throws DbException;
/**
* Makes the given group visible or invisible to future contacts by default.
* If <tt>visible</tt> is true, the group is also made visible to all
* current contacts.
*/
void setVisibleToAll(GroupId g, boolean visible) throws DbException;
/**
* Subscribes to the given group, or returns false if the user already has
* the maximum number of subscriptions.

View File

@@ -791,6 +791,13 @@ interface Database<T> {
void setTransportUpdateAcked(T txn, ContactId c, TransportId t,
long version) throws DbException;
/**
* Makes the given group visible or invisible to future contacts by default.
* <p>
* Locking: subscription write.
*/
void setVisibleToAll(T txn, GroupId g, boolean visible) throws DbException;
/**
* Updates the expiry times of the given messages with respect to the given
* contact, using the given transmission counts and the latency of the

View File

@@ -1982,22 +1982,61 @@ DatabaseCleaner.Callback {
if(!db.containsSubscription(txn, g))
throw new NoSuchSubscriptionException();
// Use HashSets for O(1) lookups, O(n) overall running time
HashSet<ContactId> newVisible =
new HashSet<ContactId>(visible);
HashSet<ContactId> oldVisible =
new HashSet<ContactId>(db.getVisibility(txn, g));
HashSet<ContactId> now = new HashSet<ContactId>(visible);
Collection<ContactId> before = db.getVisibility(txn, g);
before = new HashSet<ContactId>(before);
// Set the group's visibility for each current contact
for(ContactId c : db.getContactIds(txn)) {
boolean then = oldVisible.contains(c);
boolean now = newVisible.contains(c);
if(!then && now) {
boolean wasBefore = before.contains(c);
boolean isNow = now.contains(c);
if(!wasBefore && isNow) {
db.addVisibility(txn, c, g);
affected.add(c);
} else if(then && !now) {
} else if(wasBefore && !isNow) {
db.removeVisibility(txn, c, g);
affected.add(c);
}
}
// Make the group invisible to future contacts
db.setVisibleToAll(txn, g, false);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
if(!affected.isEmpty())
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
}
public void setVisibleToAll(GroupId g, boolean visible) throws DbException {
Collection<ContactId> affected = new ArrayList<ContactId>();
contactLock.readLock().lock();
try {
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g))
throw new NoSuchSubscriptionException();
// Make the group visible or invisible to future contacts
db.setVisibleToAll(txn, g, visible);
if(visible) {
// Make the group visible to all current contacts
Collection<ContactId> before = db.getVisibility(txn, g);
before = new HashSet<ContactId>(before);
for(ContactId c : db.getContactIds(txn)) {
if(!before.contains(c)) {
db.addVisibility(txn, c, g);
affected.add(c);
}
}
}
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);

View File

@@ -103,6 +103,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (groupId HASH NOT NULL,"
+ " name VARCHAR NOT NULL,"
+ " publicKey BINARY," // Null for unrestricted groups
+ " visibleToAll BOOLEAN NOT NULL,"
+ " PRIMARY KEY (groupId))";
// Locking: subscription
@@ -573,6 +574,27 @@ abstract class JdbcDatabase implements Database<Connection> {
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
// Make groups that are visible to everyone visible to this contact
sql = "SELECT groupId FROM groups WHERE visibleToAll = TRUE";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
Collection<byte[]> ids = new ArrayList<byte[]>();
while(rs.next()) ids.add(rs.getBytes(1));
rs.close();
ps.close();
if(!ids.isEmpty()) {
sql = "INSERT INTO groupVisibilities (contactId, groupId)"
+ " VALUES (?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
for(byte[] id : ids) {
ps.setBytes(2, id);
ps.addBatch();
}
affected = ps.executeUpdate();
if(affected != ids.size()) throw new DbStateException();
ps.close();
}
// Create a connection time row
sql = "INSERT INTO connectionTimes (contactId, lastConnected)"
+ " VALUES (?, ?)";
@@ -893,8 +915,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
if(count > MAX_SUBSCRIPTIONS) throw new DbStateException();
if(count == MAX_SUBSCRIPTIONS) return false;
sql = "INSERT INTO groups (groupId, name, publicKey)"
+ " VALUES (?, ?, ?)";
sql = "INSERT INTO groups (groupId, name, publicKey, visibleToAll)"
+ " VALUES (?, ?, ?, FALSE)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getId().getBytes());
ps.setString(2, g.getName());
@@ -3376,6 +3398,23 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void setVisibleToAll(Connection txn, GroupId g, boolean visible)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE groups SET visibleToAll = ? WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, visible);
ps.setBytes(2, g.getBytes());
int affected = ps.executeUpdate();
if(affected > 1) throw new DbStateException();
ps.close();
} catch(SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void updateExpiryTimes(Connection txn, ContactId c,
Map<MessageId, Integer> sent, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();

View File

@@ -1729,6 +1729,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).getContactIds(txn);
will(returnValue(both));
oneOf(database).removeVisibility(txn, contactId1, groupId);
oneOf(database).setVisibleToAll(txn, groupId, false);
oneOf(database).commitTransaction(txn);
oneOf(listener).eventOccurred(with(any(
LocalSubscriptionsUpdatedEvent.class)));
@@ -1745,7 +1746,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
@Test
public void testNotChangingVisibilityDoesNotCallListeners()
throws Exception {
final ContactId contactId1 = new ContactId(234);
final ContactId contactId1 = new ContactId(123);
final Collection<ContactId> both = Arrays.asList(contactId, contactId1);
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
@@ -1762,6 +1763,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(both));
oneOf(database).getContactIds(txn);
will(returnValue(both));
oneOf(database).setVisibleToAll(txn, groupId, false);
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner,
@@ -1773,6 +1775,57 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
context.assertIsSatisfied();
}
@Test
public void testSettingVisibleToAllTrueAffectsCurrentContacts()
throws Exception {
final ContactId contactId1 = new ContactId(123);
final Collection<ContactId> both = Arrays.asList(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 DatabaseListener listener = context.mock(DatabaseListener.class);
context.checking(new Expectations() {{
// setVisibility()
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
will(returnValue(true));
oneOf(database).getVisibility(txn, groupId);
will(returnValue(Collections.emptyList()));
oneOf(database).getContactIds(txn);
will(returnValue(both));
oneOf(database).addVisibility(txn, contactId, groupId);
oneOf(database).setVisibleToAll(txn, groupId, false);
oneOf(database).commitTransaction(txn);
oneOf(listener).eventOccurred(with(any(
LocalSubscriptionsUpdatedEvent.class)));
// setVisibleToAll()
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId);
will(returnValue(true));
oneOf(database).setVisibleToAll(txn, groupId, true);
oneOf(database).getVisibility(txn, groupId);
will(returnValue(Arrays.asList(contactId)));
oneOf(database).getContactIds(txn);
will(returnValue(both));
oneOf(database).addVisibility(txn, contactId1, groupId);
oneOf(database).commitTransaction(txn);
oneOf(listener).eventOccurred(with(any(
LocalSubscriptionsUpdatedEvent.class)));
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner,
shutdown);
db.addListener(listener);
db.setVisibility(groupId, Arrays.asList(contactId));
db.setVisibleToAll(groupId, true);
context.assertIsSatisfied();
}
@Test
public void testTemporarySecrets() throws Exception {
Mockery context = new Mockery();

View File

@@ -873,6 +873,7 @@ public class H2DatabaseTest extends BriarTestCase {
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
db.addSubscription(txn, group);
db.addVisibility(txn, contactId, groupId);
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
// The message is not in the database
@@ -891,6 +892,7 @@ public class H2DatabaseTest extends BriarTestCase {
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
db.addSubscription(txn, group);
db.addVisibility(txn, contactId, groupId);
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
db.addGroupMessage(txn, message);
@@ -915,6 +917,7 @@ public class H2DatabaseTest extends BriarTestCase {
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
db.addSubscription(txn, group);
db.addVisibility(txn, contactId, groupId);
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
db.addGroupMessage(txn, message);
@@ -1028,6 +1031,7 @@ public class H2DatabaseTest extends BriarTestCase {
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
db.addSubscription(txn, group);
db.addVisibility(txn, contactId, groupId);
db.addGroupMessage(txn, message);
db.addStatus(txn, contactId, messageId, false);
@@ -1048,8 +1052,8 @@ public class H2DatabaseTest extends BriarTestCase {
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
db.addSubscription(txn, group);
db.addGroupMessage(txn, message);
db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
db.addGroupMessage(txn, message);
db.addStatus(txn, contactId, messageId, false);
// The subscription is not visible