diff --git a/briar-api/src/net/sf/briar/api/Contact.java b/briar-api/src/net/sf/briar/api/Contact.java index cfe5ddf14573063efabc91f4d913a6ce90a6fffc..bbac1dc99bdebb7d720cf4fd63586e7adf72321a 100644 --- a/briar-api/src/net/sf/briar/api/Contact.java +++ b/briar-api/src/net/sf/briar/api/Contact.java @@ -4,10 +4,12 @@ public class Contact { private final ContactId id; private final String name; + private final long lastConnected; - public Contact(ContactId id, String name) { + public Contact(ContactId id, String name, long lastConnected) { this.id = id; this.name = name; + this.lastConnected = lastConnected; } public ContactId getId() { @@ -18,6 +20,10 @@ public class Contact { return name; } + public long getLastConnected() { + return lastConnected; + } + @Override public int hashCode() { return id.hashCode(); diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java index 34fe4ab8ccd3f57652d9ce549921fb733617787d..43a91fa56b4de64d43c89ea211f321685319f095 100644 --- a/briar-core/src/net/sf/briar/db/Database.java +++ b/briar-core/src/net/sf/briar/db/Database.java @@ -81,7 +81,8 @@ interface Database<T> { * Adds a contact with the given name to the database and returns an ID for * the contact. * <p> - * Locking: contact write, subscription write. + * Locking: contact write, retention write, subscription write, transport + * write, window write. */ ContactId addContact(T txn, String name) throws DbException; @@ -210,7 +211,7 @@ interface Database<T> { /** * Returns all contacts. * <p> - * Locking: contact read. + * Locking: contact read, window read. */ Collection<Contact> getContacts(T txn) throws DbException; @@ -239,6 +240,14 @@ interface Database<T> { */ MessageId getGroupMessageParent(T txn, MessageId m) throws DbException; + /** + * Returns the time at which a connection to the given contact was last + * opened or closed. + * <p> + * Locking: contact read, window read. + */ + long getLastConnected(T txn, ContactId c) throws DbException; + /** * Returns the local transport properties for the given transport. * <p> @@ -526,8 +535,8 @@ interface Database<T> { /** * Removes a contact (and all associated state) from the database. * <p> - * Locking: contact write, message write, subscription write, - * transport write, window write. + * Locking: contact write, message write, retention write, + * subscription write, transport write, window write. */ void removeContact(T txn, ContactId c) throws DbException; @@ -587,6 +596,14 @@ interface Database<T> { void setConnectionWindow(T txn, ContactId c, TransportId t, long period, long centre, byte[] bitmap) throws DbException; + /** + * Sets the time at which a connection to the given contact was last + * opened or closed. + * <p> + * Locking: contact read, window write. + */ + void setLastConnected(T txn, ContactId c, long now) throws DbException; + /** * Sets the user's rating for the given author. * <p> diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index 789b37bd563288dd61b4a723e901cea6075f1d5c..c4455971a9c5b4da12764d185b100f1ecd26209a 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -176,18 +176,33 @@ DatabaseCleaner.Callback { ContactId c; contactLock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + retentionLock.writeLock().lock(); try { - T txn = db.startTransaction(); + subscriptionLock.writeLock().lock(); try { - c = db.addContact(txn, name); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + transportLock.writeLock().lock(); + try { + windowLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + c = db.addContact(txn, name); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + subscriptionLock.writeLock().unlock(); } } finally { - subscriptionLock.writeLock().unlock(); + retentionLock.writeLock().unlock(); } } finally { contactLock.writeLock().unlock(); @@ -805,14 +820,19 @@ DatabaseCleaner.Callback { public Collection<Contact> getContacts() throws DbException { contactLock.readLock().lock(); try { - T txn = db.startTransaction(); + windowLock.readLock().lock(); try { - Collection<Contact> contacts = db.getContacts(txn); - db.commitTransaction(txn); - return contacts; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + T txn = db.startTransaction(); + try { + Collection<Contact> contacts = db.getContacts(txn); + db.commitTransaction(txn); + return contacts; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.readLock().unlock(); } } finally { contactLock.readLock().unlock(); @@ -1140,6 +1160,7 @@ DatabaseCleaner.Callback { throw new NoSuchTransportException(); long counter = db.incrementConnectionCounter(txn, c, t, period); + db.setLastConnected(txn, c, clock.currentTimeMillis()); db.commitTransaction(txn); return counter; } catch(DbException e) { @@ -1467,30 +1488,35 @@ DatabaseCleaner.Callback { try { messageLock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + retentionLock.writeLock().lock(); try { - transportLock.writeLock().lock(); + subscriptionLock.writeLock().lock(); try { - windowLock.writeLock().lock(); + transportLock.writeLock().lock(); try { - T txn = db.startTransaction(); + windowLock.writeLock().lock(); try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.removeContact(txn, c); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + T txn = db.startTransaction(); + try { + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.removeContact(txn, c); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.writeLock().unlock(); } } finally { - windowLock.writeLock().unlock(); + transportLock.writeLock().unlock(); } } finally { - transportLock.writeLock().unlock(); + subscriptionLock.writeLock().unlock(); } } finally { - subscriptionLock.writeLock().unlock(); + retentionLock.writeLock().unlock(); } } finally { messageLock.writeLock().unlock(); @@ -1536,6 +1562,7 @@ DatabaseCleaner.Callback { throw new NoSuchTransportException(); db.setConnectionWindow(txn, c, t, period, centre, bitmap); + db.setLastConnected(txn, c, clock.currentTimeMillis()); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java index dabf3df2b7988fd7aa7edb631ae9120976da62ec..f907d7a37bd483aaf4c2b6e0c8d1a513cf8c3bf1 100644 --- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java +++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java @@ -305,6 +305,16 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES transports (transportId)" + " ON DELETE CASCADE)"; + // Locking: contact read, window + private static final String CREATE_CONNECTION_TIMES = + "CREATE TABLE connectionTimes" + + " (contactId INT NOT NULL," + + " lastConnected BIGINT NOT NULL," + + " PRIMARY KEY (contactId)," + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE)"; + private static final Logger LOG = Logger.getLogger(JdbcDatabase.class.getName()); @@ -383,6 +393,7 @@ abstract class JdbcDatabase implements Database<Connection> { s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_VERSIONS)); s.executeUpdate(insertTypeNames(CREATE_ENDPOINTS)); s.executeUpdate(insertTypeNames(CREATE_SECRETS)); + s.executeUpdate(insertTypeNames(CREATE_CONNECTION_TIMES)); s.close(); } catch(SQLException e) { tryToClose(s); @@ -519,6 +530,15 @@ abstract class JdbcDatabase implements Database<Connection> { if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); + // Create a connection time row + sql = "INSERT INTO connectionTimes (contactId, lastConnected)" + + " VALUES (?, ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setLong(2, clock.currentTimeMillis()); + affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + ps.close(); // Create a retention version row sql = "INSERT INTO retentionVersions (contactId, retention," + " localVersion, localAcked, remoteVersion, remoteAcked," @@ -1032,14 +1052,18 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT contactId, name FROM contacts"; + String sql = "SELECT contactId, name, lastConnected" + + " FROM contacts AS c" + + " JOIN connectionTimes AS ct" + + " ON c.contactId = ct.contactId"; ps = txn.prepareStatement(sql); rs = ps.executeQuery(); List<Contact> contacts = new ArrayList<Contact>(); while(rs.next()) { ContactId id = new ContactId(rs.getInt(1)); String name = rs.getString(2); - contacts.add(new Contact(id, name)); + long lastConnected = rs.getLong(3); + contacts.add(new Contact(id, name, lastConnected)); } rs.close(); ps.close(); @@ -1116,6 +1140,29 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public long getLastConnected(Connection txn, ContactId c) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT lastConnected FROM connectionTimes" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + rs = ps.executeQuery(); + if(!rs.next()) throw new DbStateException(); + long lastConnected = rs.getLong(1); + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + return lastConnected; + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public TransportProperties getLocalProperties(Connection txn, TransportId t) throws DbException { PreparedStatement ps = null; @@ -2508,6 +2555,24 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public void setLastConnected(Connection txn, ContactId c, long now) + throws DbException { + PreparedStatement ps = null; + try { + String sql = "UPDATE connectionTimes SET lastConnected = ?" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setLong(1, now); + ps.setInt(2, c.getInt()); + int affected = ps.executeUpdate(); + if(affected < 1) throw new DbStateException(); + ps.close(); + } catch(SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + public Rating setRating(Connection txn, AuthorId a, Rating r) throws DbException { PreparedStatement ps = null; diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java index f035ed314ffa5faa85a79be7b1a6586c0ba28ea9..db67d27c52133d62df2fb83d19809eeb1a6457f1 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java @@ -81,7 +81,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { timestamp = System.currentTimeMillis(); size = 1234; raw = new byte[size]; - contact = new Contact(contactId, contactName); + contact = new Contact(contactId, contactName, timestamp); message = new TestMessage(messageId, null, groupId, authorId, subject, timestamp, raw); privateMessage = new TestMessage(messageId, null, null, null, subject,