diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index 0d055641a..f8e9d5a69 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -118,6 +118,18 @@ public interface DatabaseComponent extends TransactionManager { KeySetId addTransportKeys(Transaction txn, PendingContactId p, TransportKeys k) throws DbException; + /** + * Returns true if there are any acks or messages to send to the given + * contact over a transport with the given maximum latency. + *

+ * Read-only. + * + * @param eager True if messages that are not yet due for retransmission + * should be included + */ + boolean containsAnythingToSend(Transaction txn, ContactId c, + int maxLatency, boolean eager) throws DbException; + /** * Returns true if the database contains the given contact for the given * local pseudonym. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java index cc14a08bb..8c74497b0 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.api.plugin.file; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.properties.TransportProperties; @@ -37,4 +38,9 @@ public interface RemovableDriveManager { * ignored. */ RemovableDriveTask startWriterTask(ContactId c, TransportProperties p); + + /** + * Returns true if there is anything to send to the given contact. + */ + boolean isWriterTaskNeeded(ContactId c) throws DbException; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index 64f54f19b..20afbb374 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -162,6 +162,18 @@ interface Database { KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k) throws DbException; + /** + * Returns true if there are any acks or messages to send to the given + * contact over a transport with the given maximum latency. + *

+ * Read-only. + * + * @param eager True if messages that are not yet due for retransmission + * should be included + */ + boolean containsAnythingToSend(T txn, ContactId c, int maxLatency, + boolean eager) throws DbException; + /** * Returns true if the database contains the given contact for the given * local pseudonym. diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index c92245acd..9030e2c60 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -341,6 +341,15 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.addTransportKeys(txn, p, k); } + @Override + public boolean containsAnythingToSend(Transaction transaction, ContactId c, + int maxLatency, boolean eager) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.containsAnythingToSend(txn, c, maxLatency, eager); + } + @Override public boolean containsContact(Transaction transaction, AuthorId remote, AuthorId local) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index c77010b82..a9b42e235 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -1127,6 +1127,55 @@ abstract class JdbcDatabase implements Database { } } + @Override + public boolean containsAnythingToSend(Connection txn, ContactId c, + int maxLatency, boolean eager) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT NULL FROM statuses" + + " WHERE contactId = ? AND ack = TRUE"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + rs = ps.executeQuery(); + boolean acksToSend = rs.next(); + rs.close(); + ps.close(); + if (acksToSend) return true; + if (eager) { + sql = "SELECT NULL from statuses" + + " WHERE contactId = ? AND state = ?" + + " AND groupShared = TRUE AND messageShared = TRUE" + + " AND deleted = FALSE AND seen = FALSE"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, DELIVERED.getValue()); + } else { + long now = clock.currentTimeMillis(); + long eta = now + maxLatency; + sql = "SELECT NULL FROM statuses" + + " WHERE contactId = ? AND state = ?" + + " AND groupShared = TRUE AND messageShared = TRUE" + + " AND deleted = FALSE AND seen = FALSE" + + " AND (expiry <= ? OR eta > ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, DELIVERED.getValue()); + ps.setLong(3, now); + ps.setLong(4, eta); + } + rs = ps.executeQuery(); + boolean messagesToSend = rs.next(); + rs.close(); + ps.close(); + return messagesToSend; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public boolean containsContact(Connection txn, AuthorId remote, AuthorId local) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java index 78490a045..2c96d5d53 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java @@ -1,6 +1,8 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; @@ -14,12 +16,15 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; +import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY; + @ThreadSafe @NotNullByDefault class RemovableDriveManagerImpl implements RemovableDriveManager, RemovableDriveTaskRegistry { private final Executor ioExecutor; + private final DatabaseComponent db; private final RemovableDriveTaskFactory taskFactory; private final Object lock = new Object(); @@ -30,8 +35,9 @@ class RemovableDriveManagerImpl @Inject RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor, - RemovableDriveTaskFactory taskFactory) { + DatabaseComponent db, RemovableDriveTaskFactory taskFactory) { this.ioExecutor = ioExecutor; + this.db = db; this.taskFactory = taskFactory; } @@ -74,6 +80,12 @@ class RemovableDriveManagerImpl return created; } + @Override + public boolean isWriterTaskNeeded(ContactId c) throws DbException { + return db.transactionWithResult(true, txn -> + db.containsAnythingToSend(txn, c, MAX_LATENCY, true)); + } + @Override public void removeReader(RemovableDriveTask task) { synchronized (lock) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java index 6f1ad7564..d0bc374ed 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -17,7 +17,7 @@ import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.I @NotNullByDefault public class RemovableDrivePluginFactory implements SimplexPluginFactory { - private static final int MAX_LATENCY = (int) DAYS.toMillis(14); + static final int MAX_LATENCY = (int) DAYS.toMillis(14); @Inject RemovableDrivePluginFactory() { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index db6b3411b..649b09369 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -57,6 +57,7 @@ import java.util.concurrent.atomic.AtomicLong; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.concurrent.TimeUnit.SECONDS; @@ -222,21 +223,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, true, false, null); // The contact has not seen the message, so it should be sendable - Collection ids = - db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendEagerly(db, txn); + assertOneMessageToSendLazily(db, txn); // Changing the status to seen = true should make the message unsendable db.raiseSeenFlag(txn, contactId, messageId); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendEagerly(db, txn); + assertNothingToSendLazily(db, txn); db.commitTransaction(txn); db.close(); @@ -256,37 +249,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, UNKNOWN, true, false, null); // The message has not been validated, so it should not be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Marking the message delivered should make it sendable db.setMessageState(txn, messageId, DELIVERED); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); // Marking the message invalid should make it unsendable db.setMessageState(txn, messageId, INVALID); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Marking the message pending should make it unsendable db.setMessageState(txn, messageId, PENDING); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -305,45 +284,28 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, true, false, null); // The group is invisible, so the message should not be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Making the group visible should not make the message sendable db.addGroupVisibility(txn, contactId, groupId, false); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Sharing the group should make the message sendable db.setGroupVisibility(txn, contactId, groupId, true); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendEagerly(db, txn); + assertOneMessageToSendLazily(db, txn); // Unsharing the group should make the message unsendable db.setGroupVisibility(txn, contactId, groupId, false); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Making the group invisible should make the message unsendable db.removeGroupVisibility(txn, contactId, groupId); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -363,21 +325,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, false, false, null); // The message is not shared, so it should not be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Sharing the message should make it sendable db.setMessageShared(txn, messageId, true); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -397,19 +351,17 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, true, false, null); // The message is sendable, but too large to send + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); Collection ids = db.getMessagesToSend(txn, contactId, message.getRawLength() - 1, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); // The message is just the right size to send ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), MAX_LATENCY); assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); db.commitTransaction(txn); db.close(); @@ -427,6 +379,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addGroup(txn, group); db.addGroupVisibility(txn, contactId, groupId, false); + // Initially there should be nothing to send + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); + // Add some messages to ack Message message1 = getMessage(groupId); MessageId messageId1 = message1.getId(); @@ -434,6 +392,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message1, DELIVERED, true, false, contactId); // Both message IDs should be returned + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); Collection ids = db.getMessagesToAck(txn, contactId, 1234); assertEquals(asList(messageId, messageId1), ids); @@ -441,6 +403,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.lowerAckFlag(txn, contactId, asList(messageId, messageId1)); // Both message IDs should have been removed + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234)); @@ -449,6 +415,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.raiseAckFlag(txn, contactId, messageId1); // Both message IDs should be returned + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); ids = db.getMessagesToAck(txn, contactId, 1234); assertEquals(asList(messageId, messageId1), ids); @@ -469,22 +439,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, true, false, null); - // Retrieve the message from the database and mark it as sent - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); + // The message should be sendable via lazy or eager retransmission + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); + + // Mark the message as sent db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY); - // The message should no longer be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); + // The message should no longer be sendable via lazy retransmission, + // but it should still be sendable via eager retransmission + assertNothingToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); - // Pretend that the message was acked + // Mark the message as acked db.raiseSeenFlag(txn, contactId, messageId); - // The message still should not be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); + // The message still should not be sendable via lazy or eager + // retransmission + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -1957,11 +1930,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); // The message should be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); // The message should be available Message m = db.getMessage(txn, messageId); @@ -1977,10 +1947,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); // The message should not be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Requesting the message should throw an exception try { @@ -2609,6 +2577,50 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { deleteTestDirectory(testDir); } + private void assertNothingToSendLazily(Database db, + Connection txn) throws Exception { + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + Collection ids = + db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); + assertTrue(ids.isEmpty()); + } + + private void assertOneMessageToSendLazily(Database db, + Connection txn) throws Exception { + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + Collection ids = + db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); + assertEquals(singletonList(messageId), ids); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); + assertEquals(singletonList(messageId), ids); + } + + private void assertNothingToSendEagerly(Database db, + Connection txn) throws Exception { + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); + Map unacked = + db.getUnackedMessagesToSend(txn, contactId); + assertTrue(unacked.isEmpty()); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + } + + private void assertOneMessageToSendEagerly(Database db, + Connection txn) throws Exception { + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); + Map unacked = + db.getUnackedMessagesToSend(txn, contactId); + assertEquals(singleton(messageId), unacked.keySet()); + assertEquals(message.getRawLength(), unacked.get(messageId).intValue()); + assertEquals(message.getRawLength(), + db.getUnackedMessageBytesToSend(txn, contactId)); + } + private static class StoppedClock implements Clock { private final long time;