diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index f646a9e4120ffbdfd4c86532c6cc3dac122797ff..ca142d39df885c9c6a97f15e9a3e87a4010626c3 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -32,8 +32,8 @@ public interface DatabaseComponent { /** Waits for any open transactions to finish and closes the database. */ void close() throws DbException; - /** Adds a new contact to the database. */ - void addContact(ContactId c) throws DbException; + /** Adds a new contact to the database and returns an ID for the contact. */ + ContactId addContact() throws DbException; /** Adds a locally generated message to the database. */ void addLocallyGeneratedMessage(Message m) throws DbException; @@ -44,6 +44,9 @@ public interface DatabaseComponent { */ void generateBundle(ContactId c, Bundle b) throws DbException; + /** Returns the IDs of all contacts. */ + Set<ContactId> getContacts() throws DbException; + /** Returns the user's rating for the given author. */ Rating getRating(AuthorId a) throws DbException; diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 9246874b05aa801263482f43c8770a83d32c223f..a0e611a8ef3a784c18e46c4d09f6069910b6460b 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -75,19 +75,19 @@ interface Database<T> { void addBatchToAck(T txn, ContactId c, BatchId b) throws DbException; /** - * Returns false if the given message is already in the database. Otherwise - * stores the message and returns true. + * Adds a new contact to the database and returns an ID for the contact. * <p> - * Locking: messages write. + * Locking: contacts write, messageStatuses write. */ - boolean addMessage(T txn, Message m) throws DbException; + ContactId addContact(T txn) throws DbException; /** - * Adds a new contact to the database. + * Returns false if the given message is already in the database. Otherwise + * stores the message and returns true. * <p> - * Locking: contacts write, messageStatuses write. + * Locking: messages write. */ - void addContact(T txn, ContactId c) throws DbException; + boolean addMessage(T txn, Message m) throws DbException; /** * Records a sent batch as needing to be acknowledged. diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index 1270687995947f2b6d2e9e1b46405f0b170b14cb..e0440e5ce168334869546ed8170123cc828f4382 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -126,7 +126,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " (bundleId XXXX NOT NULL," + " contactId INT NOT NULL," + " timestamp BIGINT NOT NULL," - + " PRIMARY KEY (bundleId)," + + " PRIMARY KEY (bundleId, contactId)," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; @@ -363,10 +363,24 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void addContact(Connection txn, ContactId c) throws DbException { + public ContactId addContact(Connection txn) throws DbException { PreparedStatement ps = null; + ResultSet rs = null; try { - String sql = "INSERT INTO contacts" + // Get the highest existing contact ID + String sql = "SELECT contactId FROM contacts" + + " ORDER BY contactId DESC LIMIT ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, 1); + rs = ps.executeQuery(); + int nextId = rs.next() ? rs.getInt(1) + 1 : 1; + ContactId c = new ContactId(nextId); + boolean more = rs.next(); + assert !more; + rs.close(); + ps.close(); + // Create a new contact row + sql = "INSERT INTO contacts" + " (contactId, lastBundleReceived)" + " VALUES (?, ?)"; ps = txn.prepareStatement(sql); @@ -375,6 +389,7 @@ abstract class JdbcDatabase implements Database<Connection> { int rowsAffected = ps.executeUpdate(); assert rowsAffected == 1; ps.close(); + // Create a dummy received bundle row for BundleId.NONE sql = "INSERT INTO receivedBundles" + " (bundleId, contactId, timestamp)" + " VALUES (?, ?, ?)"; @@ -385,6 +400,7 @@ abstract class JdbcDatabase implements Database<Connection> { rowsAffected = ps.executeUpdate(); assert rowsAffected == 1; ps.close(); + return c; } catch(SQLException e) { tryToClose(ps); tryToClose(txn); diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java index a61cfaa6cb76d65a4c769fc3cfc1842848f82a92..eb984b59aac4be2a58367a27ca0f9ebe9ef731af 100644 --- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java +++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java @@ -83,16 +83,19 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } - public void addContact(ContactId c) throws DbException { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact " + c); + public ContactId addContact() throws DbException { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); contactLock.writeLock().lock(); try { messageStatusLock.writeLock().lock(); try { Txn txn = db.startTransaction(); try { - db.addContact(txn, c); + ContactId c = db.addContact(txn); db.commitTransaction(txn); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added contact " + c); + return c; } catch(DbException e) { db.abortTransaction(txn); throw e; @@ -307,6 +310,28 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } + public Set<ContactId> getContacts() throws DbException { + contactLock.readLock().lock(); + try { + messageStatusLock.readLock().lock(); + try { + Txn txn = db.startTransaction(); + try { + Set<ContactId> contacts = db.getContacts(txn); + db.commitTransaction(txn); + return contacts; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + public Rating getRating(AuthorId a) throws DbException { ratingLock.readLock().lock(); try { @@ -329,8 +354,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { try { Txn txn = db.startTransaction(); try { - HashSet<GroupId> subs = new HashSet<GroupId>(); - for(GroupId g : db.getSubscriptions(txn)) subs.add(g); + Set<GroupId> subs = db.getSubscriptions(txn); db.commitTransaction(txn); return subs; } catch(DbException e) { diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java index bfe67f0a59d71aee0dc78901a38e112710526214..ba57834fa2e4a65074517c096e93e5af87d8fc56 100644 --- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java +++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java @@ -62,14 +62,17 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } - public void addContact(ContactId c) throws DbException { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact " + c); + public ContactId addContact() throws DbException { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); synchronized(contactLock) { synchronized(messageStatusLock) { Txn txn = db.startTransaction(); try { - db.addContact(txn, c); + ContactId c = db.addContact(txn); db.commitTransaction(txn); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added contact " + c); + return c; } catch(DbException e) { db.abortTransaction(txn); throw e; @@ -224,6 +227,22 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } + public Set<ContactId> getContacts() throws DbException { + synchronized(contactLock) { + synchronized(messageStatusLock) { + Txn txn = db.startTransaction(); + try { + Set<ContactId> contacts = db.getContacts(txn); + db.commitTransaction(txn); + return contacts; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } + } + } + public Rating getRating(AuthorId a) throws DbException { synchronized(ratingLock) { Txn txn = db.startTransaction(); @@ -242,8 +261,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { synchronized(subscriptionLock) { Txn txn = db.startTransaction(); try { - HashSet<GroupId> subs = new HashSet<GroupId>(); - for(GroupId g : db.getSubscriptions(txn)) subs.add(g); + Set<GroupId> subs = db.getSubscriptions(txn); db.commitTransaction(txn); return subs; } catch(DbException e) { diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index a432ab88e3901d28eccd1354e0821151765c2f52..dbab966d3f0013cd023fd57a6f0e433ad2a51561 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -83,7 +83,11 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).getRating(txn, authorId); will(returnValue(Rating.UNRATED)); // addContact(contactId) - oneOf(database).addContact(txn, contactId); + oneOf(database).addContact(txn); + will(returnValue(contactId)); + // getContacts() + oneOf(database).getContacts(txn); + will(returnValue(Collections.singleton(contactId))); // subscribe(groupId) oneOf(database).addSubscription(txn, groupId); // getSubscriptions() @@ -102,7 +106,8 @@ public abstract class DatabaseComponentTest extends TestCase { db.open(false); assertEquals(Rating.UNRATED, db.getRating(authorId)); - db.addContact(contactId); + assertEquals(contactId, db.addContact()); + assertEquals(Collections.singleton(contactId), db.getContacts()); db.subscribe(groupId); assertEquals(Collections.singleton(groupId), db.getSubscriptions()); db.unsubscribe(groupId); diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index 3c18e2314bbf37a756298d86f3864d4c93199118..7827da8524f1964b883ab48e38e542d91ff68a68 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -55,7 +55,7 @@ public class H2DatabaseTest extends TestCase { super(); authorId = new AuthorId(TestUtils.getRandomId()); batchId = new BatchId(TestUtils.getRandomId()); - contactId = new ContactId(123); + contactId = new ContactId(1); groupId = new GroupId(TestUtils.getRandomId()); messageId = new MessageId(TestUtils.getRandomId()); timestamp = System.currentTimeMillis(); @@ -80,7 +80,7 @@ public class H2DatabaseTest extends TestCase { // Store some records Connection txn = db.startTransaction(); assertFalse(db.containsContact(txn, contactId)); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); assertTrue(db.containsContact(txn, contactId)); assertFalse(db.containsSubscription(txn, groupId)); db.addSubscription(txn, groupId); @@ -124,6 +124,39 @@ public class H2DatabaseTest extends TestCase { db.close(); } + @Test + public void testContactIdsIncrease() throws DbException { + ContactId contactId1 = new ContactId(2); + ContactId contactId2 = new ContactId(3); + ContactId contactId3 = new ContactId(4); + MessageFactory messageFactory = new TestMessageFactory(); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Create three contacts + Connection txn = db.startTransaction(); + assertFalse(db.containsContact(txn, contactId)); + assertEquals(contactId, db.addContact(txn)); + assertTrue(db.containsContact(txn, contactId)); + assertFalse(db.containsContact(txn, contactId1)); + assertEquals(contactId1, db.addContact(txn)); + assertTrue(db.containsContact(txn, contactId1)); + assertFalse(db.containsContact(txn, contactId2)); + assertEquals(contactId2, db.addContact(txn)); + assertTrue(db.containsContact(txn, contactId2)); + /* + // Delete one of the contacts + db.removeContact(txn, contactId1); + assertFalse(db.containsContact(txn, contactId1)); + */ + // Add another contact - a new ID should be created + assertFalse(db.containsContact(txn, contactId3)); + assertEquals(contactId3, db.addContact(txn)); + assertTrue(db.containsContact(txn, contactId3)); + db.commitTransaction(txn); + db.close(); + } + @Test public void testRatings() throws DbException { Mockery context = new Mockery(); @@ -176,7 +209,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact, subscribe to a group and store a message Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); db.addSubscription(txn, groupId); db.addSubscription(txn, contactId, groupId); db.addMessage(txn, message); @@ -219,7 +252,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact, subscribe to a group and store a message Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); db.addSubscription(txn, groupId); db.addSubscription(txn, contactId, groupId); db.addMessage(txn, message); @@ -268,7 +301,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact, subscribe to a group and store a message Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); db.addSubscription(txn, groupId); db.addMessage(txn, message); db.setSendability(txn, messageId, 1); @@ -310,7 +343,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact, subscribe to a group and store a message Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); db.addSubscription(txn, groupId); db.addSubscription(txn, contactId, groupId); db.addMessage(txn, message); @@ -346,7 +379,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact and some batches to ack Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); db.addBatchToAck(txn, contactId, batchId); db.addBatchToAck(txn, contactId, batchId1); db.commitTransaction(txn); @@ -378,7 +411,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact, subscribe to a group and store a message Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); db.addSubscription(txn, groupId); db.addSubscription(txn, contactId, groupId); db.addMessage(txn, message); @@ -422,7 +455,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact, subscribe to a group and store a message Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); db.addSubscription(txn, groupId); db.addSubscription(txn, contactId, groupId); db.addMessage(txn, message); @@ -476,7 +509,7 @@ public class H2DatabaseTest extends TestCase { Database<Connection> db = open(false, messageFactory); // Add a contact Connection txn = db.startTransaction(); - db.addContact(txn, contactId); + assertEquals(contactId, db.addContact(txn)); // Add an oustanding batch (associated with BundleId.NONE) db.addOutstandingBatch(txn, contactId, batchId, empty); // Receive a bundle