Move folder list sync logic into Backend implementations

This commit is contained in:
cketti 2018-07-22 15:37:54 +02:00
parent dc785a8e92
commit de49c9d6c2
18 changed files with 168 additions and 216 deletions

View file

@ -44,7 +44,6 @@ import com.fsck.k9.K9.Intents;
import com.fsck.k9.Preferences;
import com.fsck.k9.backend.BackendManager;
import com.fsck.k9.backend.api.Backend;
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.cache.EmailProviderCache;
@ -423,51 +422,9 @@ public class MessagingController {
List<LocalFolder> localFolders = null;
try {
Backend backend = getBackend(account);
List<FolderInfo> folders = backend.getFolders();
backend.refreshFolderList();
LocalStore localStore = account.getLocalStore();
Map<String, String> remoteFolderNameMap = new HashMap<>();
List<LocalFolder> foldersToCreate = new LinkedList<>();
localFolders = localStore.getPersonalNamespaces(false);
Set<String> localFolderServerIds = new HashSet<>();
for (Folder localFolder : localFolders) {
localFolderServerIds.add(localFolder.getServerId());
}
for (FolderInfo folder : folders) {
String folderServerId = folder.getServerId();
if (!localFolderServerIds.contains(folderServerId)) {
LocalFolder localFolder = localStore.getFolder(folderServerId);
foldersToCreate.add(localFolder);
}
remoteFolderNameMap.put(folderServerId, folder.getName());
}
localStore.createFolders(foldersToCreate, account.getDisplayCount());
localFolders = localStore.getPersonalNamespaces(false);
/*
* Clear out any folders that are no longer on the remote store.
*/
for (LocalFolder localFolder : localFolders) {
String localFolderServerId = localFolder.getServerId();
// FIXME: This is a hack used to clean up when we accidentally created the
// special placeholder folder "-NONE-".
if (K9.FOLDER_NONE.equals(localFolderServerId)) {
localFolder.delete(false);
}
boolean folderExistsOnServer = remoteFolderNameMap.containsKey(localFolderServerId);
if (folderExistsOnServer) {
String folderName = remoteFolderNameMap.get(localFolderServerId);
localFolder.setName(folderName);
} else if (!account.isSpecialFolder(localFolderServerId)) {
localFolder.delete(false);
}
}
localFolders = localStore.getPersonalNamespaces(false);
for (MessagingListener l : getListeners(listener)) {

View file

@ -362,26 +362,6 @@ class K9BackendFolder(
}
}
private fun <T> LockableDatabase.query(
table: String,
columns: Array<String>,
selection: String,
vararg selectionArgs: String,
block: (Cursor) -> T
): T {
return execute(false) { db ->
val cursor = db.query(table, columns, selection, selectionArgs, null, null, null)
cursor.use(block)
}
}
private fun <T> LockableDatabase.rawQuery(sql: String, vararg selectionArgs: String, block: (Cursor) -> T): T {
return execute(false) { db ->
val cursor = db.rawQuery(sql, selectionArgs)
cursor.use(block)
}
}
private fun Cursor.getLongOrNull(columnIndex: Int): Long? = if (isNull(columnIndex)) null else getLong(columnIndex)
private fun String.toMoreMessages(): MoreMessages = when (this) {

View file

@ -7,6 +7,7 @@ import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.backend.api.BackendFolder
import com.fsck.k9.backend.api.BackendStorage
import com.fsck.k9.backend.api.FolderInfo
class K9BackendStorage(
private val preferences: Preferences,
@ -20,6 +21,41 @@ class K9BackendStorage(
return K9BackendFolder(preferences, account, localStore, folderServerId)
}
override fun getFolderServerIds(): List<String> {
return database.query("folders", arrayOf("server_id"), "server_id != ?", account.outboxFolder) { cursor ->
val folderServerIds = mutableListOf<String>()
while (cursor.moveToNext()) {
folderServerIds.add(cursor.getString(0))
}
folderServerIds
}
}
override fun createFolders(folders: List<FolderInfo>) {
if (folders.isEmpty()) return
val localFolders = folders.map { localStore.getFolder(it.serverId, it.name) }
localStore.createFolders(localFolders, account.displayCount)
}
override fun deleteFolders(folderServerIds: List<String>) {
folderServerIds.asSequence()
.filterNot { account.isSpecialFolder(it) }
.map { localStore.getFolder(it) }
.forEach { it.delete() }
}
override fun changeFolder(folderServerId: String, name: String) {
database.execute(false) { db ->
val values = ContentValues().apply {
put("name", name)
}
db.update("folders", values, "server_id = ?", arrayOf(folderServerId))
}
}
override fun getExtraString(name: String): String? {
return database.execute(false) { db ->
val cursor = db.query(

View file

@ -101,9 +101,13 @@ public class LocalFolder extends Folder<LocalMessage> {
public LocalFolder(LocalStore localStore, String serverId) {
super();
this(localStore, serverId, null);
}
public LocalFolder(LocalStore localStore, String serverId, String name) {
this.localStore = localStore;
this.serverId = serverId;
this.name = name;
attachmentInfoExtractor = localStore.getAttachmentInfoExtractor();
if (getAccount().getInboxFolder().equals(getServerId())) {

View file

@ -394,6 +394,10 @@ public class LocalStore {
return new LocalFolder(this, serverId);
}
public LocalFolder getFolder(String serverId, String name) {
return new LocalFolder(this, serverId, name);
}
// TODO this takes about 260-300ms, seems slow.
public List<LocalFolder> getPersonalNamespaces(boolean forceListAll) throws MessagingException {
final List<LocalFolder> folders = new LinkedList<>();

View file

@ -0,0 +1,24 @@
package com.fsck.k9.mailstore
import android.database.Cursor
internal fun <T> LockableDatabase.query(
table: String,
columns: Array<String>,
selection: String?,
vararg selectionArgs: String,
block: (Cursor) -> T
): T {
return execute(false) { db ->
val cursor = db.query(table, columns, selection, selectionArgs, null, null, null)
cursor.use(block)
}
}
internal fun <T> LockableDatabase.rawQuery(sql: String, vararg selectionArgs: String, block: (Cursor) -> T): T {
return execute(false) { db ->
val cursor = db.rawQuery(sql, selectionArgs)
cursor.use(block)
}
}

View file

@ -17,7 +17,6 @@ import com.fsck.k9.K9;
import com.fsck.k9.K9RobolectricTest;
import com.fsck.k9.Preferences;
import com.fsck.k9.backend.api.Backend;
import com.fsck.k9.backend.api.FolderInfo;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException;
@ -54,7 +53,6 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
@ -248,95 +246,10 @@ public class MessagingControllerTest extends K9RobolectricTest {
}
@Test
public void refreshRemoteSynchronous_shouldCreateFoldersFromRemote() throws MessagingException {
FolderInfo remoteFolderInfo = new FolderInfo("NewFolder", "Folder Name");
List<FolderInfo> folderInfoList = Collections.singletonList(remoteFolderInfo);
when(backend.getFolders()).thenAnswer(createAnswer(folderInfoList));
LocalFolder newLocalFolder = mock(LocalFolder.class);
when(localStore.getFolder("NewFolder")).thenReturn(newLocalFolder);
public void refreshRemoteSynchronous_shouldCallBackend() throws MessagingException {
controller.refreshRemoteSynchronous(account, listener);
verify(localStore).createFolders(eq(Collections.singletonList(newLocalFolder)), anyInt());
}
@Test
public void refreshRemoteSynchronous_shouldDeleteFoldersNotOnRemote() throws MessagingException {
LocalFolder oldLocalFolder = mock(LocalFolder.class);
when(oldLocalFolder.getServerId()).thenReturn("OldLocalFolder");
when(localStore.getPersonalNamespaces(false)).thenReturn(Collections.singletonList(oldLocalFolder));
List<FolderInfo> folderInfoList = Collections.emptyList();
when(backend.getFolders()).thenAnswer(createAnswer(folderInfoList));
controller.refreshRemoteSynchronous(account, listener);
verify(oldLocalFolder).delete(false);
}
@Test
public void refreshRemoteSynchronous_shouldNotDeleteFoldersOnRemote() throws MessagingException {
configureBackendWithFolder();
when(localStore.getPersonalNamespaces(false)).thenReturn(Collections.singletonList(localFolder));
controller.refreshRemoteSynchronous(account, listener);
verify(localFolder, never()).delete(false);
}
@Test
public void refreshRemoteSynchronous_shouldNotDeleteSpecialFoldersNotOnRemote() throws MessagingException {
LocalFolder missingSpecialFolder = mock(LocalFolder.class);
when(account.isSpecialFolder("Outbox")).thenReturn(true);
when(missingSpecialFolder.getServerId()).thenReturn("Outbox");
when(localStore.getPersonalNamespaces(false))
.thenReturn(Collections.singletonList(missingSpecialFolder));
List<FolderInfo> folderInfoList = Collections.emptyList();
when(backend.getFolders()).thenAnswer(createAnswer(folderInfoList));
controller.refreshRemoteSynchronous(account, listener);
verify(missingSpecialFolder, never()).delete(false);
}
public static <T> Answer<T> createAnswer(final T value) {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return value;
}
};
}
@Test
public void refreshRemoteSynchronous_shouldProvideFolderList() throws MessagingException {
configureBackendWithFolder();
List<LocalFolder> folders = Collections.singletonList(localFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(folders);
controller.refreshRemoteSynchronous(account, listener);
verify(listener).listFolders(account, folders);
}
@Test
public void refreshRemoteSynchronous_shouldNotifyFinishedAfterSuccess() throws MessagingException {
configureBackendWithFolder();
List<LocalFolder> folders = Collections.singletonList(localFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(folders);
controller.refreshRemoteSynchronous(account, listener);
verify(listener).listFoldersFinished(account);
}
@Test
public void refreshRemoteSynchronous_shouldNotNotifyFinishedAfterFailure() throws MessagingException {
configureBackendWithFolder();
when(localStore.getPersonalNamespaces(false)).thenThrow(new MessagingException("Test"));
controller.refreshRemoteSynchronous(account, listener);
verify(listener, never()).listFoldersFinished(account);
verify(backend).refreshFolderList();
}
@Test
@ -370,7 +283,6 @@ public class MessagingControllerTest extends K9RobolectricTest {
private void setupRemoteSearch() throws Exception {
setAccountsInPreferences(Collections.singletonMap("1", account));
configureBackendWithFolder();
remoteMessages = new ArrayList<>();
Collections.addAll(remoteMessages, "oldMessageUid", "newMessageUid1", "newMessageUid2");
@ -628,12 +540,6 @@ public class MessagingControllerTest extends K9RobolectricTest {
when(localStore.getPersonalNamespaces(false)).thenReturn(Collections.singletonList(localFolder));
}
private void configureBackendWithFolder() throws MessagingException {
FolderInfo remoteFolderInfo = new FolderInfo(FOLDER_NAME, FOLDER_NAME);
List<FolderInfo> folderInfoList = Collections.singletonList(remoteFolderInfo);
when(backend.getFolders()).thenAnswer(createAnswer(folderInfoList));
}
private void setAccountsInPreferences(Map<String, Account> newAccounts)
throws Exception {
Field accounts = Preferences.class.getDeclaredField("accounts");

View file

@ -22,7 +22,7 @@ interface Backend {
val isPushCapable: Boolean
@Throws(MessagingException::class)
fun getFolders(): List<FolderInfo>
fun refreshFolderList()
// TODO: Add a way to cancel the sync process
fun sync(folder: String, syncConfig: SyncConfig, listener: SyncListener, providedRemoteFolder: Folder<*>?)

View file

@ -3,6 +3,12 @@ package com.fsck.k9.backend.api
interface BackendStorage {
fun getFolder(folderServerId: String): BackendFolder
fun getFolderServerIds(): List<String>
fun createFolders(folders: List<FolderInfo>)
fun deleteFolders(folderServerIds: List<String>)
fun changeFolder(folderServerId: String, name: String)
fun getExtraString(name: String): String?
fun setExtraString(name: String, value: String)
fun getExtraNumber(name: String): Long?

View file

@ -1,14 +0,0 @@
package com.fsck.k9.backend.imap
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.store.imap.ImapStore
internal class CommandGetFolders(private val imapStore: ImapStore) {
fun getFolders(): List<FolderInfo> {
return imapStore.personalNamespaces.map {
FolderInfo(it.serverId, it.name)
}
}
}

View file

@ -0,0 +1,31 @@
package com.fsck.k9.backend.imap
import com.fsck.k9.backend.api.BackendStorage
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.store.imap.ImapStore
internal class CommandRefreshFolderList(
private val backendStorage: BackendStorage,
private val imapStore: ImapStore
) {
fun refreshFolderList() {
val foldersOnServer = imapStore.personalNamespaces
val oldFolderServerIds = backendStorage.getFolderServerIds()
val foldersToCreate = mutableListOf<FolderInfo>()
for (folder in foldersOnServer) {
if (folder.serverId !in oldFolderServerIds) {
foldersToCreate.add(FolderInfo(folder.serverId, folder.name))
} else {
backendStorage.changeFolder(folder.serverId, folder.name)
}
}
backendStorage.createFolders(foldersToCreate)
val newFolderServerIds = foldersOnServer.map { it.serverId }
val removedFolderServerIds = oldFolderServerIds - newFolderServerIds
backendStorage.deleteFolders(removedFolderServerIds)
}
}

View file

@ -7,7 +7,6 @@ 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.BodyFactory;
@ -32,7 +31,7 @@ public class ImapBackend implements Backend {
private final PowerManager powerManager;
private final SmtpTransport smtpTransport;
private final ImapSync imapSync;
private final CommandGetFolders commandGetFolders;
private final CommandRefreshFolderList commandRefreshFolderList;
private final CommandSetFlag commandSetFlag;
private final CommandMarkAllAsRead commandMarkAllAsRead;
private final CommandExpunge commandExpunge;
@ -55,7 +54,7 @@ public class ImapBackend implements Backend {
commandMarkAllAsRead = new CommandMarkAllAsRead(imapStore);
commandExpunge = new CommandExpunge(imapStore);
commandMoveOrCopyMessages = new CommandMoveOrCopyMessages(imapStore);
commandGetFolders = new CommandGetFolders(imapStore);
commandRefreshFolderList = new CommandRefreshFolderList(backendStorage, imapStore);
commandDeleteAll = new CommandDeleteAll(imapStore);
commandSearch = new CommandSearch(imapStore);
commandFetchMessage = new CommandFetchMessage(imapStore);
@ -98,10 +97,9 @@ public class ImapBackend implements Backend {
return true;
}
@NotNull
@Override
public List<FolderInfo> getFolders() {
return commandGetFolders.getFolders();
public void refreshFolderList() {
commandRefreshFolderList.refreshFolderList();
}
@Override

View file

@ -1,12 +0,0 @@
package com.fsck.k9.backend.pop3
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.store.pop3.Pop3Folder
internal class CommandGetFolders {
fun getFolders(): List<FolderInfo> {
return listOf(FolderInfo(Pop3Folder.INBOX, Pop3Folder.INBOX))
}
}

View file

@ -0,0 +1,17 @@
package com.fsck.k9.backend.pop3
import com.fsck.k9.backend.api.BackendStorage
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.store.pop3.Pop3Folder
internal class CommandRefreshFolderList(private val backendStorage: BackendStorage) {
fun refreshFolderList() {
val folderServerIds = backendStorage.getFolderServerIds()
if (Pop3Folder.INBOX !in folderServerIds) {
val inbox = FolderInfo(Pop3Folder.INBOX, Pop3Folder.INBOX)
backendStorage.createFolders(listOf(inbox))
}
}
}

View file

@ -2,7 +2,6 @@ package com.fsck.k9.backend.pop3
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.BodyFactory
@ -23,7 +22,7 @@ class Pop3Backend(
private val smtpTransport: SmtpTransport
) : Backend {
private val pop3Sync: Pop3Sync = Pop3Sync(accountName, backendStorage, pop3Store)
private val commandGetFolders = CommandGetFolders()
private val commandRefreshFolderList = CommandRefreshFolderList(backendStorage)
private val commandSetFlag = CommandSetFlag(pop3Store)
private val commandDeleteAll = CommandDeleteAll(pop3Store)
private val commandFetchMessage = CommandFetchMessage(pop3Store)
@ -36,8 +35,8 @@ class Pop3Backend(
override val supportsSearchByDate = false
override val isPushCapable = false
override fun getFolders(): List<FolderInfo> {
return commandGetFolders.getFolders()
override fun refreshFolderList() {
commandRefreshFolderList.refreshFolderList()
}
override fun sync(folder: String, syncConfig: SyncConfig, listener: SyncListener, providedRemoteFolder: Folder<*>?) {

View file

@ -1,14 +0,0 @@
package com.fsck.k9.backend.webdav
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.store.webdav.WebDavStore
internal class CommandGetFolders(private val webDavStore: WebDavStore) {
fun getFolders(): List<FolderInfo> {
return webDavStore.personalNamespaces.map {
FolderInfo(it.serverId, it.name)
}
}
}

View file

@ -0,0 +1,31 @@
package com.fsck.k9.backend.webdav
import com.fsck.k9.backend.api.BackendStorage
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.store.webdav.WebDavStore
internal class CommandRefreshFolderList(
private val backendStorage: BackendStorage,
private val webDavStore: WebDavStore
) {
fun refreshFolderList() {
val foldersOnServer = webDavStore.personalNamespaces
val oldFolderServerIds = backendStorage.getFolderServerIds()
val foldersToCreate = mutableListOf<FolderInfo>()
for (folder in foldersOnServer) {
if (folder.serverId !in oldFolderServerIds) {
foldersToCreate.add(FolderInfo(folder.serverId, folder.name))
} else {
backendStorage.changeFolder(folder.serverId, folder.name)
}
}
backendStorage.createFolders(foldersToCreate)
val newFolderServerIds = foldersOnServer.map { it.serverId }
val removedFolderServerIds = oldFolderServerIds - newFolderServerIds
backendStorage.deleteFolders(removedFolderServerIds)
}
}

View file

@ -2,7 +2,6 @@ package com.fsck.k9.backend.webdav
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.BodyFactory
@ -24,7 +23,7 @@ class WebDavBackend(
private val webDavTransport: WebDavTransport
) : Backend {
private val webDavSync: WebDavSync = WebDavSync(accountName, backendStorage, webDavStore)
private val commandGetFolders = CommandGetFolders(webDavStore)
private val commandGetFolders = CommandRefreshFolderList(backendStorage, webDavStore)
private val commandSetFlag = CommandSetFlag(webDavStore)
private val commandMarkAllAsRead = CommandMarkAllAsRead(webDavStore)
private val commandMoveOrCopyMessages = CommandMoveOrCopyMessages(webDavStore)
@ -40,8 +39,8 @@ class WebDavBackend(
override val supportsSearchByDate = false
override val isPushCapable = false
override fun getFolders(): List<FolderInfo> {
return commandGetFolders.getFolders()
override fun refreshFolderList() {
commandGetFolders.refreshFolderList()
}
override fun sync(folder: String, syncConfig: SyncConfig, listener: SyncListener, providedRemoteFolder: Folder<*>?) {