From d5d03192e30b407e9e97deec48601c2558977819 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Thu, 11 Aug 2011 12:56:21 +0100 Subject: [PATCH] Basic connection window persistence. --- api/net/sf/briar/api/db/ConnectionWindow.java | 12 ++ .../net/sf/briar/db/ConnectionWindowImpl.java | 30 ++++ components/net/sf/briar/db/Database.java | 15 ++ components/net/sf/briar/db/H2Database.java | 2 +- components/net/sf/briar/db/JdbcDatabase.java | 150 +++++++++++++----- test/net/sf/briar/db/H2DatabaseTest.java | 56 +++++++ 6 files changed, 223 insertions(+), 42 deletions(-) create mode 100644 api/net/sf/briar/api/db/ConnectionWindow.java create mode 100644 components/net/sf/briar/db/ConnectionWindowImpl.java diff --git a/api/net/sf/briar/api/db/ConnectionWindow.java b/api/net/sf/briar/api/db/ConnectionWindow.java new file mode 100644 index 0000000000..53ed0ed23e --- /dev/null +++ b/api/net/sf/briar/api/db/ConnectionWindow.java @@ -0,0 +1,12 @@ +package net.sf.briar.api.db; + +public interface ConnectionWindow { + + long getCentre(); + + void setCentre(long centre); + + int getBitmap(); + + void setBitmap(int bitmap); +} diff --git a/components/net/sf/briar/db/ConnectionWindowImpl.java b/components/net/sf/briar/db/ConnectionWindowImpl.java new file mode 100644 index 0000000000..6010a0fc36 --- /dev/null +++ b/components/net/sf/briar/db/ConnectionWindowImpl.java @@ -0,0 +1,30 @@ +package net.sf.briar.db; + +import net.sf.briar.api.db.ConnectionWindow; + +class ConnectionWindowImpl implements ConnectionWindow { + + private long centre; + private int bitmap; + + ConnectionWindowImpl(long centre, int bitmap) { + this.centre = centre; + this.bitmap = bitmap; + } + + public long getCentre() { + return centre; + } + + public void setCentre(long centre) { + this.centre = centre; + } + + public int getBitmap() { + return bitmap; + } + + public void setBitmap(int bitmap) { + this.bitmap = bitmap; + } +} diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 4f794672af..39042ef7ea 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -5,6 +5,7 @@ import java.util.Map; import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; +import net.sf.briar.api.db.ConnectionWindow; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.Status; import net.sf.briar.api.protocol.AuthorId; @@ -155,6 +156,13 @@ interface Database<T> { */ Collection<BatchId> getBatchesToAck(T txn, ContactId c) throws DbException; + /** + * Returns the connection reordering window for the given contact and + * transport. + */ + ConnectionWindow getConnectionWindow(T txn, ContactId c, int transport) + throws DbException; + /** * Returns the IDs of all contacts. * <p> @@ -371,6 +379,13 @@ interface Database<T> { */ void removeSubscription(T txn, GroupId g) throws DbException; + /** + * Sets the connection reordering window for the given contact and + * transport. + */ + void setConnectionWindow(T txn, ContactId c, int transport, + ConnectionWindow w) throws DbException; + /** * Sets the user's rating for the given author. * <p> diff --git a/components/net/sf/briar/db/H2Database.java b/components/net/sf/briar/db/H2Database.java index d2902e7e6b..3092556462 100644 --- a/components/net/sf/briar/db/H2Database.java +++ b/components/net/sf/briar/db/H2Database.java @@ -33,7 +33,7 @@ class H2Database extends JdbcDatabase { @Inject H2Database(File dir, @DatabasePassword Password password, long maxSize, GroupFactory groupFactory) { - super(groupFactory, "BINARY(32)", "BIGINT", "BINARY"); + super(groupFactory, "BINARY(32)", "BINARY"); home = new File(dir, "db"); this.password = password; url = "jdbc:h2:split:" + home.getPath() diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index 716146fd65..39bdf88b03 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -20,6 +20,7 @@ import java.util.logging.Logger; import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; +import net.sf.briar.api.db.ConnectionWindow; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.Status; @@ -43,7 +44,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " (groupId HASH NOT NULL," + " groupName VARCHAR NOT NULL," + " groupKey BINARY," - + " start TIMESTAMP NOT NULL," + + " start BIGINT NOT NULL," + " PRIMARY KEY (groupId))"; private static final String CREATE_MESSAGES = @@ -52,7 +53,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " parentId HASH NOT NULL," + " groupId HASH NOT NULL," + " authorId HASH NOT NULL," - + " timestamp TIMESTAMP NOT NULL," + + " timestamp BIGINT NOT NULL," + " size INT NOT NULL," + " raw BLOB NOT NULL," + " sendability INT NOT NULL," @@ -66,7 +67,7 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String INDEX_MESSAGES_BY_AUTHOR = "CREATE INDEX messagesByAuthor ON messages (authorId)"; - private static final String INDEX_MESSAGES_BY_TIMESTAMP = + private static final String INDEX_MESSAGES_BY_BIGINT = "CREATE INDEX messagesByTimestamp ON messages (timestamp)"; private static final String INDEX_MESSAGES_BY_SENDABILITY = @@ -75,8 +76,8 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String CREATE_CONTACTS = "CREATE TABLE contacts" + " (contactId INT NOT NULL," - + " subscriptionsTimestamp TIMESTAMP NOT NULL," - + " transportsTimestamp TIMESTAMP NOT NULL," + + " subscriptionsTimestamp BIGINT NOT NULL," + + " transportsTimestamp BIGINT NOT NULL," + " PRIMARY KEY (contactId))"; private static final String CREATE_VISIBILITIES = @@ -106,7 +107,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " groupId HASH NOT NULL," + " groupName VARCHAR NOT NULL," + " groupKey BINARY," - + " start TIMESTAMP NOT NULL," + + " start BIGINT NOT NULL," + " PRIMARY KEY (contactId, groupId)," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; @@ -115,7 +116,7 @@ abstract class JdbcDatabase implements Database<Connection> { "CREATE TABLE outstandingBatches" + " (batchId HASH NOT NULL," + " contactId INT NOT NULL," - + " timestamp TIMESTAMP NOT NULL," + + " timestamp BIGINT NOT NULL," + " passover INT NOT NULL," + " PRIMARY KEY (batchId, contactId)," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" @@ -184,11 +185,21 @@ abstract class JdbcDatabase implements Database<Connection> { + " value VARCHAR NOT NULL," + " PRIMARY KEY (transportName, key))"; + private static final String CREATE_CONNECTION_WINDOWS = + "CREATE TABLE connectionWindows" + + " (contactId INT NOT NULL," + + " transportId INT NOT NULL," + + " centre BIGINT NOT NULL," + + " bitmap INT NOT NULL," + + " PRIMARY KEY (contactId, transportId)," + + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + + " ON DELETE CASCADE)"; + private static final Logger LOG = Logger.getLogger(JdbcDatabase.class.getName()); // Different database libraries use different names for certain types - private final String hashType, timestampType, binaryType; + private final String hashType, binaryType; private final GroupFactory groupFactory; private final LinkedList<Connection> connections = new LinkedList<Connection>(); // Locking: self @@ -199,10 +210,9 @@ abstract class JdbcDatabase implements Database<Connection> { protected abstract Connection createConnection() throws SQLException; JdbcDatabase(GroupFactory groupFactory, String hashType, - String timestampType, String binaryType) { + String binaryType) { this.groupFactory = groupFactory; this.hashType = hashType; - this.timestampType = timestampType; this.binaryType = binaryType; } @@ -250,7 +260,7 @@ abstract class JdbcDatabase implements Database<Connection> { s.executeUpdate(insertTypeNames(CREATE_MESSAGES)); s.executeUpdate(INDEX_MESSAGES_BY_PARENT); s.executeUpdate(INDEX_MESSAGES_BY_AUTHOR); - s.executeUpdate(INDEX_MESSAGES_BY_TIMESTAMP); + s.executeUpdate(INDEX_MESSAGES_BY_BIGINT); s.executeUpdate(INDEX_MESSAGES_BY_SENDABILITY); s.executeUpdate(insertTypeNames(CREATE_CONTACTS)); s.executeUpdate(insertTypeNames(CREATE_VISIBILITIES)); @@ -267,6 +277,7 @@ abstract class JdbcDatabase implements Database<Connection> { s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORTS)); s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS)); s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_CONFIG)); + s.executeUpdate(insertTypeNames(CREATE_CONNECTION_WINDOWS)); s.close(); } catch(SQLException e) { tryToClose(s); @@ -276,7 +287,6 @@ abstract class JdbcDatabase implements Database<Connection> { private String insertTypeNames(String s) { s = s.replaceAll("HASH", hashType); - s = s.replaceAll("TIMESTAMP", timestampType); s = s.replaceAll("BINARY", binaryType); return s; } @@ -392,19 +402,17 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT COUNT(batchId) FROM batchesToAck" + String sql = "SELECT NULL FROM batchesToAck" + " WHERE batchId = ? AND contactId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, b.getBytes()); ps.setInt(2, c.getInt()); rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - int count = rs.getInt(1); - if(count > 1) throw new DbStateException(); + boolean found = rs.next(); if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - if(count == 1) return; + if(found) return; sql = "INSERT INTO batchesToAck (batchId, contactId)" + " VALUES (?, ?)"; ps = txn.prepareStatement(sql); @@ -589,18 +597,15 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT COUNT(contactId) FROM contacts" - + " WHERE contactId = ?"; + String sql = "SELECT NULL FROM contacts WHERE contactId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - int count = rs.getInt(1); - if(count > 1) throw new DbStateException(); + boolean found = rs.next(); if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - return count > 0; + return found; } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -613,18 +618,15 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT COUNT(messageId) FROM messages" - + " WHERE messageId = ?"; + String sql = "SELECT NULL FROM messages WHERE messageId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - int count = rs.getInt(1); - if(count > 1) throw new DbStateException(); + boolean found = rs.next(); if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - return count > 0; + return found; } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -637,18 +639,15 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT COUNT(groupId) FROM subscriptions" - + " WHERE groupId = ?"; + String sql = "SELECT NULL FROM subscriptions WHERE groupId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getBytes()); rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - int count = rs.getInt(1); - if(count > 1) throw new DbStateException(); + boolean found = rs.next(); if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - return count > 0; + return found; } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -731,7 +730,36 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public Collection<ContactId> getContacts(Connection txn) throws DbException { + public ConnectionWindow getConnectionWindow(Connection txn, ContactId c, + int transport) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT centre, bitmap FROM connectionWindows" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, transport); + rs = ps.executeQuery(); + long centre = 0L; + int bitmap = 0; + if(rs.next()) { + centre = rs.getLong(1); + bitmap = rs.getInt(2); + if(rs.next()) throw new DbStateException(); + } + rs.close(); + ps.close(); + return new ConnectionWindowImpl(centre, bitmap); + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + + public Collection<ContactId> getContacts(Connection txn) + throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -1496,6 +1524,48 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public void setConnectionWindow(Connection txn, ContactId c, int transport, + ConnectionWindow w) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT NULL FROM connectionWindows" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, transport); + rs = ps.executeQuery(); + if(rs.next()) { + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + sql = "UPDATE connectionWindows SET centre = ?, bitmap = ?" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + } else { + rs.close(); + ps.close(); + sql = "INSERT INTO connectionWindows" + + " (contactId, transportId, centre, bitmap)" + + " VALUES(?, ?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, transport); + ps.setLong(3, w.getCentre()); + ps.setInt(4, w.getBitmap()); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + } + ps.close(); + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public Rating setRating(Connection txn, AuthorId a, Rating r) throws DbException { PreparedStatement ps = null; @@ -1608,7 +1678,7 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT COUNT(messages.messageId) FROM messages" + String sql = "SELECT NULL FROM messages" + " JOIN contactSubscriptions" + " ON messages.groupId = contactSubscriptions.groupId" + " JOIN visibilities" @@ -1621,13 +1691,11 @@ abstract class JdbcDatabase implements Database<Connection> { ps.setInt(2, c.getInt()); ps.setInt(3, c.getInt()); rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - int count = rs.getInt(1); - if(count > 1) throw new DbStateException(); + boolean found = rs.next(); if(rs.next()) throw new DbStateException(); rs.close(); ps.close(); - if(count == 0) return false; + if(!found) return false; sql = "UPDATE statuses SET status = ?" + " WHERE messageId = ? AND contactId = ?"; ps = txn.prepareStatement(sql); diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index 1514006f02..95021acd29 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -17,6 +17,7 @@ import net.sf.briar.TestUtils; import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; import net.sf.briar.api.crypto.Password; +import net.sf.briar.api.db.ConnectionWindow; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.Status; import net.sf.briar.api.protocol.AuthorId; @@ -1204,6 +1205,61 @@ public class H2DatabaseTest extends TestCase { // Make the group invisible again db.setVisibility(txn, groupId, Collections.<ContactId>emptySet()); assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId)); + + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testGettingUnknownConnectionWindowReturnsDefault() + throws DbException { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a contact + assertEquals(contactId, db.addContact(txn, transports)); + + // Get the connection window for a new transport + ConnectionWindow w = db.getConnectionWindow(txn, contactId, 123); + + // The connection window should exist and be in the initial state + assertNotNull(w); + assertEquals(0L, w.getCentre()); + assertEquals(0, w.getBitmap()); + + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testConnectionWindow() throws DbException { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a contact + assertEquals(contactId, db.addContact(txn, transports)); + + // Get the connection window for a new transport + ConnectionWindow w = db.getConnectionWindow(txn, contactId, 123); + + // The connection window should exist and be in the initial state + assertNotNull(w); + assertEquals(0L, w.getCentre()); + assertEquals(0, w.getBitmap()); + + // Update the connection window and store it + w.setCentre(1); + w.setBitmap(0x00008000); + db.setConnectionWindow(txn, contactId, 123, w); + + // Check that the connection window was stored + w = db.getConnectionWindow(txn, contactId, 123); + assertNotNull(w); + assertEquals(1L, w.getCentre()); + assertEquals(0x00008000, w.getBitmap()); + + db.commitTransaction(txn); + db.close(); } @Test -- GitLab