Move code to search/fetch messages to Backend implementations

This commit is contained in:
cketti 2018-06-17 16:57:18 +02:00
parent d7558a1313
commit 80c76e6fb9
16 changed files with 232 additions and 62 deletions

View file

@ -1,8 +1,10 @@
package com.fsck.k9.backend.api
import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Folder
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.MessagingException
@ -44,4 +46,15 @@ interface Backend {
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>?
@Throws(MessagingException::class)
fun search(
folderServerId: String,
query: String?,
requiredFlags: Set<Flag>?,
forbiddenFlags: Set<Flag>?
): List<String>
@Throws(MessagingException::class)
fun fetchMessage(folderServerId: String, messageServerId: String, fetchProfile: FetchProfile): Message
}

View file

@ -0,0 +1,45 @@
package com.fsck.k9.backend.imap
import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.store.imap.ImapFolder
import com.fsck.k9.mail.store.imap.ImapMessage
import com.fsck.k9.mail.store.imap.ImapStore
internal class CommandFetchMessage(private val imapStore: ImapStore) {
fun fetchMessage(folderServerId: String, messageServerId: String, fetchProfile: FetchProfile): Message {
val folder = imapStore.getFolder(folderServerId)
try {
val message = folder.getMessage(messageServerId)
//fun fact: ImapFolder.fetch can't handle getting STRUCTURE at same time as headers
if (fetchProfile.contains(FetchProfile.Item.STRUCTURE) &&
fetchProfile.contains(FetchProfile.Item.ENVELOPE)) {
val headerFetchProfile = fetchProfile.without(FetchProfile.Item.STRUCTURE)
val structureFetchProfile = FetchProfile().apply { add(FetchProfile.Item.STRUCTURE) }
fetchMessage(folder, message, headerFetchProfile)
fetchMessage(folder, message, structureFetchProfile)
} else {
fetchMessage(folder, message, fetchProfile)
}
return message
} finally {
folder.close()
}
}
private fun fetchMessage(remoteFolder: ImapFolder, message: ImapMessage, fetchProfile: FetchProfile) {
remoteFolder.fetch(listOf(message), fetchProfile, null)
}
private fun FetchProfile.without(item: FetchProfile.Item) = FetchProfile().apply {
this@without.forEach {
if (it != item) add(it)
}
}
}

View file

@ -0,0 +1,25 @@
package com.fsck.k9.backend.imap
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.store.imap.ImapStore
internal class CommandSearch(private val imapStore: ImapStore) {
fun search(
folderServerId: String,
query: String?,
requiredFlags: Set<Flag>?,
forbiddenFlags: Set<Flag>?
): List<String> {
val folder = imapStore.getFolder(folderServerId)
try {
return folder.search(query, requiredFlags, forbiddenFlags)
.sortedWith(UidReverseComparator())
.map { it.uid }
} finally {
folder.close()
}
}
}

View file

@ -3,14 +3,17 @@ package com.fsck.k9.backend.imap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fsck.k9.backend.api.Backend;
import com.fsck.k9.backend.api.BackendStorage;
import com.fsck.k9.backend.api.FolderInfo;
import com.fsck.k9.backend.api.SyncConfig;
import com.fsck.k9.backend.api.SyncListener;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.imap.ImapStore;
import org.jetbrains.annotations.NotNull;
@ -25,6 +28,8 @@ public class ImapBackend implements Backend {
private final CommandExpunge commandExpunge;
private final CommandMoveOrCopyMessages commandMoveOrCopyMessages;
private final CommandDeleteAll commandDeleteAll;
private final CommandSearch commandSearch;
private final CommandFetchMessage commandFetchMessage;
public ImapBackend(String accountName, BackendStorage backendStorage, ImapStore imapStore) {
@ -35,6 +40,8 @@ public class ImapBackend implements Backend {
commandMoveOrCopyMessages = new CommandMoveOrCopyMessages(imapStore);
commandGetFolders = new CommandGetFolders(imapStore);
commandDeleteAll = new CommandDeleteAll(imapStore);
commandSearch = new CommandSearch(imapStore);
commandFetchMessage = new CommandFetchMessage(imapStore);
}
@Override
@ -99,4 +106,19 @@ public class ImapBackend implements Backend {
@NotNull List<String> messageServerIds) throws MessagingException {
return commandMoveOrCopyMessages.copyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds);
}
@NotNull
@Override
public List<String> search(@NotNull String folderServerId, @Nullable String query,
@Nullable Set<? extends Flag> requiredFlags, @Nullable Set<? extends Flag> forbiddenFlags)
throws MessagingException {
return commandSearch.search(folderServerId, query, requiredFlags, forbiddenFlags);
}
@NotNull
@Override
public Message fetchMessage(@NotNull String folderServerId, @NotNull String messageServerId,
@NotNull FetchProfile fetchProfile) throws MessagingException {
return commandFetchMessage.fetchMessage(folderServerId, messageServerId, fetchProfile);
}
}

