Loading app/core/src/main/java/com/fsck/k9/KoinModule.kt +1 −0 Original line number Diff line number Diff line Loading @@ -25,4 +25,5 @@ val mainModule = applicationContext { bean { TrustManagerFactory.createInstance(get()) } bean { LocalKeyStoreManager(get()) } bean { DefaultTrustedSocketFactory(get(), get()) as TrustedSocketFactory } bean { Clock.INSTANCE } } app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +33 −24 Original line number Diff line number Diff line Loading @@ -38,8 +38,6 @@ import com.fsck.k9.Account.DeletePolicy; import com.fsck.k9.Account.Expunge; import com.fsck.k9.AccountStats; import com.fsck.k9.CoreResourceProvider; import com.fsck.k9.controller.ControllerExtension.ControllerInternals; import com.fsck.k9.core.BuildConfig; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.K9.Intents; Loading @@ -49,6 +47,7 @@ import com.fsck.k9.backend.api.Backend; import com.fsck.k9.backend.api.SyncConfig; import com.fsck.k9.backend.api.SyncListener; import com.fsck.k9.cache.EmailProviderCache; import com.fsck.k9.controller.ControllerExtension.ControllerInternals; import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend; import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand; import com.fsck.k9.controller.MessagingControllerCommands.PendingEmptyTrash; Loading @@ -57,6 +56,7 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead; import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy; import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag; import com.fsck.k9.controller.ProgressBodyFactory.ProgressListener; import com.fsck.k9.core.BuildConfig; import com.fsck.k9.helper.Contacts; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.AuthenticationFailedException; Loading @@ -78,6 +78,9 @@ import com.fsck.k9.mailstore.LocalFolder; import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.mailstore.LocalStore; import com.fsck.k9.mailstore.LocalStoreProvider; import com.fsck.k9.mailstore.OutboxState; import com.fsck.k9.mailstore.OutboxStateRepository; import com.fsck.k9.mailstore.SendState; import com.fsck.k9.mailstore.UnavailableStorageException; import com.fsck.k9.notification.NotificationController; import com.fsck.k9.power.TracingPowerManager; Loading Loading @@ -123,7 +126,6 @@ public class MessagingController { private final BlockingQueue<Command> queuedCommands = new PriorityBlockingQueue<>(); private final Set<MessagingListener> listeners = new CopyOnWriteArraySet<>(); private final ConcurrentHashMap<String, AtomicInteger> sendCount = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Account, Pusher> pushers = new ConcurrentHashMap<>(); private final ExecutorService threadPool = Executors.newCachedThreadPool(); private final MemorizingMessagingListener memorizingMessagingListener = new MemorizingMessagingListener(); Loading Loading @@ -1189,17 +1191,6 @@ public class MessagingController { localFolder = localStore.getFolder(folderServerId); localFolder.open(Folder.OPEN_MODE_RW); // Allows for re-allowing sending of messages that could not be sent if (flag == Flag.FLAGGED && !newState && account.getOutboxFolder().equals(folderServerId)) { for (Message message : messages) { String uid = message.getUid(); if (uid != null) { sendCount.remove(uid); } } } // Update the messages in the local store localFolder.setFlags(messages, Collections.singleton(flag), newState); Loading Loading @@ -1456,9 +1447,14 @@ public class MessagingController { localFolder.open(Folder.OPEN_MODE_RW); localFolder.appendMessages(Collections.singletonList(message)); LocalMessage localMessage = localFolder.getMessage(message.getUid()); long messageId = localMessage.getDatabaseId(); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localMessage.setCachedDecryptedSubject(plaintextSubject); localFolder.close(); OutboxStateRepository outboxStateRepository = localStore.getOutboxStateRepository(); outboxStateRepository.initializeOutboxState(messageId); sendPendingMessages(account, listener); } catch (Exception e) { /* Loading Loading @@ -1554,6 +1550,7 @@ public class MessagingController { boolean wasPermanentFailure = false; try { LocalStore localStore = localStoreProvider.getInstance(account); OutboxStateRepository outboxStateRepository = localStore.getOutboxStateRepository(); localFolder = localStore.getFolder( account.getOutboxFolder()); if (!localFolder.exists()) { Loading Loading @@ -1586,26 +1583,26 @@ public class MessagingController { for (LocalMessage message : localMessages) { if (message.isSet(Flag.DELETED)) { //FIXME: When uploading a message to the remote Sent folder the move code creates a placeholder // message in the Outbox. This code gets rid of these messages. It'd be preferable if the // placeholder message was never created, though. message.destroy(); continue; } try { AtomicInteger count = new AtomicInteger(0); AtomicInteger oldCount = sendCount.putIfAbsent(message.getUid(), count); if (oldCount != null) { count = oldCount; } Timber.i("Send count for message %s is %d", message.getUid(), count.get()); long messageId = message.getDatabaseId(); OutboxState outboxState = outboxStateRepository.getOutboxState(messageId); if (count.incrementAndGet() > K9.MAX_SEND_ATTEMPTS) { Timber.e("Send count for message %s can't be delivered after %d attempts. " + "Giving up until the user restarts the device", message.getUid(), MAX_SEND_ATTEMPTS); if (outboxState.getSendState() != SendState.READY) { Timber.v("Skipping sending message " + message.getUid()); notificationController.showSendFailedNotification(account, new MessagingException(message.getSubject())); continue; } Timber.i("Send count for message %s is %d", message.getUid(), outboxState.getNumberOfSendAttempts()); localFolder.fetch(Collections.singletonList(message), fp, null); try { if (message.getHeader(K9.IDENTITY_HEADER).length > 0 || message.isSet(Flag.DRAFT)) { Loading @@ -1614,6 +1611,7 @@ public class MessagingController { continue; } outboxStateRepository.incrementSendAttempts(messageId); message.setFlag(Flag.X_SEND_IN_PROGRESS, true); Timber.i("Sending message with UID %s", message.getUid()); Loading @@ -1626,13 +1624,17 @@ public class MessagingController { l.synchronizeMailboxProgress(account, account.getSentFolder(), progress, todo); } moveOrDeleteSentMessage(account, localStore, localFolder, message); outboxStateRepository.removeOutboxState(messageId); } catch (AuthenticationFailedException e) { outboxStateRepository.decrementSendAttempts(messageId); lastFailure = e; wasPermanentFailure = false; handleAuthenticationFailure(account, false); handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); } catch (CertificateValidationException e) { outboxStateRepository.decrementSendAttempts(messageId); lastFailure = e; wasPermanentFailure = false; Loading @@ -1642,6 +1644,13 @@ public class MessagingController { lastFailure = e; wasPermanentFailure = e.isPermanentFailure(); if (wasPermanentFailure) { String errorMessage = e.getMessage(); outboxStateRepository.setSendAttemptError(messageId, errorMessage); } else if (outboxState.getNumberOfSendAttempts() + 1 >= MAX_SEND_ATTEMPTS) { outboxStateRepository.setSendAttemptsExceeded(messageId); } handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); } catch (Exception e) { lastFailure = e; Loading app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java +9 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.text.TextUtils; import com.fsck.k9.Account; import com.fsck.k9.AccountStats; import com.fsck.k9.Clock; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.Preferences; Loading Loading @@ -180,6 +181,7 @@ public class LocalStore { private final Account account; private final LockableDatabase database; private final OutboxStateRepository outboxStateRepository; static LocalStore createInstance(Account account, Context context) throws MessagingException { return new LocalStore(account, context); Loading Loading @@ -209,6 +211,9 @@ public class LocalStore { database = new LockableDatabase(context, account.getUuid(), schemaDefinition); database.setStorageProviderId(account.getLocalStorageProviderId()); database.open(); Clock clock = DI.get(Clock.class); outboxStateRepository = new OutboxStateRepository(database, clock); } public static int getDbVersion() { Loading Loading @@ -248,6 +253,10 @@ public class LocalStore { return Preferences.getPreferences(context); } public OutboxStateRepository getOutboxStateRepository() { return outboxStateRepository; } public long getSize() throws MessagingException { final StorageManager storageManager = StorageManager.getInstance(context); Loading app/core/src/main/java/com/fsck/k9/mailstore/OutboxState.kt 0 → 100644 +8 −0 Original line number Diff line number Diff line package com.fsck.k9.mailstore data class OutboxState( val sendState: SendState, val numberOfSendAttempts: Int, val sendError: String?, val sendErrorTimestamp: Long ) app/core/src/main/java/com/fsck/k9/mailstore/OutboxStateRepository.kt 0 → 100644 +114 −0 Original line number Diff line number Diff line package com.fsck.k9.mailstore import android.content.ContentValues import com.fsck.k9.Clock class OutboxStateRepository(private val database: LockableDatabase, private val clock: Clock) { fun getOutboxState(messageId: Long): OutboxState { return database.execute(false) { db -> db.query( TABLE_NAME, COLUMNS, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString()), null, null, null ).use { cursor -> if (!cursor.moveToFirst()) { throw IllegalStateException("No outbox_state entry for message with id $messageId") } val sendStateString = cursor.getString(cursor.getColumnIndex(COLUMN_SEND_STATE)) val numberOfSendAttempts = cursor.getInt(cursor.getColumnIndex(COLUMN_NUMBER_OF_SEND_ATTEMPTS)) val sendErrorTimestamp = cursor.getLong(cursor.getColumnIndex(COLUMN_ERROR_TIMESTAMP)) val sendErrorColumnIndex = cursor.getColumnIndex(COLUMN_ERROR) val sendError = if (cursor.isNull(sendErrorColumnIndex)) null else cursor.getString(sendErrorColumnIndex) val sendState = SendState.fromDatabaseName(sendStateString) OutboxState(sendState, numberOfSendAttempts, sendError, sendErrorTimestamp) } } } fun initializeOutboxState(messageId: Long) { database.execute(false) { db -> val contentValues = ContentValues().apply { put(COLUMN_MESSAGE_ID, messageId) put(COLUMN_SEND_STATE, SendState.READY.databaseName) } db.insert(TABLE_NAME, null, contentValues) } } fun removeOutboxState(messageId: Long) { database.execute(false) { db -> db.delete(TABLE_NAME, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString())) } } fun incrementSendAttempts(messageId: Long) { database.execute(false) { db -> db.execSQL("UPDATE $TABLE_NAME " + "SET $COLUMN_NUMBER_OF_SEND_ATTEMPTS = $COLUMN_NUMBER_OF_SEND_ATTEMPTS + 1 " + "WHERE $COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString()) ) } } fun decrementSendAttempts(messageId: Long) { database.execute(false) { db -> db.execSQL("UPDATE $TABLE_NAME " + "SET $COLUMN_NUMBER_OF_SEND_ATTEMPTS = $COLUMN_NUMBER_OF_SEND_ATTEMPTS - 1 " + "WHERE $COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString()) ) } } fun setSendAttemptError(messageId: Long, errorMessage: String) { val sendErrorTimestamp = clock.time database.execute(false) { db -> val contentValues = ContentValues().apply { put(COLUMN_SEND_STATE, SendState.ERROR.databaseName) put(COLUMN_ERROR_TIMESTAMP, sendErrorTimestamp) put(COLUMN_ERROR, errorMessage) } db.update(TABLE_NAME, contentValues, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString())) } } fun setSendAttemptsExceeded(messageId: Long) { val sendErrorTimestamp = clock.time database.execute(false) { db -> val contentValues = ContentValues().apply { put(COLUMN_SEND_STATE, SendState.RETRIES_EXCEEDED.databaseName) put(COLUMN_ERROR_TIMESTAMP, sendErrorTimestamp) putNull(COLUMN_ERROR) } db.update(TABLE_NAME, contentValues, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString())) } } companion object { private const val TABLE_NAME = "outbox_state" private const val COLUMN_MESSAGE_ID = "message_id" private const val COLUMN_SEND_STATE = "send_state" private const val COLUMN_NUMBER_OF_SEND_ATTEMPTS = "number_of_send_attempts" private const val COLUMN_ERROR_TIMESTAMP = "error_timestamp" private const val COLUMN_ERROR = "error" private val COLUMNS = arrayOf( COLUMN_SEND_STATE, COLUMN_NUMBER_OF_SEND_ATTEMPTS, COLUMN_ERROR_TIMESTAMP, COLUMN_ERROR ) } } Loading
app/core/src/main/java/com/fsck/k9/KoinModule.kt +1 −0 Original line number Diff line number Diff line Loading @@ -25,4 +25,5 @@ val mainModule = applicationContext { bean { TrustManagerFactory.createInstance(get()) } bean { LocalKeyStoreManager(get()) } bean { DefaultTrustedSocketFactory(get(), get()) as TrustedSocketFactory } bean { Clock.INSTANCE } }
app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +33 −24 Original line number Diff line number Diff line Loading @@ -38,8 +38,6 @@ import com.fsck.k9.Account.DeletePolicy; import com.fsck.k9.Account.Expunge; import com.fsck.k9.AccountStats; import com.fsck.k9.CoreResourceProvider; import com.fsck.k9.controller.ControllerExtension.ControllerInternals; import com.fsck.k9.core.BuildConfig; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.K9.Intents; Loading @@ -49,6 +47,7 @@ import com.fsck.k9.backend.api.Backend; import com.fsck.k9.backend.api.SyncConfig; import com.fsck.k9.backend.api.SyncListener; import com.fsck.k9.cache.EmailProviderCache; import com.fsck.k9.controller.ControllerExtension.ControllerInternals; import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend; import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand; import com.fsck.k9.controller.MessagingControllerCommands.PendingEmptyTrash; Loading @@ -57,6 +56,7 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead; import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy; import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag; import com.fsck.k9.controller.ProgressBodyFactory.ProgressListener; import com.fsck.k9.core.BuildConfig; import com.fsck.k9.helper.Contacts; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.AuthenticationFailedException; Loading @@ -78,6 +78,9 @@ import com.fsck.k9.mailstore.LocalFolder; import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.mailstore.LocalStore; import com.fsck.k9.mailstore.LocalStoreProvider; import com.fsck.k9.mailstore.OutboxState; import com.fsck.k9.mailstore.OutboxStateRepository; import com.fsck.k9.mailstore.SendState; import com.fsck.k9.mailstore.UnavailableStorageException; import com.fsck.k9.notification.NotificationController; import com.fsck.k9.power.TracingPowerManager; Loading Loading @@ -123,7 +126,6 @@ public class MessagingController { private final BlockingQueue<Command> queuedCommands = new PriorityBlockingQueue<>(); private final Set<MessagingListener> listeners = new CopyOnWriteArraySet<>(); private final ConcurrentHashMap<String, AtomicInteger> sendCount = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Account, Pusher> pushers = new ConcurrentHashMap<>(); private final ExecutorService threadPool = Executors.newCachedThreadPool(); private final MemorizingMessagingListener memorizingMessagingListener = new MemorizingMessagingListener(); Loading Loading @@ -1189,17 +1191,6 @@ public class MessagingController { localFolder = localStore.getFolder(folderServerId); localFolder.open(Folder.OPEN_MODE_RW); // Allows for re-allowing sending of messages that could not be sent if (flag == Flag.FLAGGED && !newState && account.getOutboxFolder().equals(folderServerId)) { for (Message message : messages) { String uid = message.getUid(); if (uid != null) { sendCount.remove(uid); } } } // Update the messages in the local store localFolder.setFlags(messages, Collections.singleton(flag), newState); Loading Loading @@ -1456,9 +1447,14 @@ public class MessagingController { localFolder.open(Folder.OPEN_MODE_RW); localFolder.appendMessages(Collections.singletonList(message)); LocalMessage localMessage = localFolder.getMessage(message.getUid()); long messageId = localMessage.getDatabaseId(); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localMessage.setCachedDecryptedSubject(plaintextSubject); localFolder.close(); OutboxStateRepository outboxStateRepository = localStore.getOutboxStateRepository(); outboxStateRepository.initializeOutboxState(messageId); sendPendingMessages(account, listener); } catch (Exception e) { /* Loading Loading @@ -1554,6 +1550,7 @@ public class MessagingController { boolean wasPermanentFailure = false; try { LocalStore localStore = localStoreProvider.getInstance(account); OutboxStateRepository outboxStateRepository = localStore.getOutboxStateRepository(); localFolder = localStore.getFolder( account.getOutboxFolder()); if (!localFolder.exists()) { Loading Loading @@ -1586,26 +1583,26 @@ public class MessagingController { for (LocalMessage message : localMessages) { if (message.isSet(Flag.DELETED)) { //FIXME: When uploading a message to the remote Sent folder the move code creates a placeholder // message in the Outbox. This code gets rid of these messages. It'd be preferable if the // placeholder message was never created, though. message.destroy(); continue; } try { AtomicInteger count = new AtomicInteger(0); AtomicInteger oldCount = sendCount.putIfAbsent(message.getUid(), count); if (oldCount != null) { count = oldCount; } Timber.i("Send count for message %s is %d", message.getUid(), count.get()); long messageId = message.getDatabaseId(); OutboxState outboxState = outboxStateRepository.getOutboxState(messageId); if (count.incrementAndGet() > K9.MAX_SEND_ATTEMPTS) { Timber.e("Send count for message %s can't be delivered after %d attempts. " + "Giving up until the user restarts the device", message.getUid(), MAX_SEND_ATTEMPTS); if (outboxState.getSendState() != SendState.READY) { Timber.v("Skipping sending message " + message.getUid()); notificationController.showSendFailedNotification(account, new MessagingException(message.getSubject())); continue; } Timber.i("Send count for message %s is %d", message.getUid(), outboxState.getNumberOfSendAttempts()); localFolder.fetch(Collections.singletonList(message), fp, null); try { if (message.getHeader(K9.IDENTITY_HEADER).length > 0 || message.isSet(Flag.DRAFT)) { Loading @@ -1614,6 +1611,7 @@ public class MessagingController { continue; } outboxStateRepository.incrementSendAttempts(messageId); message.setFlag(Flag.X_SEND_IN_PROGRESS, true); Timber.i("Sending message with UID %s", message.getUid()); Loading @@ -1626,13 +1624,17 @@ public class MessagingController { l.synchronizeMailboxProgress(account, account.getSentFolder(), progress, todo); } moveOrDeleteSentMessage(account, localStore, localFolder, message); outboxStateRepository.removeOutboxState(messageId); } catch (AuthenticationFailedException e) { outboxStateRepository.decrementSendAttempts(messageId); lastFailure = e; wasPermanentFailure = false; handleAuthenticationFailure(account, false); handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); } catch (CertificateValidationException e) { outboxStateRepository.decrementSendAttempts(messageId); lastFailure = e; wasPermanentFailure = false; Loading @@ -1642,6 +1644,13 @@ public class MessagingController { lastFailure = e; wasPermanentFailure = e.isPermanentFailure(); if (wasPermanentFailure) { String errorMessage = e.getMessage(); outboxStateRepository.setSendAttemptError(messageId, errorMessage); } else if (outboxState.getNumberOfSendAttempts() + 1 >= MAX_SEND_ATTEMPTS) { outboxStateRepository.setSendAttemptsExceeded(messageId); } handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); } catch (Exception e) { lastFailure = e; Loading
app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java +9 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.text.TextUtils; import com.fsck.k9.Account; import com.fsck.k9.AccountStats; import com.fsck.k9.Clock; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.Preferences; Loading Loading @@ -180,6 +181,7 @@ public class LocalStore { private final Account account; private final LockableDatabase database; private final OutboxStateRepository outboxStateRepository; static LocalStore createInstance(Account account, Context context) throws MessagingException { return new LocalStore(account, context); Loading Loading @@ -209,6 +211,9 @@ public class LocalStore { database = new LockableDatabase(context, account.getUuid(), schemaDefinition); database.setStorageProviderId(account.getLocalStorageProviderId()); database.open(); Clock clock = DI.get(Clock.class); outboxStateRepository = new OutboxStateRepository(database, clock); } public static int getDbVersion() { Loading Loading @@ -248,6 +253,10 @@ public class LocalStore { return Preferences.getPreferences(context); } public OutboxStateRepository getOutboxStateRepository() { return outboxStateRepository; } public long getSize() throws MessagingException { final StorageManager storageManager = StorageManager.getInstance(context); Loading
app/core/src/main/java/com/fsck/k9/mailstore/OutboxState.kt 0 → 100644 +8 −0 Original line number Diff line number Diff line package com.fsck.k9.mailstore data class OutboxState( val sendState: SendState, val numberOfSendAttempts: Int, val sendError: String?, val sendErrorTimestamp: Long )
app/core/src/main/java/com/fsck/k9/mailstore/OutboxStateRepository.kt 0 → 100644 +114 −0 Original line number Diff line number Diff line package com.fsck.k9.mailstore import android.content.ContentValues import com.fsck.k9.Clock class OutboxStateRepository(private val database: LockableDatabase, private val clock: Clock) { fun getOutboxState(messageId: Long): OutboxState { return database.execute(false) { db -> db.query( TABLE_NAME, COLUMNS, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString()), null, null, null ).use { cursor -> if (!cursor.moveToFirst()) { throw IllegalStateException("No outbox_state entry for message with id $messageId") } val sendStateString = cursor.getString(cursor.getColumnIndex(COLUMN_SEND_STATE)) val numberOfSendAttempts = cursor.getInt(cursor.getColumnIndex(COLUMN_NUMBER_OF_SEND_ATTEMPTS)) val sendErrorTimestamp = cursor.getLong(cursor.getColumnIndex(COLUMN_ERROR_TIMESTAMP)) val sendErrorColumnIndex = cursor.getColumnIndex(COLUMN_ERROR) val sendError = if (cursor.isNull(sendErrorColumnIndex)) null else cursor.getString(sendErrorColumnIndex) val sendState = SendState.fromDatabaseName(sendStateString) OutboxState(sendState, numberOfSendAttempts, sendError, sendErrorTimestamp) } } } fun initializeOutboxState(messageId: Long) { database.execute(false) { db -> val contentValues = ContentValues().apply { put(COLUMN_MESSAGE_ID, messageId) put(COLUMN_SEND_STATE, SendState.READY.databaseName) } db.insert(TABLE_NAME, null, contentValues) } } fun removeOutboxState(messageId: Long) { database.execute(false) { db -> db.delete(TABLE_NAME, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString())) } } fun incrementSendAttempts(messageId: Long) { database.execute(false) { db -> db.execSQL("UPDATE $TABLE_NAME " + "SET $COLUMN_NUMBER_OF_SEND_ATTEMPTS = $COLUMN_NUMBER_OF_SEND_ATTEMPTS + 1 " + "WHERE $COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString()) ) } } fun decrementSendAttempts(messageId: Long) { database.execute(false) { db -> db.execSQL("UPDATE $TABLE_NAME " + "SET $COLUMN_NUMBER_OF_SEND_ATTEMPTS = $COLUMN_NUMBER_OF_SEND_ATTEMPTS - 1 " + "WHERE $COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString()) ) } } fun setSendAttemptError(messageId: Long, errorMessage: String) { val sendErrorTimestamp = clock.time database.execute(false) { db -> val contentValues = ContentValues().apply { put(COLUMN_SEND_STATE, SendState.ERROR.databaseName) put(COLUMN_ERROR_TIMESTAMP, sendErrorTimestamp) put(COLUMN_ERROR, errorMessage) } db.update(TABLE_NAME, contentValues, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString())) } } fun setSendAttemptsExceeded(messageId: Long) { val sendErrorTimestamp = clock.time database.execute(false) { db -> val contentValues = ContentValues().apply { put(COLUMN_SEND_STATE, SendState.RETRIES_EXCEEDED.databaseName) put(COLUMN_ERROR_TIMESTAMP, sendErrorTimestamp) putNull(COLUMN_ERROR) } db.update(TABLE_NAME, contentValues, "$COLUMN_MESSAGE_ID = ?", arrayOf(messageId.toString())) } } companion object { private const val TABLE_NAME = "outbox_state" private const val COLUMN_MESSAGE_ID = "message_id" private const val COLUMN_SEND_STATE = "send_state" private const val COLUMN_NUMBER_OF_SEND_ATTEMPTS = "number_of_send_attempts" private const val COLUMN_ERROR_TIMESTAMP = "error_timestamp" private const val COLUMN_ERROR = "error" private val COLUMNS = arrayOf( COLUMN_SEND_STATE, COLUMN_NUMBER_OF_SEND_ATTEMPTS, COLUMN_ERROR_TIMESTAMP, COLUMN_ERROR ) } }