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

Commit c60f97fb authored by cketti's avatar cketti Committed by GitHub
Browse files

Merge pull request #2069 from k9mail/optimize-message-sync

Don't load all local message metadata into memory during synchronize
parents 8030977d f228abd3
Loading
Loading
Loading
Loading
+11 −13
Original line number Diff line number Diff line
@@ -796,11 +796,7 @@ public class MessagingController {
            final LocalFolder localFolder = tLocalFolder;
            localFolder.open(Folder.OPEN_MODE_RW);
            localFolder.updateLastUid();
            List<? extends Message> localMessages = localFolder.getMessages(null);
            Map<String, Message> localUidMap = new HashMap<>();
            for (Message message : localMessages) {
                localUidMap.put(message.getUid(), message);
            }
            Map<String, Long> localUidMap = localFolder.getAllMessagesAndEffectiveDates();

            if (providedRemoteFolder != null) {
                if (K9.DEBUG)
@@ -870,6 +866,7 @@ public class MessagingController {
            if (K9.DEBUG)
                Log.v(K9.LOG_TAG, "SYNC: Remote message count for folder " + folder + " is " + remoteMessageCount);
            final Date earliestDate = account.getEarliestPollDate();
            long earliestTimestamp = earliestDate != null ? earliestDate.getTime() : 0L;


            int remoteStart = 1;
@@ -899,8 +896,8 @@ public class MessagingController {
                    for (MessagingListener l : getListeners(listener)) {
                        l.synchronizeMailboxHeadersProgress(account, folder, headerProgress.get(), messageCount);
                    }
                    Message localMessage = localUidMap.get(thisMess.getUid());
                    if (localMessage == null || !localMessage.olderThan(earliestDate)) {
                    Long localMessageTimestamp = localUidMap.get(thisMess.getUid());
                    if (localMessageTimestamp == null || localMessageTimestamp >= earliestTimestamp) {
                        remoteMessages.add(thisMess);
                        remoteUidMap.put(thisMess.getUid(), thisMess);
                    }
@@ -921,14 +918,15 @@ public class MessagingController {
             */
            MoreMessages moreMessages = localFolder.getMoreMessages();
            if (account.syncRemoteDeletions()) {
                List<Message> destroyMessages = new ArrayList<>();
                for (Message localMessage : localMessages) {
                    if (remoteUidMap.get(localMessage.getUid()) == null) {
                        destroyMessages.add(localMessage);
                List<String> destroyMessageUids = new ArrayList<>();
                for (String localMessageUid : localUidMap.keySet()) {
                    if (remoteUidMap.get(localMessageUid) == null) {
                        destroyMessageUids.add(localMessageUid);
                    }
                }

                if (!destroyMessages.isEmpty()) {
                List<LocalMessage> destroyMessages = localFolder.getMessagesByUids(destroyMessageUids);
                if (!destroyMessageUids.isEmpty()) {
                    moreMessages = MoreMessages.UNKNOWN;

                    localFolder.destroyMessages(destroyMessages);
@@ -941,7 +939,7 @@ public class MessagingController {
                }
            }
            // noinspection UnusedAssignment, free memory early? (better break up the method!)
            localMessages = null;
            localUidMap = null;

            if (moreMessages == MoreMessages.UNKNOWN) {
                updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart);
+37 −0
Original line number Diff line number Diff line
@@ -865,6 +865,43 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
        }
    }

    public Map<String,Long> getAllMessagesAndEffectiveDates() throws MessagingException {
        try {
            return  localStore.database.execute(false, new DbCallback<Map<String, Long>>() {
                @Override
                public Map<String, Long> doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
                    Cursor cursor = null;
                    HashMap<String, Long> result = new HashMap<>();

                    try {
                        open(OPEN_MODE_RO);

                        cursor = db.rawQuery(
                                "SELECT uid, date " +
                                        "FROM messages " +
                                        "WHERE empty = 0 AND deleted = 0 AND " +
                                        "folder_id = ? ORDER BY date DESC",
                                new String[] { Long.toString(mFolderId) });

                        while (cursor.moveToNext()) {
                            String uid = cursor.getString(0);
                            Long date = cursor.isNull(1) ? null : cursor.getLong(1);
                            result.put(uid, date);
                        }
                    } catch (MessagingException e) {
                        throw new WrappedException(e);
                    } finally {
                        Utility.closeQuietly(cursor);
                    }

                    return result;
                }
            });
        } catch (WrappedException e) {
            throw(MessagingException) e.getCause();
        }
    }

    public List<LocalMessage> getMessages(MessageRetrievalListener<LocalMessage> listener) throws MessagingException {
        return getMessages(listener, true);
    }
+8 −4
Original line number Diff line number Diff line
package com.fsck.k9.controller;


import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
@@ -26,7 +27,6 @@ import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.notification.NotificationController;
import com.fsck.k9.search.LocalSearch;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -65,6 +65,7 @@ import static org.mockito.Mockito.when;
public class MessagingControllerTest {
    private static final String FOLDER_NAME = "Folder";
    private static final int MAXIMUM_SMALL_MESSAGE_SIZE = 1000;
    private static final String MESSAGE_UID1 = "message-uid1";


    private MessagingController controller;
@@ -573,7 +574,9 @@ public class MessagingControllerTest {
        messageCountInRemoteFolder(0);
        LocalMessage localCopyOfRemoteDeletedMessage = mock(LocalMessage.class);
        when(account.syncRemoteDeletions()).thenReturn(true);
        when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(localCopyOfRemoteDeletedMessage));
        when(localFolder.getAllMessagesAndEffectiveDates()).thenReturn(Collections.singletonMap(MESSAGE_UID1, 0L));
        when(localFolder.getMessagesByUids(any(List.class)))
                .thenReturn(Collections.singletonList(localCopyOfRemoteDeletedMessage));

        controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);

@@ -606,7 +609,8 @@ public class MessagingControllerTest {
        when(account.syncRemoteDeletions()).thenReturn(true);
        when(account.getEarliestPollDate()).thenReturn(dateOfEarliestPoll);
        when(localMessage.olderThan(dateOfEarliestPoll)).thenReturn(true);
        when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(localMessage));
        when(localFolder.getAllMessagesAndEffectiveDates()).thenReturn(Collections.singletonMap(MESSAGE_UID1, 0L));
        when(localFolder.getMessagesByUids(any(List.class))).thenReturn(Collections.singletonList(localMessage));

        controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);

@@ -638,9 +642,9 @@ public class MessagingControllerTest {

        verify(remoteFolder, atLeastOnce()).fetch(any(List.class), fetchProfileCaptor.capture(),
                any(MessageRetrievalListener.class));
        assertEquals(2, fetchProfileCaptor.getAllValues().get(0).size());
        assertTrue(fetchProfileCaptor.getAllValues().get(0).contains(FetchProfile.Item.FLAGS));
        assertTrue(fetchProfileCaptor.getAllValues().get(0).contains(FetchProfile.Item.ENVELOPE));
        assertEquals(2, fetchProfileCaptor.getAllValues().get(0).size());
    }

    @Test