diff --git a/api/net/sf/briar/api/db/event/BatchReceivedEvent.java b/api/net/sf/briar/api/db/event/BatchReceivedEvent.java index 6791a53108d87d92189fb7be6da7694468d86919..a7ebcdef98c8b1a4ccd9902c1d259c35a7f6f946 100644 --- a/api/net/sf/briar/api/db/event/BatchReceivedEvent.java +++ b/api/net/sf/briar/api/db/event/BatchReceivedEvent.java @@ -1,5 +1,6 @@ package net.sf.briar.api.db.event; +/** An event that is broadcast when a batch of messages is received. */ public class BatchReceivedEvent extends DatabaseEvent { } diff --git a/api/net/sf/briar/api/db/event/ContactAddedEvent.java b/api/net/sf/briar/api/db/event/ContactAddedEvent.java index 2d02c03de9807b3f16946a045460866694284466..84cc5f298865d7bde35ca176283ba5081ebd7519 100644 --- a/api/net/sf/briar/api/db/event/ContactAddedEvent.java +++ b/api/net/sf/briar/api/db/event/ContactAddedEvent.java @@ -2,6 +2,7 @@ package net.sf.briar.api.db.event; import net.sf.briar.api.ContactId; +/** An event that is broadcast when a contact is added. */ public class ContactAddedEvent extends DatabaseEvent { private final ContactId contactId; diff --git a/api/net/sf/briar/api/db/event/ContactRemovedEvent.java b/api/net/sf/briar/api/db/event/ContactRemovedEvent.java index e9322fcaa5aaa8e9dda13ba287be7a3bd8575c98..face9f4e6d1eae72d19280ef3e9f867e5d26506a 100644 --- a/api/net/sf/briar/api/db/event/ContactRemovedEvent.java +++ b/api/net/sf/briar/api/db/event/ContactRemovedEvent.java @@ -2,6 +2,7 @@ package net.sf.briar.api.db.event; import net.sf.briar.api.ContactId; +/** An event that is broadcast when a contact is removed. */ public class ContactRemovedEvent extends ContactAddedEvent { public ContactRemovedEvent(ContactId contactId) { diff --git a/api/net/sf/briar/api/db/event/DatabaseEvent.java b/api/net/sf/briar/api/db/event/DatabaseEvent.java index ff3b363dc8776e05b1b399891c2550f8fa38214c..2316bdef350f98cd8ebe2e63f1000d2b03be5560 100644 --- a/api/net/sf/briar/api/db/event/DatabaseEvent.java +++ b/api/net/sf/briar/api/db/event/DatabaseEvent.java @@ -1,5 +1,6 @@ package net.sf.briar.api.db.event; +/** An abstract superclass for database events. */ public abstract class DatabaseEvent { } diff --git a/api/net/sf/briar/api/db/event/DatabaseListener.java b/api/net/sf/briar/api/db/event/DatabaseListener.java index d55be9b767a751b0b803fafe67396edd5963e5f1..58605bee125550652d113bbb480b915772763f38 100644 --- a/api/net/sf/briar/api/db/event/DatabaseListener.java +++ b/api/net/sf/briar/api/db/event/DatabaseListener.java @@ -1,6 +1,5 @@ package net.sf.briar.api.db.event; - /** An interface for receiving notifications when database events occur. */ public interface DatabaseListener { diff --git a/api/net/sf/briar/api/db/event/MessagesAddedEvent.java b/api/net/sf/briar/api/db/event/MessagesAddedEvent.java index d8efb677bed0142a1f74b85f60806589fab5b7c9..c51515d528a1e409911fe6678c0b76ae03439c34 100644 --- a/api/net/sf/briar/api/db/event/MessagesAddedEvent.java +++ b/api/net/sf/briar/api/db/event/MessagesAddedEvent.java @@ -1,5 +1,9 @@ package net.sf.briar.api.db.event; +/** + * An event that is broadcast when one or more messages are added to the + * database. + */ public class MessagesAddedEvent extends DatabaseEvent { } diff --git a/api/net/sf/briar/api/db/event/SubscriptionsUpdatedEvent.java b/api/net/sf/briar/api/db/event/SubscriptionsUpdatedEvent.java index 416a49752a0e6b6659a927434428df63fc069c64..2f878438b655a0d3adff422a67789671b3edf30d 100644 --- a/api/net/sf/briar/api/db/event/SubscriptionsUpdatedEvent.java +++ b/api/net/sf/briar/api/db/event/SubscriptionsUpdatedEvent.java @@ -1,18 +1,27 @@ package net.sf.briar.api.db.event; import java.util.Collection; +import java.util.Collections; import net.sf.briar.api.ContactId; +/** + * An event that is broadcast when the set of subscriptions visible to one or + * more contacts is updated. + */ public class SubscriptionsUpdatedEvent extends DatabaseEvent { private final Collection<ContactId> affectedContacts; - // FIXME: Replace this constructor public SubscriptionsUpdatedEvent() { - affectedContacts = null; + affectedContacts = Collections.emptyList(); } + public SubscriptionsUpdatedEvent(Collection<ContactId> affectedContacts) { + this.affectedContacts = affectedContacts; + } + + /** Returns the contacts affected by the update. */ public Collection<ContactId> getAffectedContacts() { return affectedContacts; } diff --git a/api/net/sf/briar/api/db/event/TransportsUpdatedEvent.java b/api/net/sf/briar/api/db/event/TransportsUpdatedEvent.java index 0778eacf83ccdb616432839d3cee7c6eff054388..1360fe9ace25847ed97332eac6f22a68ee5df431 100644 --- a/api/net/sf/briar/api/db/event/TransportsUpdatedEvent.java +++ b/api/net/sf/briar/api/db/event/TransportsUpdatedEvent.java @@ -1,5 +1,6 @@ package net.sf.briar.api.db.event; +/** An event that is broadcast when the local transports are updated. */ public class TransportsUpdatedEvent extends DatabaseEvent { } diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 7a423ae9a3cb5b9d709762fc54c3a88dce543230..61f34953a7b34e6c5b46ae1112673ad2eb0f85eb 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -116,11 +116,12 @@ interface Database<T> { boolean addPrivateMessage(T txn, Message m, ContactId c) throws DbException; /** - * Subscribes to the given group. + * Subscribes to the given group and returns true if the subscription did + * not previously exist. * <p> * Locking: subscriptions write. */ - void addSubscription(T txn, Group g) throws DbException; + boolean addSubscription(T txn, Group g) throws DbException; /** * Returns true if the database contains the given contact. diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index 927ca49ce8579f8668ca16a1f76aa5eaf790c21e..7fdebaa51ebc17ac1d0817f2be8141faae20f8cb 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -1277,19 +1278,19 @@ DatabaseCleaner.Callback { public void setVisibility(GroupId g, Collection<ContactId> visible) throws DbException { + Collection<ContactId> then, now; contactLock.readLock().lock(); try { subscriptionLock.writeLock().lock(); try { T txn = db.startTransaction(); try { - // Remove any ex-contacts from the set - Collection<ContactId> present = - new ArrayList<ContactId>(visible.size()); - for(ContactId c : visible) { - if(db.containsContact(txn, c)) present.add(c); - } - db.setVisibility(txn, g, present); + // Get the contacts to which the group used to be visible + 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); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -1301,19 +1302,22 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } + // Work out which contacts were affected by the change + Collection<ContactId> affected = new ArrayList<ContactId>(); + for(ContactId c : then) if(!now.contains(c)) affected.add(c); + for(ContactId c : now) if(!then.contains(c)) affected.add(c); + // Call the listeners outside the lock + callListeners(new SubscriptionsUpdatedEvent(affected)); } public void subscribe(Group g) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g); - boolean added = false; + boolean added; subscriptionLock.writeLock().lock(); try { T txn = db.startTransaction(); try { - if(db.containsSubscription(txn, g.getId())) { - db.addSubscription(txn, g); - added = true; - } + added = db.addSubscription(txn, g); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -1328,7 +1332,8 @@ DatabaseCleaner.Callback { public void unsubscribe(GroupId g) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Unsubscribing from " + g); - boolean removed = false; + boolean removed; + Collection<ContactId> affected; contactLock.readLock().lock(); try { messageLock.writeLock().lock(); @@ -1339,6 +1344,7 @@ DatabaseCleaner.Callback { try { T txn = db.startTransaction(); try { + affected = db.getVisibility(txn, g); removed = db.removeSubscription(txn, g); db.commitTransaction(txn); } catch(DbException e) { @@ -1358,7 +1364,7 @@ DatabaseCleaner.Callback { contactLock.readLock().unlock(); } // Call the listeners outside the lock - if(removed) callListeners(new SubscriptionsUpdatedEvent()); + if(removed) callListeners(new SubscriptionsUpdatedEvent(affected)); } public void checkFreeSpaceAndClean() throws DbException { diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index 59c5fd7e052fda9d06d7b929582e8ed1da31c61e..15a28e3cf9540ee8d41a3d9cd28433bf06286812 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -652,10 +652,20 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void addSubscription(Connection txn, Group g) throws DbException { + public boolean addSubscription(Connection txn, Group g) throws DbException { PreparedStatement ps = null; + ResultSet rs = null; try { - String sql = "INSERT INTO subscriptions" + String sql = "SELECT NULL FROM subscriptions WHERE groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, g.getId().getBytes()); + rs = ps.executeQuery(); + boolean found = rs.next(); + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + if(found) return false; + sql = "INSERT INTO subscriptions" + " (groupId, groupName, groupKey, start)" + " VALUES (?, ?, ?, ?)"; ps = txn.prepareStatement(sql); @@ -666,6 +676,7 @@ abstract class JdbcDatabase implements Database<Connection> { int affected = ps.executeUpdate(); if(affected != 1) throw new DbStateException(); ps.close(); + return true; } catch(SQLException e) { tryToClose(ps); throw new DbException(e); diff --git a/components/net/sf/briar/transport/stream/StreamConnection.java b/components/net/sf/briar/transport/stream/StreamConnection.java index 8683d25fd12702d455dab22a0db589cb572b4443..419909437688c580aad2a5ed859c465be93d4936 100644 --- a/components/net/sf/briar/transport/stream/StreamConnection.java +++ b/components/net/sf/briar/transport/stream/StreamConnection.java @@ -102,9 +102,12 @@ abstract class StreamConnection implements DatabaseListener { writerFlags |= Flags.MESSAGES_ADDED; notifyAll(); } else if(e instanceof SubscriptionsUpdatedEvent) { - // FIXME: Check whether the change affected this contact - writerFlags |= Flags.SUBSCRIPTIONS_UPDATED; - notifyAll(); + Collection<ContactId> affected = + ((SubscriptionsUpdatedEvent) e).getAffectedContacts(); + if(affected.contains(contactId)) { + writerFlags |= Flags.SUBSCRIPTIONS_UPDATED; + notifyAll(); + } } else if(e instanceof TransportsUpdatedEvent) { writerFlags |= Flags.TRANSPORTS_UPDATED; notifyAll(); diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index f629c64512e6edf7f2306047985bff3cf342c1dc..15b1b74014e9772cac010bbaf9991ee91e6170fa 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -134,27 +134,26 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).getRemoteProperties(txn, transportId); will(returnValue(remoteProperties)); // subscribe(group) - oneOf(group).getId(); - will(returnValue(groupId)); - oneOf(database).containsSubscription(txn, groupId); - will(returnValue(false)); oneOf(database).addSubscription(txn, group); + will(returnValue(true)); oneOf(listener).eventOccurred(with(any( SubscriptionsUpdatedEvent.class))); // subscribe(group) again - oneOf(group).getId(); - will(returnValue(groupId)); - oneOf(database).containsSubscription(txn, groupId); - will(returnValue(true)); + oneOf(database).addSubscription(txn, group); + will(returnValue(false)); // getSubscriptions() oneOf(database).getSubscriptions(txn); will(returnValue(Collections.singletonList(groupId))); // unsubscribe(groupId) + oneOf(database).getVisibility(txn, groupId); + will(returnValue(Collections.<ContactId>emptySet())); oneOf(database).removeSubscription(txn, groupId); will(returnValue(true)); oneOf(listener).eventOccurred(with(any( SubscriptionsUpdatedEvent.class))); // unsubscribe(groupId) again + oneOf(database).getVisibility(txn, groupId); + will(returnValue(Collections.<ContactId>emptySet())); oneOf(database).removeSubscription(txn, groupId); will(returnValue(false)); // setConnectionWindow(contactId, 123, connectionWindow)