Merge pull request #5209 from k9mail/convert_to_kotlin

This commit is contained in:
cketti 2021-03-24 15:03:05 +01:00 committed by GitHub
commit cc2413a180
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 316 additions and 385 deletions

View file

@ -13,7 +13,15 @@ import com.fsck.k9.setup.ServerNameSuggester
import org.koin.dsl.module import org.koin.dsl.module
val mainModule = module { val mainModule = module {
single { Preferences.getPreferences(get()) } single {
Preferences(
context = get(),
storagePersister = get(),
localStoreProvider = get(),
localKeyStoreManager = get(),
accountPreferenceSerializer = get()
)
}
single { get<Context>().resources } single { get<Context>().resources }
single { get<Context>().contentResolver } single { get<Context>().contentResolver }
single { LocalStoreProvider() } single { LocalStoreProvider() }

View file

@ -1,329 +0,0 @@
package com.fsck.k9;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import android.content.Context;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import com.fsck.k9.backend.BackendManager;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.mailstore.LocalStoreProvider;
import com.fsck.k9.preferences.Storage;
import com.fsck.k9.preferences.StorageEditor;
import com.fsck.k9.preferences.StoragePersister;
import timber.log.Timber;
public class Preferences {
private static Preferences preferences;
private AccountPreferenceSerializer accountPreferenceSerializer;
public static synchronized Preferences getPreferences(Context context) {
if (preferences == null) {
Context appContext = context.getApplicationContext();
CoreResourceProvider resourceProvider = DI.get(CoreResourceProvider.class);
LocalKeyStoreManager localKeyStoreManager = DI.get(LocalKeyStoreManager.class);
AccountPreferenceSerializer accountPreferenceSerializer = DI.get(AccountPreferenceSerializer.class);
LocalStoreProvider localStoreProvider = DI.get(LocalStoreProvider.class);
StoragePersister storagePersister = DI.get(StoragePersister.class);
preferences = new Preferences(appContext, resourceProvider, storagePersister, localStoreProvider, localKeyStoreManager, accountPreferenceSerializer);
}
return preferences;
}
private Storage storage;
@GuardedBy("accountLock")
private Map<String, Account> accounts = null;
@GuardedBy("accountLock")
private List<Account> accountsInOrder = null;
@GuardedBy("accountLock")
private Account newAccount;
private final List<AccountsChangeListener> accountsChangeListeners = new CopyOnWriteArrayList<>();
private final Context context;
private final LocalStoreProvider localStoreProvider;
private final CoreResourceProvider resourceProvider;
private final LocalKeyStoreManager localKeyStoreManager;
private final StoragePersister storagePersister;
private final Object accountLock = new Object();
private Preferences(Context context, CoreResourceProvider resourceProvider,
StoragePersister storagePersister, LocalStoreProvider localStoreProvider,
LocalKeyStoreManager localKeyStoreManager,
AccountPreferenceSerializer accountPreferenceSerializer) {
this.storage = new Storage();
this.storagePersister = storagePersister;
this.context = context;
this.resourceProvider = resourceProvider;
this.localStoreProvider = localStoreProvider;
this.localKeyStoreManager = localKeyStoreManager;
this.accountPreferenceSerializer = accountPreferenceSerializer;
Map<String, String> persistedStorageValues = storagePersister.loadValues();
storage.replaceAll(persistedStorageValues);
if (storage.isEmpty()) {
Timber.i("Preferences storage is zero-size, importing from Android-style preferences");
StorageEditor editor = createStorageEditor();
editor.copy(context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE));
editor.commit();
}
}
public StorageEditor createStorageEditor() {
return storagePersister.createStorageEditor(storage);
}
@RestrictTo(Scope.TESTS)
public void clearAccounts() {
synchronized (accountLock) {
accounts = new HashMap<>();
accountsInOrder = new LinkedList<>();
}
}
public void loadAccounts() {
synchronized (accountLock) {
accounts = new HashMap<>();
accountsInOrder = new LinkedList<>();
String accountUuids = getStorage().getString("accountUuids", null);
if ((accountUuids != null) && (accountUuids.length() != 0)) {
String[] uuids = accountUuids.split(",");
for (String uuid : uuids) {
Account newAccount = new Account(uuid);
accountPreferenceSerializer.loadAccount(newAccount, storage);
accounts.put(uuid, newAccount);
accountsInOrder.add(newAccount);
}
}
if ((newAccount != null) && newAccount.getAccountNumber() != -1) {
accounts.put(newAccount.getUuid(), newAccount);
if (!accountsInOrder.contains(newAccount)) {
accountsInOrder.add(newAccount);
}
newAccount = null;
}
}
}
/**
* Returns an array of the accounts on the system. If no accounts are
* registered the method returns an empty array.
*
* @return all accounts
*/
public List<Account> getAccounts() {
synchronized (accountLock) {
if (accounts == null) {
loadAccounts();
}
return Collections.unmodifiableList(new ArrayList<>(accountsInOrder));
}
}
/**
* Returns an array of the accounts on the system. If no accounts are
* registered the method returns an empty array.
*
* @return all accounts with {@link Account#isAvailable(Context)}
*/
public Collection<Account> getAvailableAccounts() {
List<Account> allAccounts = getAccounts();
Collection<Account> result = new ArrayList<>(allAccounts.size());
for (Account account : allAccounts) {
if (account.isAvailable(context)) {
result.add(account);
}
}
return result;
}
public Account getAccount(String uuid) {
synchronized (accountLock) {
if (accounts == null) {
loadAccounts();
}
return accounts.get(uuid);
}
}
public Account newAccount() {
synchronized (accountLock) {
String accountUuid = UUID.randomUUID().toString();
newAccount = new Account(accountUuid);
accountPreferenceSerializer.loadDefaults(newAccount);
accounts.put(newAccount.getUuid(), newAccount);
accountsInOrder.add(newAccount);
return newAccount;
}
}
public void deleteAccount(Account account) {
synchronized (accountLock) {
if (accounts != null) {
accounts.remove(account.getUuid());
}
if (accountsInOrder != null) {
accountsInOrder.remove(account);
}
try {
getBackendManager().removeBackend(account);
} catch (Exception e) {
Timber.e(e, "Failed to reset remote store for account %s", account.getUuid());
}
LocalStore.removeAccount(account);
StorageEditor storageEditor = createStorageEditor();
accountPreferenceSerializer.delete(storageEditor, storage, account);
storageEditor.commit();
localKeyStoreManager.deleteCertificates(account);
if (newAccount == account) {
newAccount = null;
}
}
notifyListeners();
}
/**
* Returns the Account marked as default. If no account is marked as default
* the first account in the list is marked as default and then returned. If
* there are no accounts on the system the method returns null.
*/
public Account getDefaultAccount() {
Account defaultAccount;
synchronized (accountLock) {
String defaultAccountUuid = getStorage().getString("defaultAccountUuid", null);
defaultAccount = getAccount(defaultAccountUuid);
}
if (defaultAccount == null) {
Collection<Account> accounts = getAvailableAccounts();
if (!accounts.isEmpty()) {
defaultAccount = accounts.iterator().next();
setDefaultAccount(defaultAccount);
}
}
return defaultAccount;
}
public void setDefaultAccount(Account account) {
createStorageEditor().putString("defaultAccountUuid", account.getUuid()).commit();
}
public Storage getStorage() {
return storage;
}
private BackendManager getBackendManager() {
return DI.get(BackendManager.class);
}
public void saveAccount(Account account) {
ensureAssignedAccountNumber(account);
processChangedValues(account);
StorageEditor editor = createStorageEditor();
accountPreferenceSerializer.save(editor, storage, account);
editor.commit();
notifyListeners();
}
private void ensureAssignedAccountNumber(Account account) {
if (account.getAccountNumber() != Account.UNASSIGNED_ACCOUNT_NUMBER) {
return;
}
int accountNumber = generateAccountNumber();
account.setAccountNumber(accountNumber);
}
private void processChangedValues(Account account) {
if (account.isChangedVisibleLimits()) {
try {
localStoreProvider.getInstance(account).resetVisibleLimits(account.getDisplayCount());
} catch (MessagingException e) {
Timber.e(e, "Failed to load LocalStore!");
}
}
account.resetChangeMarkers();
}
public int generateAccountNumber() {
List<Integer> accountNumbers = getExistingAccountNumbers();
return findNewAccountNumber(accountNumbers);
}
private List<Integer> getExistingAccountNumbers() {
List<Account> accounts = getAccounts();
List<Integer> accountNumbers = new ArrayList<>(accounts.size());
for (Account a : accounts) {
accountNumbers.add(a.getAccountNumber());
}
return accountNumbers;
}
private static int findNewAccountNumber(List<Integer> accountNumbers) {
int newAccountNumber = -1;
Collections.sort(accountNumbers);
for (int accountNumber : accountNumbers) {
if (accountNumber > newAccountNumber + 1) {
break;
}
newAccountNumber = accountNumber;
}
newAccountNumber++;
return newAccountNumber;
}
public void move(Account account, boolean mUp) {
synchronized (accountLock) {
StorageEditor storageEditor = createStorageEditor();
accountPreferenceSerializer.move(storageEditor, account, storage, mUp);
storageEditor.commit();
loadAccounts();
}
notifyListeners();
}
private void notifyListeners() {
for (AccountsChangeListener listener : accountsChangeListeners) {
listener.onAccountsChanged();
}
}
public void addOnAccountsChangeListener(@NonNull AccountsChangeListener accountsChangeListener) {
accountsChangeListeners.add(accountsChangeListener);
}
public void removeOnAccountsChangeListener(@NonNull AccountsChangeListener accountsChangeListener) {
accountsChangeListeners.remove(accountsChangeListener);
}
}

View file

@ -0,0 +1,263 @@
package com.fsck.k9
import android.content.Context
import androidx.annotation.GuardedBy
import androidx.annotation.RestrictTo
import com.fsck.k9.backend.BackendManager
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mailstore.LocalStore
import com.fsck.k9.mailstore.LocalStoreProvider
import com.fsck.k9.preferences.Storage
import com.fsck.k9.preferences.StorageEditor
import com.fsck.k9.preferences.StoragePersister
import java.util.HashMap
import java.util.LinkedList
import java.util.UUID
import java.util.concurrent.CopyOnWriteArrayList
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
class Preferences internal constructor(
private val context: Context,
private val storagePersister: StoragePersister,
private val localStoreProvider: LocalStoreProvider,
private val localKeyStoreManager: LocalKeyStoreManager,
private val accountPreferenceSerializer: AccountPreferenceSerializer
) : KoinComponent {
private val backendManager: BackendManager by inject()
private val accountLock = Any()
@GuardedBy("accountLock")
private var accountsMap: MutableMap<String, Account>? = null
@GuardedBy("accountLock")
private var accountsInOrder = mutableListOf<Account>()
@GuardedBy("accountLock")
private var newAccount: Account? = null
private val accountsChangeListeners = CopyOnWriteArrayList<AccountsChangeListener>()
val storage = Storage()
init {
val persistedStorageValues = storagePersister.loadValues()
storage.replaceAll(persistedStorageValues)
if (storage.isEmpty) {
Timber.i("Preferences storage is zero-size, importing from Android-style preferences")
val editor = createStorageEditor()
editor.copy(context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE))
editor.commit()
}
}
fun createStorageEditor(): StorageEditor {
return storagePersister.createStorageEditor(storage)
}
@RestrictTo(RestrictTo.Scope.TESTS)
fun clearAccounts() {
synchronized(accountLock) {
accountsMap = HashMap()
accountsInOrder = LinkedList()
}
}
fun loadAccounts() {
synchronized(accountLock) {
val accounts = mutableMapOf<String, Account>()
val accountsInOrder = mutableListOf<Account>()
val accountUuids = storage.getString("accountUuids", null)
if (!accountUuids.isNullOrEmpty()) {
accountUuids.split(",").forEach { uuid ->
val newAccount = Account(uuid)
accountPreferenceSerializer.loadAccount(newAccount, storage)
accounts[uuid] = newAccount
accountsInOrder.add(newAccount)
}
}
newAccount?.takeIf { it.accountNumber != -1 }?.let { newAccount ->
accounts[newAccount.uuid] = newAccount
if (newAccount !in accountsInOrder) {
accountsInOrder.add(newAccount)
}
this.newAccount = null
}
this.accountsMap = accounts
this.accountsInOrder = accountsInOrder
}
}
val accounts: List<Account>
get() {
synchronized(accountLock) {
if (accountsMap == null) {
loadAccounts()
}
return accountsInOrder.toList()
}
}
val availableAccounts: Collection<Account>
get() = accounts.filter { it.isAvailable(context) }
fun getAccount(uuid: String): Account? {
synchronized(accountLock) {
if (accountsMap == null) {
loadAccounts()
}
return accountsMap!![uuid]
}
}
fun newAccount(): Account {
val accountUuid = UUID.randomUUID().toString()
val account = Account(accountUuid)
accountPreferenceSerializer.loadDefaults(account)
synchronized(accountLock) {
newAccount = account
accountsMap!![account.uuid] = account
accountsInOrder.add(account)
}
return account
}
fun deleteAccount(account: Account) {
synchronized(accountLock) {
accountsMap?.remove(account.uuid)
accountsInOrder.remove(account)
try {
backendManager.removeBackend(account)
} catch (e: Exception) {
Timber.e(e, "Failed to reset remote store for account %s", account.uuid)
}
LocalStore.removeAccount(account)
val storageEditor = createStorageEditor()
accountPreferenceSerializer.delete(storageEditor, storage, account)
storageEditor.commit()
localKeyStoreManager.deleteCertificates(account)
if (account === newAccount) {
newAccount = null
}
}
notifyListeners()
}
var defaultAccount: Account?
get() {
return getDefaultAccountOrNull() ?: availableAccounts.firstOrNull()?.also { newDefaultAccount ->
defaultAccount = newDefaultAccount
}
}
set(account) {
requireNotNull(account)
createStorageEditor()
.putString("defaultAccountUuid", account.uuid)
.commit()
}
private fun getDefaultAccountOrNull(): Account? {
return synchronized(accountLock) {
storage.getString("defaultAccountUuid", null)?.let { defaultAccountUuid ->
getAccount(defaultAccountUuid)
}
}
}
fun saveAccount(account: Account) {
ensureAssignedAccountNumber(account)
processChangedValues(account)
val editor = createStorageEditor()
accountPreferenceSerializer.save(editor, storage, account)
editor.commit()
notifyListeners()
}
private fun ensureAssignedAccountNumber(account: Account) {
if (account.accountNumber != Account.UNASSIGNED_ACCOUNT_NUMBER) return
account.accountNumber = generateAccountNumber()
}
private fun processChangedValues(account: Account) {
if (account.isChangedVisibleLimits) {
try {
localStoreProvider.getInstance(account).resetVisibleLimits(account.displayCount)
} catch (e: MessagingException) {
Timber.e(e, "Failed to load LocalStore!")
}
}
account.resetChangeMarkers()
}
fun generateAccountNumber(): Int {
val accountNumbers = accounts.map { it.accountNumber }
return findNewAccountNumber(accountNumbers)
}
private fun findNewAccountNumber(accountNumbers: List<Int>): Int {
var newAccountNumber = -1
for (accountNumber in accountNumbers.sorted()) {
if (accountNumber > newAccountNumber + 1) {
break
}
newAccountNumber = accountNumber
}
newAccountNumber++
return newAccountNumber
}
fun move(account: Account, up: Boolean) {
synchronized(accountLock) {
val storageEditor = createStorageEditor()
accountPreferenceSerializer.move(storageEditor, account, storage, up)
storageEditor.commit()
loadAccounts()
}
notifyListeners()
}
private fun notifyListeners() {
for (listener in accountsChangeListeners) {
listener.onAccountsChanged()
}
}
fun addOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) {
accountsChangeListeners.add(accountsChangeListener)
}
fun removeOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) {
accountsChangeListeners.remove(accountsChangeListener)
}
companion object {
@JvmStatic
fun getPreferences(context: Context): Preferences {
return DI.get(Preferences::class.java)
}
}
}

