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 +40 −47 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,11 +1447,16 @@ 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); if (plaintextSubject != null) { localMessage.setCachedDecryptedSubject(plaintextSubject); } localFolder.close(); OutboxStateRepository outboxStateRepository = localStore.getOutboxStateRepository(); outboxStateRepository.initializeOutboxState(messageId); sendPendingMessages(account, listener); } catch (Exception e) { /* Loading Loading @@ -1556,6 +1552,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 @@ -1588,26 +1585,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 @@ -1616,6 +1613,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 @@ -1628,28 +1626,39 @@ 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); handleSendFailure(account, localFolder, message, e); } catch (CertificateValidationException e) { outboxStateRepository.decrementSendAttempts(messageId); lastFailure = e; wasPermanentFailure = false; notifyUserIfCertificateProblem(account, e, false); handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); handleSendFailure(account, localFolder, message, e); } catch (MessagingException e) { lastFailure = e; wasPermanentFailure = e.isPermanentFailure(); handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); if (wasPermanentFailure) { String errorMessage = e.getMessage(); outboxStateRepository.setSendAttemptError(messageId, errorMessage); } else if (outboxState.getNumberOfSendAttempts() + 1 >= MAX_SEND_ATTEMPTS) { outboxStateRepository.setSendAttemptsExceeded(messageId); } handleSendFailure(account, localFolder, message, e); } catch (Exception e) { lastFailure = e; wasPermanentFailure = true; handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); handleSendFailure(account, localFolder, message, e); } } catch (Exception e) { lastFailure = e; Loading Loading @@ -1691,7 +1700,7 @@ public class MessagingController { LocalFolder localFolder, LocalMessage message) throws MessagingException { if (!account.hasSentFolder() || !account.isUploadSentMessages()) { Timber.i("Not uploading sent message; deleting local message"); message.setFlag(Flag.DELETED, true); message.destroy(); } else { LocalFolder localSentFolder = localStore.getFolder(account.getSentFolder()); Timber.i("Moving sent message to folder '%s' (%d)", account.getSentFolder(), localSentFolder.getDatabaseId()); Loading @@ -1706,31 +1715,15 @@ public class MessagingController { } } private void handleSendFailure(Account account, LocalStore localStore, Folder localFolder, Message message, Exception exception, boolean permanentFailure) throws MessagingException { private void handleSendFailure(Account account, Folder localFolder, Message message, Exception exception) throws MessagingException { Timber.e(exception, "Failed to send message"); if (permanentFailure) { moveMessageToDraftsFolder(account, localFolder, localStore, message); } message.setFlag(Flag.X_SEND_FAILED, true); notifySynchronizeMailboxFailed(account, localFolder, exception); } private void moveMessageToDraftsFolder(Account account, Folder localFolder, LocalStore localStore, Message message) throws MessagingException { if (!account.hasDraftsFolder()) { Timber.d("Can't move message to Drafts folder. No Drafts folder configured."); return; } LocalFolder draftsFolder = localStore.getFolder(account.getDraftsFolder()); localFolder.moveMessages(Collections.singletonList(message), draftsFolder); } private void notifySynchronizeMailboxFailed(Account account, Folder localFolder, Exception exception) { String folderServerId = localFolder.getServerId(); String errorMessage = getRootCauseMessage(exception); 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/LockableDatabase.java +3 −0 Original line number Diff line number Diff line Loading @@ -378,6 +378,9 @@ public class LockableDatabase { } doOpenOrCreateDb(databaseFile); } mDb.execSQL("PRAGMA foreign_keys = ON;"); if (mDb.getVersion() != mSchemaDefinition.getVersion()) { mSchemaDefinition.doDbUpgrade(mDb); } 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 ) 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 +40 −47 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,11 +1447,16 @@ 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); if (plaintextSubject != null) { localMessage.setCachedDecryptedSubject(plaintextSubject); } localFolder.close(); OutboxStateRepository outboxStateRepository = localStore.getOutboxStateRepository(); outboxStateRepository.initializeOutboxState(messageId); sendPendingMessages(account, listener); } catch (Exception e) { /* Loading Loading @@ -1556,6 +1552,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 @@ -1588,26 +1585,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 @@ -1616,6 +1613,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 @@ -1628,28 +1626,39 @@ 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); handleSendFailure(account, localFolder, message, e); } catch (CertificateValidationException e) { outboxStateRepository.decrementSendAttempts(messageId); lastFailure = e; wasPermanentFailure = false; notifyUserIfCertificateProblem(account, e, false); handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); handleSendFailure(account, localFolder, message, e); } catch (MessagingException e) { lastFailure = e; wasPermanentFailure = e.isPermanentFailure(); handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); if (wasPermanentFailure) { String errorMessage = e.getMessage(); outboxStateRepository.setSendAttemptError(messageId, errorMessage); } else if (outboxState.getNumberOfSendAttempts() + 1 >= MAX_SEND_ATTEMPTS) { outboxStateRepository.setSendAttemptsExceeded(messageId); } handleSendFailure(account, localFolder, message, e); } catch (Exception e) { lastFailure = e; wasPermanentFailure = true; handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); handleSendFailure(account, localFolder, message, e); } } catch (Exception e) { lastFailure = e; Loading Loading @@ -1691,7 +1700,7 @@ public class MessagingController { LocalFolder localFolder, LocalMessage message) throws MessagingException { if (!account.hasSentFolder() || !account.isUploadSentMessages()) { Timber.i("Not uploading sent message; deleting local message"); message.setFlag(Flag.DELETED, true); message.destroy(); } else { LocalFolder localSentFolder = localStore.getFolder(account.getSentFolder()); Timber.i("Moving sent message to folder '%s' (%d)", account.getSentFolder(), localSentFolder.getDatabaseId()); Loading @@ -1706,31 +1715,15 @@ public class MessagingController { } } private void handleSendFailure(Account account, LocalStore localStore, Folder localFolder, Message message, Exception exception, boolean permanentFailure) throws MessagingException { private void handleSendFailure(Account account, Folder localFolder, Message message, Exception exception) throws MessagingException { Timber.e(exception, "Failed to send message"); if (permanentFailure) { moveMessageToDraftsFolder(account, localFolder, localStore, message); } message.setFlag(Flag.X_SEND_FAILED, true); notifySynchronizeMailboxFailed(account, localFolder, exception); } private void moveMessageToDraftsFolder(Account account, Folder localFolder, LocalStore localStore, Message message) throws MessagingException { if (!account.hasDraftsFolder()) { Timber.d("Can't move message to Drafts folder. No Drafts folder configured."); return; } LocalFolder draftsFolder = localStore.getFolder(account.getDraftsFolder()); localFolder.moveMessages(Collections.singletonList(message), draftsFolder); } private void notifySynchronizeMailboxFailed(Account account, Folder localFolder, Exception exception) { String folderServerId = localFolder.getServerId(); String errorMessage = getRootCauseMessage(exception); 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/LockableDatabase.java +3 −0 Original line number Diff line number Diff line Loading @@ -378,6 +378,9 @@ public class LockableDatabase { } doOpenOrCreateDb(databaseFile); } mDb.execSQL("PRAGMA foreign_keys = ON;"); if (mDb.getVersion() != mSchemaDefinition.getVersion()) { mSchemaDefinition.doDbUpgrade(mDb); } 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 )