diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index 4a5da87dd..ae0f59424 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -48,11 +48,11 @@ public interface DatabaseComponent { /** Waits for any open transactions to finish and closes the database. */ void close() throws DbException; - /** Adds a listener to be notified when new messages are available. */ - void addListener(MessageListener m); + /** Adds a listener to be notified when database events occur. */ + void addListener(DatabaseListener d); /** Removes a listener. */ - void removeListener(MessageListener m); + void removeListener(DatabaseListener d); /** * Adds a new contact to the database with the given transport details and diff --git a/api/net/sf/briar/api/db/DatabaseListener.java b/api/net/sf/briar/api/db/DatabaseListener.java new file mode 100644 index 000000000..3d52e8fbb --- /dev/null +++ b/api/net/sf/briar/api/db/DatabaseListener.java @@ -0,0 +1,13 @@ +package net.sf.briar.api.db; + +/** An interface for receiving notifications when database events occur. */ +public interface DatabaseListener { + + static enum Event { + MESSAGES_ADDED, + SUBSCRIPTIONS_UPDATED, + TRANSPORTS_UPDATED + }; + + void eventOccurred(Event e); +} diff --git a/api/net/sf/briar/api/db/MessageListener.java b/api/net/sf/briar/api/db/MessageListener.java deleted file mode 100644 index 667d79207..000000000 --- a/api/net/sf/briar/api/db/MessageListener.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.sf.briar.api.db; - -/** - * An interface for receiving notifications when the database may have new - * messages available. - */ -public interface MessageListener { - - void messagesAdded(); -} diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index a73c08bed..c7c10d577 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -10,7 +10,7 @@ import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; -import net.sf.briar.api.db.MessageListener; +import net.sf.briar.api.db.DatabaseListener; import net.sf.briar.api.db.Status; import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.Message; @@ -29,8 +29,8 @@ DatabaseCleaner.Callback { protected final Database db; protected final DatabaseCleaner cleaner; - private final List listeners = - new ArrayList(); // Locking: self + private final List listeners = + new ArrayList(); // Locking: self private final Object spaceLock = new Object(); private final Object writeLock = new Object(); private long bytesStoredSinceLastCheck = 0L; // Locking: spaceLock @@ -47,15 +47,15 @@ DatabaseCleaner.Callback { cleaner.startCleaning(); } - public void addListener(MessageListener m) { + public void addListener(DatabaseListener d) { synchronized(listeners) { - listeners.add(m); + listeners.add(d); } } - public void removeListener(MessageListener m) { + public void removeListener(DatabaseListener d) { synchronized(listeners) { - listeners.remove(m); + listeners.remove(d); } } @@ -80,13 +80,13 @@ DatabaseCleaner.Callback { } /** Notifies all MessageListeners that new messages may be available. */ - protected void callMessageListeners() { + protected void callListeners(DatabaseListener.Event e) { synchronized(listeners) { if(!listeners.isEmpty()) { - // Shuffle the listeners so we don't always send new messages + // Shuffle the listeners so we don't always send new packets // to contacts in the same order Collections.shuffle(listeners); - for(MessageListener m : listeners) m.messagesAdded(); + for(DatabaseListener d : listeners) d.eventOccurred(e); } } } diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java index 996d24e9a..5f698cbc4 100644 --- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java +++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java @@ -12,6 +12,7 @@ import java.util.logging.Logger; import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; +import net.sf.briar.api.db.DatabaseListener; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.protocol.Ack; @@ -193,7 +194,7 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { contactLock.readLock().unlock(); } // Call the listeners outside the lock - if(added) callMessageListeners(); + if(added) callListeners(DatabaseListener.Event.MESSAGES_ADDED); } public void findLostBatches(ContactId c) throws DbException { @@ -742,7 +743,7 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { contactLock.readLock().unlock(); } // Call the listeners outside the lock - if(anyAdded) callMessageListeners(); + if(anyAdded) callListeners(DatabaseListener.Event.MESSAGES_ADDED); } public void receiveOffer(ContactId c, Offer o, RequestWriter r) @@ -931,11 +932,15 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { public void subscribe(Group g) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g); + boolean added = false; subscriptionLock.writeLock().lock(); try { Txn txn = db.startTransaction(); try { - db.addSubscription(txn, g); + if(!db.containsSubscription(txn, g.getId())) { + db.addSubscription(txn, g); + added = true; + } db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -944,10 +949,13 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } finally { subscriptionLock.writeLock().unlock(); } + // Call the listeners outside the lock + if(added) callListeners(DatabaseListener.Event.SUBSCRIPTIONS_UPDATED); } public void unsubscribe(GroupId g) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Unsubscribing from " + g); + boolean removed = false; contactLock.readLock().lock(); try { messageLock.writeLock().lock(); @@ -958,7 +966,10 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { try { Txn txn = db.startTransaction(); try { - db.removeSubscription(txn, g); + if(db.containsSubscription(txn, g)) { + db.removeSubscription(txn, g); + removed = true; + } db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -976,5 +987,7 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } finally { contactLock.readLock().unlock(); } + // Call the listeners outside the lock + if(removed) callListeners(DatabaseListener.Event.SUBSCRIPTIONS_UPDATED); } } \ No newline at end of file diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java index 3c1aecd15..5bf42d643 100644 --- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java +++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java @@ -11,6 +11,7 @@ import java.util.logging.Logger; import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; +import net.sf.briar.api.db.DatabaseListener; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.protocol.Ack; @@ -146,7 +147,7 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } // Call the listeners outside the lock - if(added) callMessageListeners(); + if(added) callListeners(DatabaseListener.Event.MESSAGES_ADDED); } public void findLostBatches(ContactId c) throws DbException { @@ -550,7 +551,7 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } // Call the listeners outside the lock - if(anyAdded) callMessageListeners(); + if(anyAdded) callListeners(DatabaseListener.Event.MESSAGES_ADDED); } public void receiveOffer(ContactId c, Offer o, RequestWriter r) @@ -691,27 +692,37 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { public void subscribe(Group g) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g); + boolean added = false; synchronized(subscriptionLock) { Txn txn = db.startTransaction(); try { - db.addSubscription(txn, g); + if(!db.containsSubscription(txn, g.getId())) { + db.addSubscription(txn, g); + added = true; + } db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); throw e; } } + // Call the listeners outside the lock + if(added) callListeners(DatabaseListener.Event.SUBSCRIPTIONS_UPDATED); } public void unsubscribe(GroupId g) throws DbException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Unsubscribing from " + g); + boolean removed = false; synchronized(contactLock) { synchronized(messageLock) { synchronized(messageStatusLock) { synchronized(subscriptionLock) { Txn txn = db.startTransaction(); try { - db.removeSubscription(txn, g); + if(db.containsSubscription(txn, g)) { + db.removeSubscription(txn, g); + removed = true; + } db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -721,5 +732,7 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } } + // Call the listeners outside the lock + if(removed) callListeners(DatabaseListener.Event.SUBSCRIPTIONS_UPDATED); } } diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index 9489fa74d..4deefed87 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -12,7 +12,7 @@ import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; -import net.sf.briar.api.db.MessageListener; +import net.sf.briar.api.db.DatabaseListener; import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.db.Status; import net.sf.briar.api.protocol.Ack; @@ -81,7 +81,7 @@ public abstract class DatabaseComponentTest extends TestCase { final Database database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final Group group = context.mock(Group.class); - final MessageListener listener = context.mock(MessageListener.class); + final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -104,12 +104,22 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).getTransports(txn, contactId); will(returnValue(transports)); // subscribe(group) + oneOf(group).getId(); + will(returnValue(groupId)); + oneOf(database).containsSubscription(txn, groupId); + will(returnValue(false)); oneOf(database).addSubscription(txn, group); + oneOf(listener).eventOccurred( + DatabaseListener.Event.SUBSCRIPTIONS_UPDATED); // getSubscriptions() oneOf(database).getSubscriptions(txn); will(returnValue(Collections.singletonList(groupId))); // unsubscribe(groupId) + oneOf(database).containsSubscription(txn, groupId); + will(returnValue(true)); oneOf(database).removeSubscription(txn, groupId); + oneOf(listener).eventOccurred( + DatabaseListener.Event.SUBSCRIPTIONS_UPDATED); // removeContact(contactId) oneOf(database).removeContact(txn, contactId); // close() @@ -1035,7 +1045,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); - final MessageListener listener = context.mock(MessageListener.class); + final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ // addLocallyGeneratedMessage(message) oneOf(database).startTransaction(); @@ -1054,7 +1064,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).setSendability(txn, messageId, 0); oneOf(database).commitTransaction(txn); // The message was added, so the listener should be called - oneOf(listener).messagesAdded(); + oneOf(listener).eventOccurred( + DatabaseListener.Event.MESSAGES_ADDED); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner); @@ -1070,7 +1081,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); - final MessageListener listener = context.mock(MessageListener.class); + final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ // addLocallyGeneratedMessage(message) oneOf(database).startTransaction();