View file

@ -0,0 +1,23 @@
package com.fsck.k9.backend.pop3
import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.store.pop3.Pop3Store
internal class CommandFetchMessage(private val pop3Store: Pop3Store) {
fun fetchMessage(folderServerId: String, messageServerId: String, fetchProfile: FetchProfile): Message {
val folder = pop3Store.getFolder(folderServerId)
try {
val message = folder.getMessage(messageServerId)
folder.fetch(listOf(message), fetchProfile, null)
return message
} finally {
folder.close()
}
}
}

View file

@ -5,8 +5,10 @@ import com.fsck.k9.backend.api.BackendStorage
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.backend.api.SyncConfig
import com.fsck.k9.backend.api.SyncListener
import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Folder
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.store.pop3.Pop3Store
class Pop3Backend(accountName: String, backendStorage: BackendStorage, pop3Store: Pop3Store) : Backend {
@ -14,6 +16,7 @@ class Pop3Backend(accountName: String, backendStorage: BackendStorage, pop3Store
private val commandGetFolders = CommandGetFolders()
private val commandSetFlag = CommandSetFlag(pop3Store)
private val commandDeleteAll = CommandDeleteAll(pop3Store)
private val commandFetchMessage = CommandFetchMessage(pop3Store)
override val supportsSeenFlag: Boolean = false
override val supportsExpunge: Boolean = false
@ -61,4 +64,17 @@ class Pop3Backend(accountName: String, backendStorage: BackendStorage, pop3Store
): Map<String, String>? {
throw UnsupportedOperationException("not supported")
}
override fun search(
folderServerId: String,
query: String?,
requiredFlags: Set<Flag>?,
forbiddenFlags: Set<Flag>?
): List<String> {
throw UnsupportedOperationException("not supported")
}
override fun fetchMessage(folderServerId: String, messageServerId: String, fetchProfile: FetchProfile): Message {
return commandFetchMessage.fetchMessage(folderServerId, messageServerId, fetchProfile)
}
}

View file

@ -0,0 +1,23 @@
package com.fsck.k9.backend.webdav
import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.store.webdav.WebDavStore
internal class CommandFetchMessage(private val webDavStore: WebDavStore) {
fun fetchMessage(folderServerId: String, messageServerId: String, fetchProfile: FetchProfile): Message {
val folder = webDavStore.getFolder(folderServerId)
try {
val message = folder.getMessage(messageServerId)
folder.fetch(listOf(message), fetchProfile, null)
return message
} finally {
folder.close()
}
}
}

View file

@ -5,8 +5,10 @@ import com.fsck.k9.backend.api.BackendStorage
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.backend.api.SyncConfig
import com.fsck.k9.backend.api.SyncListener
import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Folder
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.store.webdav.WebDavStore
@ -17,6 +19,7 @@ class WebDavBackend(accountName: String, backendStorage: BackendStorage, webDavS
private val commandMarkAllAsRead = CommandMarkAllAsRead(webDavStore)
private val commandMoveOrCopyMessages = CommandMoveOrCopyMessages(webDavStore)
private val commandDeleteAll = CommandDeleteAll(webDavStore)
private val commandFetchMessage = CommandFetchMessage(webDavStore)
override val supportsSeenFlag: Boolean = true
override val supportsExpunge: Boolean = true
@ -66,4 +69,17 @@ class WebDavBackend(accountName: String, backendStorage: BackendStorage, webDavS
): Map<String, String>? {
return commandMoveOrCopyMessages.copyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds)
}
override fun search(
folderServerId: String,
query: String?,
requiredFlags: Set<Flag>?,
forbiddenFlags: Set<Flag>?
): List<String> {
throw UnsupportedOperationException("not supported")
}
override fun fetchMessage(folderServerId: String, messageServerId: String, fetchProfile: FetchProfile): Message {
return commandFetchMessage.fetchMessage(folderServerId, messageServerId, fetchProfile)
}
}

View file

