diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java index 08b420d59c4728ec7e827b074c4acd306f8eca3b..c7a9a51c453342f8fee536f46ee22aee868c496d 100644 --- a/briar-core/src/net/sf/briar/db/Database.java +++ b/briar-core/src/net/sf/briar/db/Database.java @@ -256,15 +256,6 @@ interface Database<T> { */ Collection<Transport> getLocalTransports(T txn) throws DbException; - /** - * Returns the IDs of any messages sent to the given contact that should - * now be considered lost. - * <p> - * Locking: contact read, message read, messageStatus read. - */ - Collection<MessageId> getLostMessages(T txn, ContactId c) - throws DbException; - /** * Returns the message identified by the given ID, in serialised form. * <p> @@ -307,23 +298,14 @@ interface Database<T> { throws DbException; /** - * Returns the IDs of any messages received from the given contact that - * need to be acknowledged. + * Returns the IDs of some messages received from the given contact that + * need to be acknowledged, up to the given number of messages. * <p> * Locking: contact read, messageStatus read. */ Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages) throws DbException; - /** - * Returns the number of children of the message identified by the given - * ID that are present in the database and have sendability scores greater - * than zero. - * <p> - * Locking: message read. - */ - int getNumberOfSendableChildren(T txn, MessageId m) throws DbException; - /** * Returns the IDs of some messages that are eligible to be sent to the * given contact, up to the given number of messages. @@ -331,9 +313,18 @@ interface Database<T> { * Locking: contact read, message read, messageStatus read, * subscription read. */ - Collection<MessageId> getOfferableMessages(T txn, ContactId c, + Collection<MessageId> getMessagesToOffer(T txn, ContactId c, int maxMessages) throws DbException; + /** + * Returns the number of children of the message identified by the given + * ID that are present in the database and have sendability scores greater + * than zero. + * <p> + * Locking: message read. + */ + int getNumberOfSendableChildren(T txn, MessageId m) throws DbException; + /** * Returns the IDs of the oldest messages in the database, with a total * size less than or equal to the given size. @@ -354,7 +345,7 @@ interface Database<T> { * <p> * Locking: message read, messageFlag read. */ - boolean getRead(T txn, MessageId m) throws DbException; + boolean getReadFlag(T txn, MessageId m) throws DbException; /** * Returns all remote properties for the given transport. @@ -394,7 +385,7 @@ interface Database<T> { * <p> * Locking: message read, messageFlag read. */ - boolean getStarred(T txn, MessageId m) throws DbException; + boolean getStarredFlag(T txn, MessageId m) throws DbException; /** * Returns the groups to which the user subscribes. @@ -500,8 +491,8 @@ interface Database<T> { * <p> * Locking: contact read, message read, messageStatus write. */ - void removeAckedMessages(T txn, ContactId c, Collection<MessageId> acked) - throws DbException; + void removeOutstandingMessages(T txn, ContactId c, + Collection<MessageId> acked) throws DbException; /** * Marks the given messages received from the given contact as having been @@ -520,16 +511,6 @@ interface Database<T> { */ void removeContact(T txn, ContactId c) throws DbException; - /** - * Removes outstanding messages that have been lost. Any messages that are - * still considered outstanding (Status.SENT) with respect to the given - * contact are now considered unsent (Status.NEW). - * <p> - * Locking: contact read, message read, messageStatus write. - */ - void removeLostMessages(T txn, ContactId c, Collection<MessageId> lost) - throws DbException; - /** * Removes a message (and all associated state) from the database. * <p> diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index 2fa615d6018353a287bbb0c700b49ca696af9028..d9333bcd2bf4e0fb31d204539ba61f87530acc5a 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -612,7 +612,7 @@ DatabaseCleaner.Callback { try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); - offered = db.getOfferableMessages(txn, c, maxMessages); + offered = db.getMessagesToOffer(txn, c, maxMessages); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -1048,12 +1048,7 @@ DatabaseCleaner.Callback { try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); - // Mark all acked messages as seen - db.removeAckedMessages(txn, c, a.getMessageIds()); - // Find any lost messages that need to be retransmitted - // FIXME: Merge these methods - Collection<MessageId> lost = db.getLostMessages(txn, c); - if(!lost.isEmpty()) db.removeLostMessages(txn, c, lost); + db.removeOutstandingMessages(txn, c, a.getMessageIds()); 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 10e29501bdac7c6088b84964491b18ae821fc84e..81c1f63375e3cfca9540fcd38f749792a3359ed8 100644 --- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java +++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java @@ -64,9 +64,9 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String CREATE_MESSAGES = "CREATE TABLE messages" + " (messageId HASH NOT NULL," - + " parentId HASH," // Null for the first message in a thread + + " parentId HASH," // Null for the first msg in a thread + " groupId HASH," // Null for private messages - + " authorId HASH," // Null for private or anonymous messages + + " authorId HASH," // Null for private or anonymous msgs + " subject VARCHAR NOT NULL," + " timestamp BIGINT NOT NULL," + " length INT NOT NULL," @@ -76,7 +76,8 @@ abstract class JdbcDatabase implements Database<Connection> { + " sendability INT," // Null for private messages + " contactId INT," // Null for group messages + " PRIMARY KEY (messageId)," - + " FOREIGN KEY (groupId) REFERENCES subscriptions (groupId)" + + " FOREIGN KEY (groupId)" + + " REFERENCES subscriptions (groupId)" + " ON DELETE CASCADE," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; @@ -101,7 +102,8 @@ abstract class JdbcDatabase implements Database<Connection> { + " deleted BIGINT NOT NULL," + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " ON DELETE CASCADE," - + " FOREIGN KEY (groupId) REFERENCES subscriptions (groupId)" + + " FOREIGN KEY (groupId)" + + " REFERENCES subscriptions (groupId)" + " ON DELETE CASCADE)"; private static final String INDEX_VISIBILITIES_BY_GROUP = @@ -537,16 +539,29 @@ abstract class JdbcDatabase implements Database<Connection> { public void addMessageToAck(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; + ResultSet rs = null; try { - String sql = "INSERT INTO messagesToAck (messageId, contactId)" + String sql = "SELECT NULL FROM messagesToAck" + + " WHERE messageId = ? AND contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, m.getBytes()); + ps.setInt(2, c.getInt()); + rs = ps.executeQuery(); + boolean found = rs.next(); + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + if(found) return; + sql = "INSERT INTO messagesToAck (messageId, contactId)" + " VALUES (?, ?)"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); ps.setInt(2, c.getInt()); int affected = ps.executeUpdate(); - if(affected > 1) throw new DbStateException(); + if(affected != 1) throw new DbStateException(); ps.close(); } catch(SQLException e) { + tryToClose(rs); tryToClose(ps); throw new DbException(e); } @@ -1117,12 +1132,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public Collection<MessageId> getLostMessages(Connection txn, ContactId c) - throws DbException { - // FIXME: Retransmission - return Collections.emptyList(); - } - public byte[] getMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; ResultSet rs = null; @@ -1319,6 +1328,64 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public Collection<MessageId> getMessagesToOffer(Connection txn, + ContactId c, int maxMessages) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + // Do we have any sendable private messages? + String sql = "SELECT m.messageId FROM messages AS m" + + " JOIN statuses AS s" + + " ON m.messageId = s.messageId" + + " WHERE m.contactId = ? AND status = ?" + + " ORDER BY timestamp" + + " LIMIT ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setShort(2, (short) Status.NEW.ordinal()); + ps.setInt(3, maxMessages); + rs = ps.executeQuery(); + List<MessageId> ids = new ArrayList<MessageId>(); + while(rs.next()) ids.add(new MessageId(rs.getBytes(2))); + rs.close(); + ps.close(); + if(ids.size() == maxMessages) + return Collections.unmodifiableList(ids); + // Do we have any sendable group messages? + sql = "SELECT m.messageId FROM messages AS m" + + " JOIN contactSubscriptions AS cs" + + " ON m.groupId = cs.groupId" + + " JOIN visibilities AS v" + + " ON m.groupId = v.groupId" + + " AND cs.contactId = v.contactId" + + " JOIN statuses AS s" + + " ON m.messageId = s.messageId" + + " AND cs.contactId = s.contactId" + + " JOIN subscriptionTimes AS st" + + " ON cs.contactId = st.contactId" + + " WHERE cs.contactId = ?" + + " AND timestamp >= start" + + " AND timestamp >= expiry" + + " AND status = ?" + + " AND sendability > ZERO()" + + " ORDER BY timestamp" + + " LIMIT ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setShort(2, (short) Status.NEW.ordinal()); + ps.setInt(3, maxMessages - ids.size()); + rs = ps.executeQuery(); + while(rs.next()) ids.add(new MessageId(rs.getBytes(2))); + rs.close(); + ps.close(); + return Collections.unmodifiableList(ids); + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public int getNumberOfSendableChildren(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1403,7 +1470,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public boolean getRead(Connection txn, MessageId m) throws DbException { + public boolean getReadFlag(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -1519,64 +1586,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public Collection<MessageId> getOfferableMessages(Connection txn, - ContactId c, int maxMessages) throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - // Do we have any sendable private messages? - String sql = "SELECT m.messageId FROM messages AS m" - + " JOIN statuses AS s" - + " ON m.messageId = s.messageId" - + " WHERE m.contactId = ? AND status = ?" - + " ORDER BY timestamp" - + " LIMIT ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setShort(2, (short) Status.NEW.ordinal()); - ps.setInt(3, maxMessages); - rs = ps.executeQuery(); - List<MessageId> ids = new ArrayList<MessageId>(); - while(rs.next()) ids.add(new MessageId(rs.getBytes(2))); - rs.close(); - ps.close(); - if(ids.size() == maxMessages) - return Collections.unmodifiableList(ids); - // Do we have any sendable group messages? - sql = "SELECT m.messageId FROM messages AS m" - + " JOIN contactSubscriptions AS cs" - + " ON m.groupId = cs.groupId" - + " JOIN visibilities AS v" - + " ON m.groupId = v.groupId" - + " AND cs.contactId = v.contactId" - + " JOIN statuses AS s" - + " ON m.messageId = s.messageId" - + " AND cs.contactId = s.contactId" - + " JOIN subscriptionTimes AS st" - + " ON cs.contactId = st.contactId" - + " WHERE cs.contactId = ?" - + " AND timestamp >= start" - + " AND timestamp >= expiry" - + " AND status = ?" - + " AND sendability > ZERO()" - + " ORDER BY timestamp" - + " LIMIT ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setShort(2, (short) Status.NEW.ordinal()); - ps.setInt(3, maxMessages - ids.size()); - rs = ps.executeQuery(); - while(rs.next()) ids.add(new MessageId(rs.getBytes(2))); - rs.close(); - ps.close(); - return Collections.unmodifiableList(ids); - } catch(SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - public Collection<MessageId> getSendableMessages(Connection txn, ContactId c, int maxLength) throws DbException { PreparedStatement ps = null; @@ -1641,7 +1650,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public boolean getStarred(Connection txn, MessageId m) throws DbException { + public boolean getStarredFlag(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -1975,28 +1984,24 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void removeAckedMessages(Connection txn, ContactId c, + public void removeOutstandingMessages(Connection txn, ContactId c, Collection<MessageId> acked) throws DbException { - setStatus(txn, c, acked, Status.SEEN); - } - - private void setStatus(Connection txn, ContactId c, - Collection<MessageId> ids, Status newStatus) throws DbException { PreparedStatement ps = null; try { - // Set the status of each message if it's currently SENT + // Set the status of each message to SEEN if it's currently SENT String sql = "UPDATE statuses SET status = ?" + " WHERE messageId = ? AND contactId = ? AND status = ?"; ps = txn.prepareStatement(sql); - ps.setShort(1, (short) newStatus.ordinal()); + ps.setShort(1, (short) Status.SEEN.ordinal()); ps.setInt(3, c.getInt()); ps.setShort(4, (short) Status.SENT.ordinal()); - for(MessageId m : ids) { + for(MessageId m : acked) { ps.setBytes(2, m.getBytes()); ps.addBatch(); } int[] batchAffected = ps.executeBatch(); - if(batchAffected.length != ids.size()) throw new DbStateException(); + if(batchAffected.length != acked.size()) + throw new DbStateException(); for(int i = 0; i < batchAffected.length; i++) { if(batchAffected[i] > 1) throw new DbStateException(); } @@ -2048,11 +2053,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void removeLostMessages(Connection txn, ContactId c, - Collection<MessageId> lost) throws DbException { - setStatus(txn, c, lost, Status.NEW); - } - public void removeMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; try { diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java index 5a2fabba23ffefc56ed4d11373625b3d5fb4c243..4e03a034116232fe268608712e3e423cb2a7011b 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java @@ -755,7 +755,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { allowing(database).containsContact(txn, contactId); will(returnValue(true)); // Get the sendable message IDs - oneOf(database).getOfferableMessages(txn, contactId, 123); + oneOf(database).getMessagesToOffer(txn, contactId, 123); will(returnValue(offerable)); // Create the packet oneOf(packetFactory).createOffer(offerable); @@ -898,11 +898,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase { // Get the acked messages oneOf(ack).getMessageIds(); will(returnValue(Collections.singletonList(messageId))); - oneOf(database).removeAckedMessages(txn, contactId, + oneOf(database).removeOutstandingMessages(txn, contactId, Collections.singletonList(messageId)); - // Find lost messages - oneOf(database).getLostMessages(txn, contactId); - will(returnValue(Collections.emptyList())); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, shutdown, packetFactory); diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java index e9ba2d0b74c627e950b84496e31a765b147054dd..8e9a2c23b074abd072c0c200894d15785086971e 100644 --- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java +++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java @@ -556,7 +556,7 @@ public class H2DatabaseTest extends BriarTestCase { } @Test - public void testRemoveAckedMessage() throws Exception { + public void testOutstandingMessageAcked() throws Exception { Database<Connection> db = open(false); Connection txn = db.startTransaction(); @@ -584,7 +584,7 @@ public class H2DatabaseTest extends BriarTestCase { assertFalse(it.hasNext()); // Pretend that the message was acked - db.removeAckedMessages(txn, contactId, + db.removeOutstandingMessages(txn, contactId, Collections.singletonList(messageId)); // The message still should not be sendable @@ -595,48 +595,6 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } - @Test - public void testRemoveLostMessage() throws Exception { - Database<Connection> db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact, subscribe to a group and store a message - assertEquals(contactId, db.addContact(txn)); - db.addSubscription(txn, group); - db.addVisibility(txn, contactId, groupId); - db.addSubscription(txn, contactId, group, 0L); - db.addGroupMessage(txn, message); - db.setSendability(txn, messageId, 1); - db.setStatus(txn, contactId, messageId, Status.NEW); - - // Get the message and mark it as sent - Iterator<MessageId> it = - db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - db.setStatus(txn, contactId, messageId, Status.SENT); - db.addOutstandingMessages(txn, contactId, - Collections.singletonList(messageId)); - - // The message should no longer be sendable - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); - - // Pretend that the message was lost - db.removeLostMessages(txn, contactId, - Collections.singletonList(messageId)); - - // The message should be sendable again - it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); - - db.commitTransaction(txn); - db.close(); - } - @Test public void testGetMessagesByAuthor() throws Exception { AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); @@ -1467,12 +1425,12 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroupMessage(txn, message); // The message should be unread by default - assertFalse(db.getRead(txn, messageId)); + assertFalse(db.getReadFlag(txn, messageId)); // Marking the message read should return the old value assertFalse(db.setRead(txn, messageId, true)); assertTrue(db.setRead(txn, messageId, true)); // The message should be read - assertTrue(db.getRead(txn, messageId)); + assertTrue(db.getReadFlag(txn, messageId)); // Marking the message unread should return the old value assertTrue(db.setRead(txn, messageId, false)); assertFalse(db.setRead(txn, messageId, false)); @@ -1493,12 +1451,12 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroupMessage(txn, message); // The message should be unstarred by default - assertFalse(db.getStarred(txn, messageId)); + assertFalse(db.getStarredFlag(txn, messageId)); // Starring the message should return the old value assertFalse(db.setStarred(txn, messageId, true)); assertTrue(db.setStarred(txn, messageId, true)); // The message should be starred - assertTrue(db.getStarred(txn, messageId)); + assertTrue(db.getStarredFlag(txn, messageId)); // Unstarring the message should return the old value assertTrue(db.setStarred(txn, messageId, false)); assertFalse(db.setStarred(txn, messageId, false));