View file

@ -27,7 +27,7 @@ class K9JobManager(
private fun scheduleMailSync() { private fun scheduleMailSync() {
cancelAllMailSyncJobs() cancelAllMailSyncJobs()
preferences.availableAccounts?.forEach { account -> preferences.availableAccounts.forEach { account ->
mailSyncWorkerManager.scheduleMailSync(account) mailSyncWorkerManager.scheduleMailSync(account)
} }
} }

View file

@ -65,8 +65,9 @@ class SettingsExporter(
serializer.startTag(null, ACCOUNTS_ELEMENT) serializer.startTag(null, ACCOUNTS_ELEMENT)
for (accountUuid in accountUuids) { for (accountUuid in accountUuids) {
val account = preferences.getAccount(accountUuid) preferences.getAccount(accountUuid)?.let { account ->
writeAccount(serializer, account, prefs) writeAccount(serializer, account, prefs)
}
} }
serializer.endTag(null, ACCOUNTS_ELEMENT) serializer.endTag(null, ACCOUNTS_ELEMENT)

View file

@ -1,18 +1,14 @@
package com.fsck.k9.controller; package com.fsck.k9.controller;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import android.content.Context; import android.content.Context;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.AccountPreferenceSerializer;
import com.fsck.k9.DI;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.K9RobolectricTest; import com.fsck.k9.K9RobolectricTest;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
@ -46,7 +42,6 @@ import org.mockito.ArgumentMatchers;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@ -62,7 +57,6 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -74,7 +68,6 @@ public class MessagingControllerTest extends K9RobolectricTest {
private static final String FOLDER_NAME = "Folder"; private static final String FOLDER_NAME = "Folder";
private static final long SENT_FOLDER_ID = 10; private static final long SENT_FOLDER_ID = 10;
private static final int MAXIMUM_SMALL_MESSAGE_SIZE = 1000; private static final int MAXIMUM_SMALL_MESSAGE_SIZE = 1000;
private static final String ACCOUNT_UUID = "1";
private MessagingController controller; private MessagingController controller;
private Account account; private Account account;
@ -130,6 +123,9 @@ public class MessagingControllerTest extends K9RobolectricTest {
} }
}; };
private Preferences preferences;
private String accountUuid;
@Before @Before
public void setUp() throws MessagingException { public void setUp() throws MessagingException {
@ -137,7 +133,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
appContext = RuntimeEnvironment.application; appContext = RuntimeEnvironment.application;
Preferences preferences = Preferences.getPreferences(appContext); preferences = Preferences.getPreferences(appContext);
controller = new MessagingController(appContext, notificationController, notificationStrategy, controller = new MessagingController(appContext, notificationController, notificationStrategy,
localStoreProvider, unreadMessageCountProvider, backendManager, preferences, messageStoreProvider, localStoreProvider, unreadMessageCountProvider, backendManager, preferences, messageStoreProvider,
@ -186,7 +182,6 @@ public class MessagingControllerTest extends K9RobolectricTest {
@Test @Test
public void searchLocalMessagesSynchronous_shouldCallSearchForMessagesOnLocalStore() public void searchLocalMessagesSynchronous_shouldCallSearchForMessagesOnLocalStore()
throws Exception { throws Exception {
setAccountsInPreferences(Collections.singletonMap(ACCOUNT_UUID, account));
when(search.searchAllAccounts()).thenReturn(true); when(search.searchAllAccounts()).thenReturn(true);
when(search.getAccountUuids()).thenReturn(new String[0]); when(search.getAccountUuids()).thenReturn(new String[0]);
@ -198,7 +193,6 @@ public class MessagingControllerTest extends K9RobolectricTest {
@Test @Test
public void searchLocalMessagesSynchronous_shouldNotifyWhenStoreFinishesRetrievingAMessage() public void searchLocalMessagesSynchronous_shouldNotifyWhenStoreFinishesRetrievingAMessage()
throws Exception { throws Exception {
setAccountsInPreferences(Collections.singletonMap(ACCOUNT_UUID, account));
LocalMessage localMessage = mock(LocalMessage.class); LocalMessage localMessage = mock(LocalMessage.class);
when(localMessage.getFolder()).thenReturn(localFolder); when(localMessage.getFolder()).thenReturn(localFolder);
when(search.searchAllAccounts()).thenReturn(true); when(search.searchAllAccounts()).thenReturn(true);
@ -215,8 +209,6 @@ public class MessagingControllerTest extends K9RobolectricTest {
} }
private void setupRemoteSearch() throws Exception { private void setupRemoteSearch() throws Exception {
setAccountsInPreferences(Collections.singletonMap(ACCOUNT_UUID, account));
remoteMessages = new ArrayList<>(); remoteMessages = new ArrayList<>();
Collections.addAll(remoteMessages, "oldMessageUid", "newMessageUid1", "newMessageUid2"); Collections.addAll(remoteMessages, "oldMessageUid", "newMessageUid1", "newMessageUid2");
List<String> newRemoteMessages = new ArrayList<>(); List<String> newRemoteMessages = new ArrayList<>();
@ -261,7 +253,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
public void searchRemoteMessagesSynchronous_shouldNotifyStartedListingRemoteMessages() throws Exception { public void searchRemoteMessagesSynchronous_shouldNotifyStartedListingRemoteMessages() throws Exception {
setupRemoteSearch(); setupRemoteSearch();
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(listener).remoteSearchStarted(FOLDER_ID); verify(listener).remoteSearchStarted(FOLDER_ID);
} }
@ -270,7 +262,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
public void searchRemoteMessagesSynchronous_shouldQueryRemoteFolder() throws Exception { public void searchRemoteMessagesSynchronous_shouldQueryRemoteFolder() throws Exception {
setupRemoteSearch(); setupRemoteSearch();
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(backend).search(FOLDER_NAME, "query", reqFlags, forbiddenFlags, false); verify(backend).search(FOLDER_NAME, "query", reqFlags, forbiddenFlags, false);
} }
@ -279,7 +271,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
public void searchRemoteMessagesSynchronous_shouldAskLocalFolderToDetermineNewMessages() throws Exception { public void searchRemoteMessagesSynchronous_shouldAskLocalFolderToDetermineNewMessages() throws Exception {
setupRemoteSearch(); setupRemoteSearch();
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(localFolder).extractNewMessages(remoteMessages); verify(localFolder).extractNewMessages(remoteMessages);
} }
@ -288,7 +280,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
public void searchRemoteMessagesSynchronous_shouldTryAndGetNewMessages() throws Exception { public void searchRemoteMessagesSynchronous_shouldTryAndGetNewMessages() throws Exception {
setupRemoteSearch(); setupRemoteSearch();
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(localFolder).getMessage("newMessageUid1"); verify(localFolder).getMessage("newMessageUid1");
} }
@ -297,7 +289,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
public void searchRemoteMessagesSynchronous_shouldNotTryAndGetOldMessages() throws Exception { public void searchRemoteMessagesSynchronous_shouldNotTryAndGetOldMessages() throws Exception {
setupRemoteSearch(); setupRemoteSearch();
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(localFolder, never()).getMessage("oldMessageUid"); verify(localFolder, never()).getMessage("oldMessageUid");
} }
@ -306,7 +298,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
public void searchRemoteMessagesSynchronous_shouldFetchNewMessages() throws Exception { public void searchRemoteMessagesSynchronous_shouldFetchNewMessages() throws Exception {
setupRemoteSearch(); setupRemoteSearch();
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(backend).fetchMessage(eq(FOLDER_NAME), eq("newMessageUid2"), fetchProfileCaptor.capture(), verify(backend).fetchMessage(eq(FOLDER_NAME), eq("newMessageUid2"), fetchProfileCaptor.capture(),
eq(MAXIMUM_SMALL_MESSAGE_SIZE)); eq(MAXIMUM_SMALL_MESSAGE_SIZE));
@ -316,7 +308,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
public void searchRemoteMessagesSynchronous_shouldNotFetchExistingMessages() throws Exception { public void searchRemoteMessagesSynchronous_shouldNotFetchExistingMessages() throws Exception {
setupRemoteSearch(); setupRemoteSearch();
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(backend, never()).fetchMessage(eq(FOLDER_NAME), eq("newMessageUid1"), fetchProfileCaptor.capture(), verify(backend, never()).fetchMessage(eq(FOLDER_NAME), eq("newMessageUid1"), fetchProfileCaptor.capture(),
eq(MAXIMUM_SMALL_MESSAGE_SIZE)); eq(MAXIMUM_SMALL_MESSAGE_SIZE));
@ -328,7 +320,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
when(backend.search(anyString(), anyString(), nullable(Set.class), nullable(Set.class), eq(false))) when(backend.search(anyString(), anyString(), nullable(Set.class), nullable(Set.class), eq(false)))
.thenThrow(new MessagingException("Test")); .thenThrow(new MessagingException("Test"));
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(listener).remoteSearchFailed(null, "Test"); verify(listener).remoteSearchFailed(null, "Test");
} }
@ -339,14 +331,14 @@ public class MessagingControllerTest extends K9RobolectricTest {
when(backend.search(anyString(), nullable(String.class), nullable(Set.class), nullable(Set.class), eq(false))) when(backend.search(anyString(), nullable(String.class), nullable(Set.class), nullable(Set.class), eq(false)))
.thenThrow(new MessagingException("Test")); .thenThrow(new MessagingException("Test"));
controller.searchRemoteMessagesSynchronous(ACCOUNT_UUID, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener); controller.searchRemoteMessagesSynchronous(accountUuid, FOLDER_ID, "query", reqFlags, forbiddenFlags, listener);
verify(listener).remoteSearchFinished(FOLDER_ID, 0, 50, Collections.<String>emptyList()); verify(listener).remoteSearchFinished(FOLDER_ID, 0, 50, Collections.<String>emptyList());
} }
@Test @Test
public void sendPendingMessagesSynchronous_withNonExistentOutbox_shouldNotStartSync() throws MessagingException { public void sendPendingMessagesSynchronous_withNonExistentOutbox_shouldNotStartSync() throws MessagingException {
when(account.getOutboxFolderId()).thenReturn(FOLDER_ID); account.setOutboxFolderId(FOLDER_ID);
when(localFolder.exists()).thenReturn(false); when(localFolder.exists()).thenReturn(false);
controller.addListener(listener); controller.addListener(listener);
@ -433,7 +425,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
} }
private void setupAccountWithMessageToSend() throws MessagingException { private void setupAccountWithMessageToSend() throws MessagingException {
when(account.getOutboxFolderId()).thenReturn(FOLDER_ID); account.setOutboxFolderId(FOLDER_ID);
account.setSentFolderId(SENT_FOLDER_ID); account.setSentFolderId(SENT_FOLDER_ID);
when(localStore.getFolder(SENT_FOLDER_ID)).thenReturn(sentFolder); when(localStore.getFolder(SENT_FOLDER_ID)).thenReturn(sentFolder);
when(sentFolder.getDatabaseId()).thenReturn(SENT_FOLDER_ID); when(sentFolder.getDatabaseId()).thenReturn(SENT_FOLDER_ID);
@ -455,13 +447,12 @@ public class MessagingControllerTest extends K9RobolectricTest {
when(backendManager.getBackend(account)).thenReturn(backend); when(backendManager.getBackend(account)).thenReturn(backend);
} }
private void configureAccount() throws MessagingException { private void configureAccount() {
// TODO use simple account object without mocks account = preferences.newAccount();
account = spy(new Account(ACCOUNT_UUID)); accountUuid = account.getUuid();
DI.get(AccountPreferenceSerializer.class).loadDefaults(account);
account.setMaximumAutoDownloadMessageSize(MAXIMUM_SMALL_MESSAGE_SIZE); account.setMaximumAutoDownloadMessageSize(MAXIMUM_SMALL_MESSAGE_SIZE);
account.setEmail("user@host.com"); account.setEmail("user@host.com");
Mockito.doReturn(true).when(account).isAvailable(appContext);
} }
private void configureLocalStore() throws MessagingException { private void configureLocalStore() throws MessagingException {
@ -474,20 +465,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
when(localStoreProvider.getInstance(account)).thenReturn(localStore); when(localStoreProvider.getInstance(account)).thenReturn(localStore);
} }
private void setAccountsInPreferences(Map<String, Account> newAccounts)
throws Exception {
// TODO this affects other tests, try to get rid of it
Field accounts = Preferences.class.getDeclaredField("accounts");
accounts.setAccessible(true);
accounts.set(Preferences.getPreferences(appContext), newAccounts);
Field accountsInOrder = Preferences.class.getDeclaredField("accountsInOrder");
accountsInOrder.setAccessible(true);
ArrayList<Account> newAccountsInOrder = new ArrayList<>(newAccounts.values());
accountsInOrder.set(Preferences.getPreferences(appContext), newAccountsInOrder);
}
private void removeAccountsFromPreferences() { private void removeAccountsFromPreferences() {
Preferences.getPreferences(appContext).clearAccounts(); preferences.clearAccounts();
} }
} }

View file

@ -135,7 +135,9 @@ class UnreadWidgetConfigurationFragment : PreferenceFragmentCompat() {
} }
private fun handleRegularAccount() { private fun handleRegularAccount() {
val selectedAccount = preferences.getAccount(selectedAccountUuid) val selectedAccount = preferences.getAccount(selectedAccountUuid!!)
?: error("Account $selectedAccountUuid not found")
val accountDescription: String? = selectedAccount.description val accountDescription: String? = selectedAccount.description
val summary = if (accountDescription.isNullOrEmpty()) selectedAccount.email else accountDescription val summary = if (accountDescription.isNullOrEmpty()) selectedAccount.email else accountDescription

View file

@ -33,8 +33,8 @@ class EditIdentity : K9Activity() {
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
identityIndex = intent.getIntExtra(EXTRA_IDENTITY_INDEX, -1) identityIndex = intent.getIntExtra(EXTRA_IDENTITY_INDEX, -1)
val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT) val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT) ?: error("Missing account UUID")
account = Preferences.getPreferences(this).getAccount(accountUuid) account = Preferences.getPreferences(this).getAccount(accountUuid) ?: error("Couldn't find account")
identity = when { identity = when {
savedInstanceState != null -> savedInstanceState.getParcelable(EXTRA_IDENTITY) ?: error("Missing state") savedInstanceState != null -> savedInstanceState.getParcelable(EXTRA_IDENTITY) ?: error("Missing state")

View file

@ -434,6 +434,11 @@ open class MessageList :
if (accountUuid != null) { if (accountUuid != null) {
// We've most likely been started by an old unread widget or accounts shortcut // We've most likely been started by an old unread widget or accounts shortcut
val account = preferences.getAccount(accountUuid) val account = preferences.getAccount(accountUuid)
if (account == null) {
Timber.d("Account %s not found.", accountUuid)
return LaunchData(createDefaultLocalSearch())
}
val folderId = defaultFolderProvider.getDefaultFolder(account) val folderId = defaultFolderProvider.getDefaultFolder(account)
val search = LocalSearch().apply { val search = LocalSearch().apply {
addAccountUuid(accountUuid) addAccountUuid(accountUuid)
@ -455,7 +460,7 @@ open class MessageList :
} }
private fun createDefaultLocalSearch(): LocalSearch { private fun createDefaultLocalSearch(): LocalSearch {
val account = preferences.defaultAccount val account = preferences.defaultAccount ?: error("No default account available")
return LocalSearch().apply { return LocalSearch().apply {
addAccountUuid(account.uuid) addAccountUuid(account.uuid)
addAllowedFolder(defaultFolderProvider.getDefaultFolder(account)) addAllowedFolder(defaultFolderProvider.getDefaultFolder(account))
@ -1141,7 +1146,7 @@ open class MessageList :
} }
override fun openMessage(messageReference: MessageReference) { override fun openMessage(messageReference: MessageReference) {
val account = preferences.getAccount(messageReference.accountUuid) val account = preferences.getAccount(messageReference.accountUuid) ?: error("Account not found")
val folderId = messageReference.folderId val folderId = messageReference.folderId
val draftsFolderId = account.draftsFolderId val draftsFolderId = account.draftsFolderId

View file

@ -1048,7 +1048,7 @@ class MessageListFragment :
} }
private fun groupMessagesByAccount(messages: List<MessageReference>): Map<Account, List<MessageReference>> { private fun groupMessagesByAccount(messages: List<MessageReference>): Map<Account, List<MessageReference>> {
return messages.groupBy { preferences.getAccount(it.accountUuid) } return messages.groupBy { preferences.getAccount(it.accountUuid)!! }
} }
private fun onSpam(messages: List<MessageReference>) { private fun onSpam(messages: List<MessageReference>) {

View file

@ -35,7 +35,7 @@ class AutocryptKeyTransferPresenter internal constructor(
return return
} }
account = preferences.getAccount(accountUuid) account = preferences.getAccount(accountUuid) ?: error("Account $accountUuid not found")
openPgpApiManager.setOpenPgpProvider( openPgpApiManager.setOpenPgpProvider(
account.openPgpProvider, account.openPgpProvider,

View file

@ -24,7 +24,8 @@ class MessageListExtractor(
): MessageListItem { ): MessageListItem {
val position = cursor.position val position = cursor.position
val accountUuid = cursor.getString(MLFProjectionInfo.ACCOUNT_UUID_COLUMN) val accountUuid = cursor.getString(MLFProjectionInfo.ACCOUNT_UUID_COLUMN)
val account = preferences.getAccount(accountUuid) val account = preferences.getAccount(accountUuid) ?: error("Account $accountUuid not found")
val fromList = cursor.getString(MLFProjectionInfo.SENDER_LIST_COLUMN) val fromList = cursor.getString(MLFProjectionInfo.SENDER_LIST_COLUMN)
val toList = cursor.getString(MLFProjectionInfo.TO_LIST_COLUMN) val toList = cursor.getString(MLFProjectionInfo.TO_LIST_COLUMN)
val ccList = cursor.getString(MLFProjectionInfo.CC_LIST_COLUMN) val ccList = cursor.getString(MLFProjectionInfo.CC_LIST_COLUMN)

View file

@ -47,7 +47,9 @@ class AccountSettingsViewModel(
} }
} }
private fun loadAccount(accountUuid: String) = preferences.getAccount(accountUuid) private fun loadAccount(accountUuid: String): Account {
return preferences.getAccount(accountUuid) ?: error("Account $accountUuid not found")
}
fun getFolders(account: Account): LiveData<RemoteFolderInfo> { fun getFolders(account: Account): LiveData<RemoteFolderInfo> {
if (foldersLiveData.value == null) { if (foldersLiveData.value == null) {

View file

@ -15,7 +15,7 @@ class AccountActivator(
private val messagingController: MessagingController private val messagingController: MessagingController
) { ) {
fun enableAccount(accountUuid: String, incomingServerPassword: String?, outgoingServerPassword: String?) { fun enableAccount(accountUuid: String, incomingServerPassword: String?, outgoingServerPassword: String?) {
val account = preferences.getAccount(accountUuid) val account = preferences.getAccount(accountUuid) ?: error("Account $accountUuid not found")
setAccountPasswords(account, incomingServerPassword, outgoingServerPassword) setAccountPasswords(account, incomingServerPassword, outgoingServerPassword)