@ -590,45 +590,36 @@ public class MessagingController {
listener.remoteSearchStarted(folderServerId);
}
List<Message> extraResults = new ArrayList<>();
List<String> extraResults = new ArrayList<>();
try {
RemoteStore remoteStore = acct.getRemoteStore();
LocalStore localStore = acct.getLocalStore();
if (remoteStore == null || localStore == null) {
throw new MessagingException("Could not get store");
}
Folder remoteFolder = remoteStore.getFolder(folderServerId);
LocalFolder localFolder = localStore.getFolder(folderServerId);
if (remoteFolder == null || localFolder == null) {
if (localFolder == null) {
throw new MessagingException("Folder not found");
}
List<Message> messages = remoteFolder.search(query, requiredFlags, forbiddenFlags);
Backend backend = getBackend(acct);
Timber.i("Remote search got %d results", messages.size());
List<String> messageServerIds = backend.search(folderServerId, query, requiredFlags, forbiddenFlags);
Timber.i("Remote search got %d results", messageServerIds.size());
// There's no need to fetch messages already completely downloaded
List<Message> remoteMessages = localFolder.extractNewMessages(messages);
messages.clear();
messageServerIds = localFolder.extractNewMessages(messageServerIds);
if (listener != null) {
listener.remoteSearchServerQueryComplete(folderServerId, remoteMessages.size(),
listener.remoteSearchServerQueryComplete(folderServerId, messageServerIds.size(),
acct.getRemoteSearchNumResults());
}
Collections.sort(remoteMessages, new UidReverseComparator());
int resultLimit = acct.getRemoteSearchNumResults();
if (resultLimit > 0 && remoteMessages.size() > resultLimit) {
extraResults = remoteMessages.subList(resultLimit, remoteMessages.size());
remoteMessages = remoteMessages.subList(0, resultLimit);
if (resultLimit > 0 && messageServerIds.size() > resultLimit) {
extraResults = messageServerIds.subList(resultLimit, messageServerIds.size());
messageServerIds = messageServerIds.subList(0, resultLimit);
}
loadSearchResultsSynchronous(remoteMessages, localFolder, remoteFolder, listener);
loadSearchResultsSynchronous(acct, messageServerIds, localFolder);
} catch (Exception e) {
if (Thread.currentThread().isInterrupted()) {
Timber.i(e, "Caught exception on aborted remote search; safe to ignore.");
@ -647,8 +638,8 @@ public class MessagingController {
}
public void loadSearchResults(final Account account, final String folderServerId, final List<Message> messages,
final MessagingListener listener) {
public void loadSearchResults(final Account account, final String folderServerId,
final List<String> messageServerIds, final MessagingListener listener) {
threadPool.execute(new Runnable() {
@Override
public void run() {
@ -656,20 +647,18 @@ public class MessagingController {
listener.enableProgressIndicator(true);
}
try {
RemoteStore remoteStore = account.getRemoteStore();
LocalStore localStore = account.getLocalStore();
if (remoteStore == null || localStore == null) {
if (localStore == null) {
throw new MessagingException("Could not get store");
}
Folder remoteFolder = remoteStore.getFolder(folderServerId);
LocalFolder localFolder = localStore.getFolder(folderServerId);
if (remoteFolder == null || localFolder == null) {
if (localFolder == null) {
throw new MessagingException("Folder not found");
}
loadSearchResultsSynchronous(messages, localFolder, remoteFolder, listener);
loadSearchResultsSynchronous(account, messageServerIds, localFolder);
} catch (MessagingException e) {
Timber.e(e, "Exception in loadSearchResults");
} finally {
@ -681,25 +670,23 @@ public class MessagingController {
});
}
private void loadSearchResultsSynchronous(List<Message> messages, LocalFolder localFolder, Folder remoteFolder,
MessagingListener listener) throws MessagingException {
final FetchProfile header = new FetchProfile();
header.add(FetchProfile.Item.FLAGS);
header.add(FetchProfile.Item.ENVELOPE);
final FetchProfile structure = new FetchProfile();
structure.add(FetchProfile.Item.STRUCTURE);
private void loadSearchResultsSynchronous(Account account, List<String> messageServerIds, LocalFolder localFolder)
throws MessagingException {
int i = 0;
for (Message message : messages) {
i++;
LocalMessage localMsg = localFolder.getMessage(message.getUid());
FetchProfile fetchProfile = new FetchProfile();
fetchProfile.add(FetchProfile.Item.FLAGS);
fetchProfile.add(FetchProfile.Item.ENVELOPE);
fetchProfile.add(FetchProfile.Item.STRUCTURE);
if (localMsg == null) {
remoteFolder.fetch(Collections.singletonList(message), header, null);
//fun fact: ImapFolder.fetch can't handle getting STRUCTURE at same time as headers
remoteFolder.fetch(Collections.singletonList(message), structure, null);
Backend backend = getBackend(account);
String folderServerId = localFolder.getServerId();
for (String messageServerId : messageServerIds) {
LocalMessage localMessage = localFolder.getMessage(messageServerId);
if (localMessage == null) {
Message message = backend.fetchMessage(folderServerId, messageServerId, fetchProfile);
localFolder.appendMessages(Collections.singletonList(message));
localMsg = localFolder.getMessage(message.getUid());
}
}
}

View file

@ -69,7 +69,7 @@ public interface MessagingListener {
void remoteSearchStarted(String folder);
void remoteSearchServerQueryComplete(String folderServerId, int numResults, int maxResults);
void remoteSearchFinished(String folderServerId, int numResults, int maxResults, List<Message> extraResults);
void remoteSearchFinished(String folderServerId, int numResults, int maxResults, List<String> extraResults);
void remoteSearchFailed(String folderServerId, String err);
void enableProgressIndicator(boolean enable);

View file

@ -171,7 +171,7 @@ public abstract class SimpleMessagingListener implements MessagingListener {
}
@Override
public void remoteSearchFinished(String folderServerId, int numResults, int maxResults, List<Message> extraResults) {
public void remoteSearchFinished(String folderServerId, int numResults, int maxResults, List<String> extraResults) {
}
@Override

View file

@ -183,7 +183,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
private boolean remoteSearchPerformed = false;
private Future<?> remoteSearchFuture = null;
private List<Message> extraSearchResults;
private List<String> extraSearchResults;
private String title;
private LocalSearch search = null;
@ -360,7 +360,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
int numResults = extraSearchResults.size();
int limit = account.getRemoteSearchNumResults();
List<Message> toProcess = extraSearchResults;
List<String> toProcess = extraSearchResults;
if (limit > 0 && numResults > limit) {
toProcess = toProcess.subList(0, limit);
@ -1318,7 +1318,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
@Override
public void remoteSearchFinished(String folderServerId, int numResults, int maxResults, List<Message> extraResults) {
public void remoteSearchFinished(String folderServerId, int numResults, int maxResults, List<String> extraResults) {
handler.progress(false);
handler.remoteSearchFinished();
extraSearchResults = extraResults;

View file

@ -2252,32 +2252,32 @@ public class LocalFolder extends Folder<LocalMessage> {
return new ThreadInfo(threadId, msgId, messageId, rootId, parentId);
}
public List<Message> extractNewMessages(final List<Message> messages)
public List<String> extractNewMessages(final List<String> messageServerIds)
throws MessagingException {
try {
return this.localStore.getDatabase().execute(false, new DbCallback<List<Message>>() {
return this.localStore.getDatabase().execute(false, new DbCallback<List<String>>() {
@Override
public List<Message> doDbWork(final SQLiteDatabase db) throws WrappedException {
public List<String> doDbWork(final SQLiteDatabase db) throws WrappedException {
try {
open(OPEN_MODE_RW);
} catch (MessagingException e) {
throw new WrappedException(e);
}
List<Message> result = new ArrayList<>();
List<String> result = new ArrayList<>();
List<String> selectionArgs = new ArrayList<>();
Set<String> existingMessages = new HashSet<>();
int start = 0;
while (start < messages.size()) {
while (start < messageServerIds.size()) {
StringBuilder selection = new StringBuilder();
selection.append("folder_id = ? AND UID IN (");
selectionArgs.add(Long.toString(databaseId));
int count = Math.min(messages.size() - start, LocalStore.UID_CHECK_BATCH_SIZE);
int count = Math.min(messageServerIds.size() - start, LocalStore.UID_CHECK_BATCH_SIZE);
for (int i = start, end = start + count; i < end; i++) {
if (i > start) {
@ -2286,7 +2286,7 @@ public class LocalFolder extends Folder<LocalMessage> {
selection.append("?");
}
selectionArgs.add(messages.get(i).getUid());
selectionArgs.add(messageServerIds.get(i));
}
selection.append(")");
@ -2305,9 +2305,9 @@ public class LocalFolder extends Folder<LocalMessage> {
}
for (int i = start, end = start + count; i < end; i++) {
Message message = messages.get(i);
if (!existingMessages.contains(message.getUid())) {
result.add(message);
String messageServerId = messageServerIds.get(i);
if (!existingMessages.contains(messageServerId)) {
result.add(messageServerId);
}
}

View file

@ -9,7 +9,7 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeMessage;
class ImapMessage extends MimeMessage {
public class ImapMessage extends MimeMessage {
ImapMessage(String uid, Folder folder) {
this.mUid = uid;
this.mFolder = folder;

View file

@ -8,7 +8,7 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeMessage;
class Pop3Message extends MimeMessage {
public class Pop3Message extends MimeMessage {
Pop3Message(String uid, Pop3Folder folder) {
mUid = uid;
mFolder = folder;

View file

@ -16,7 +16,7 @@ import static com.fsck.k9.mail.helper.UrlEncodingHelper.encodeUtf8;
/**
* A WebDav Message
*/
class WebDavMessage extends MimeMessage {
public class WebDavMessage extends MimeMessage {
private String mUrl = "";