Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit ae2f831b authored by cketti's avatar cketti Committed by GitHub
Browse files

Merge pull request #3806 from k9mail/outbox_state

Persist send error state
parents 72b605fa 6a408df8
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -25,4 +25,5 @@ val mainModule = applicationContext {
    bean { TrustManagerFactory.createInstance(get()) }
    bean { LocalKeyStoreManager(get()) }
    bean { DefaultTrustedSocketFactory(get(), get()) as TrustedSocketFactory }
    bean { Clock.INSTANCE }
}
+40 −47
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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();
@@ -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);

@@ -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) {
            /*
@@ -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()) {
@@ -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)) {
@@ -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());
@@ -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;
@@ -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());
@@ -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);
+9 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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);
@@ -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() {
@@ -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);
+3 −0
Original line number Diff line number Diff line
@@ -378,6 +378,9 @@ public class LockableDatabase {
                }
                doOpenOrCreateDb(databaseFile);
            }

            mDb.execSQL("PRAGMA foreign_keys = ON;");

            if (mDb.getVersion() != mSchemaDefinition.getVersion()) {
                mSchemaDefinition.doDbUpgrade(mDb);
            }
+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