extract persistence logic from Storage into StoragePersister

This commit is contained in:
Vincent Breitmoser 2018-11-30 19:43:41 +01:00
parent d1c4701256
commit f7faccf5d9
20 changed files with 339 additions and 316 deletions

View file

@ -192,7 +192,7 @@ class AccountPreferenceSerializer(
}
@Synchronized
fun save(storage: Storage, editor: StorageEditor, account: Account) {
fun save(editor: StorageEditor, storage: Storage, account: Account) {
val accountUuid = account.uuid
if (!storage.getString("accountUuids", "").contains(account.uuid)) {
@ -294,13 +294,11 @@ class AccountPreferenceSerializer(
}
saveIdentities(account, storage, editor)
editor.commit()
}
@Synchronized
fun delete(storage: Storage, account: Account) {
fun delete(editor: StorageEditor, storage: Storage, account: Account) {
val accountUuid = account.uuid
// Get the list of account UUIDs
@ -314,8 +312,6 @@ class AccountPreferenceSerializer(
}
}
val editor = storage.edit()
// Only change the 'accountUuids' value if this account's UUID was listed before
if (newUuids.size < uuids.size) {
val accountUuids = Utility.combine(newUuids.toTypedArray(), ',')
@ -407,7 +403,6 @@ class AccountPreferenceSerializer(
}
deleteIdentities(account, storage, editor)
// TODO: Remove preference settings that may exist for individual folders in the account.
editor.commit()
}
@Synchronized
@ -450,9 +445,8 @@ class AccountPreferenceSerializer(
} while (gotOne)
}
fun move(account: Account, storage: Storage, moveUp: Boolean) {
fun move(editor: StorageEditor, account: Account, storage: Storage, moveUp: Boolean) {
val uuids = storage.getString("accountUuids", "").split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val editor = storage.edit()
val newUuids = arrayOfNulls<String>(uuids.size)
if (moveUp) {
for (i in uuids.indices) {
@ -475,7 +469,6 @@ class AccountPreferenceSerializer(
}
val accountUuids = Utility.combine(newUuids, ',')
editor.putString("accountUuids", accountUuids)
editor.commit()
}
private fun <T : Enum<T>> getEnumStringPref(storage: Storage, key: String, defaultEnum: T): T {

View file

@ -383,7 +383,7 @@ public class K9 {
preferences.saveAccount(account);
}
storage.edit()
preferences.createStorageEditor()
.remove("openPgpProvider")
.remove("openPgpSupportSignOnly")
.commit();
@ -1075,7 +1075,7 @@ public class K9 {
@Override
protected Void doInBackground(Void... voids) {
Preferences prefs = DI.get(Preferences.class);
StorageEditor editor = prefs.getStorage().edit();
StorageEditor editor = prefs.createStorageEditor();
save(editor);
editor.commit();

View file

@ -10,12 +10,14 @@ import com.fsck.k9.mail.ssl.TrustedSocketFactory
import com.fsck.k9.mailstore.LocalStoreProvider
import com.fsck.k9.mailstore.StorageManager
import com.fsck.k9.power.TracingPowerManager
import com.fsck.k9.preferences.StoragePersister
import org.koin.dsl.module.applicationContext
val mainModule = applicationContext {
bean { Preferences.getPreferences(get()) }
bean { get<Context>().resources }
bean { StorageManager.getInstance(get()) }
bean { StoragePersister(get()) }
bean { LocalStoreProvider() }
bean { TracingPowerManager.getPowerManager(get()) as PowerManager }
bean { Contacts.getInstance(get()) }

View file

@ -21,6 +21,7 @@ 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;
@ -30,13 +31,15 @@ public class Preferences {
private AccountPreferenceSerializer accountPreferenceSerializer;
public static synchronized Preferences getPreferences(Context context) {
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);
if (preferences == null) {
preferences = new Preferences(appContext, resourceProvider, localStoreProvider, localKeyStoreManager, accountPreferenceSerializer);
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;
}
@ -49,24 +52,35 @@ public class Preferences {
private final LocalStoreProvider localStoreProvider;
private final CoreResourceProvider resourceProvider;
private final LocalKeyStoreManager localKeyStoreManager;
private final StoragePersister storagePersister;
private Preferences(Context context, CoreResourceProvider resourceProvider,
LocalStoreProvider localStoreProvider, LocalKeyStoreManager localKeyStoreManager,
StoragePersister storagePersister, LocalStoreProvider localStoreProvider,
LocalKeyStoreManager localKeyStoreManager,
AccountPreferenceSerializer accountPreferenceSerializer) {
storage = Storage.getStorage(context);
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 = storage.edit();
StorageEditor editor = createStorageEditor();
editor.copy(context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE));
editor.commit();
}
}
public StorageEditor createStorageEditor() {
return new StorageEditor(storage, storagePersister);
}
@RestrictTo(Scope.TESTS)
public void clearAccounts() {
accounts = new HashMap<>();
@ -160,7 +174,9 @@ public class Preferences {
}
LocalStore.removeAccount(account);
DI.get(AccountPreferenceSerializer.class).delete(storage, account);
StorageEditor storageEditor = createStorageEditor();
accountPreferenceSerializer.delete(storageEditor, storage, account);
storageEditor.commit();
localKeyStoreManager.deleteCertificates(account);
if (newAccount == account) {
@ -189,7 +205,7 @@ public class Preferences {
}
public void setDefaultAccount(Account account) {
getStorage().edit().putString("defaultAccountUuid", account.getUuid()).commit();
createStorageEditor().putString("defaultAccountUuid", account.getUuid()).commit();
}
public Storage getStorage() {
@ -201,11 +217,12 @@ public class Preferences {
}
public void saveAccount(Account account) {
StorageEditor editor = storage.edit();
ensureAssignedAccountNumber(account);
processChangedValues(account);
accountPreferenceSerializer.save(storage, editor, account);
StorageEditor editor = createStorageEditor();
accountPreferenceSerializer.save(editor, storage, account);
editor.commit();
}
private void ensureAssignedAccountNumber(Account account) {
@ -265,7 +282,9 @@ public class Preferences {
}
public void move(Account account, boolean mUp) {
accountPreferenceSerializer.move(account, storage, mUp);
StorageEditor storageEditor = createStorageEditor();
accountPreferenceSerializer.move(storageEditor, account, storage, mUp);
storageEditor.commit();
loadAccounts();
}
}

View file

@ -29,6 +29,7 @@ import android.support.annotation.NonNull;
import com.fsck.k9.Account;
import com.fsck.k9.DI;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.controller.MessageReference;
import com.fsck.k9.backend.api.MessageRemovalListener;
import com.fsck.k9.crypto.EncryptionExtractor;
@ -630,7 +631,7 @@ public class LocalFolder extends Folder<LocalMessage> {
public void delete() throws MessagingException {
String id = getPrefId();
StorageEditor editor = this.localStore.getStorage().edit();
StorageEditor editor = localStore.getPreferences().createStorageEditor();
editor.remove(id + ".displayMode");
editor.remove(id + ".syncMode");
@ -642,7 +643,7 @@ public class LocalFolder extends Folder<LocalMessage> {
}
public void save() throws MessagingException {
StorageEditor editor = this.localStore.getStorage().edit();
StorageEditor editor = localStore.getPreferences().createStorageEditor();
save(editor);
editor.commit();
}

View file

@ -244,6 +244,10 @@ public class LocalStore {
return Preferences.getPreferences(context).getStorage();
}
protected Preferences getPreferences() {
return Preferences.getPreferences(context);
}
public long getSize() throws MessagingException {
final StorageManager storageManager = StorageManager.getInstance(context);
@ -1336,6 +1340,11 @@ public class LocalStore {
return LocalStore.this.getStorage();
}
@Override
public Preferences getPreferences() {
return LocalStore.this.getPreferences();
}
@Override
public Account getAccount() {
return LocalStore.this.getAccount();

View file

@ -6,8 +6,8 @@ import java.util.List;
import android.content.Context;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.preferences.Storage;
@ -17,6 +17,7 @@ import com.fsck.k9.preferences.Storage;
public interface MigrationsHelper {
LocalStore getLocalStore();
Storage getStorage();
Preferences getPreferences();
Account getAccount();
Context getContext();
String serializeFlags(List<Flag> flags);

View file

@ -179,7 +179,7 @@ public class SettingsImporter {
if (globalSettings) {
try {
StorageEditor editor = storage.edit();
StorageEditor editor = preferences.createStorageEditor();
if (imported.globalSettings != null) {
importGlobalSettings(storage, editor, imported.contentVersion, imported.globalSettings);
} else {
@ -202,7 +202,7 @@ public class SettingsImporter {
if (imported.accounts.containsKey(accountUuid)) {
ImportedAccount account = imported.accounts.get(accountUuid);
try {
StorageEditor editor = storage.edit();
StorageEditor editor = preferences.createStorageEditor();
AccountDescriptionPair importResult = importAccount(context, editor,
imported.contentVersion, account, overwrite);
@ -214,7 +214,7 @@ public class SettingsImporter {
// Add UUID of the account we just imported to the list of
// account UUIDs
if (!importResult.overwritten) {
editor = storage.edit();
editor = preferences.createStorageEditor();
String newUuid = importResult.imported.uuid;
String oldAccountUuids = storage.getString("accountUuids", "");
@ -253,7 +253,7 @@ public class SettingsImporter {
}
}
StorageEditor editor = storage.edit();
StorageEditor editor = preferences.createStorageEditor();
String defaultAccountUuid = storage.getString("defaultAccountUuid", null);
if (defaultAccountUuid == null) {

View file

@ -1,199 +1,24 @@
package com.fsck.k9.preferences;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.os.SystemClock;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.preferences.migrations.StorageMigrations;
import com.fsck.k9.preferences.migrations.StorageMigrationsHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import timber.log.Timber;
public class Storage {
private static ConcurrentMap<Context, Storage> storages = new ConcurrentHashMap<>();
private HashMap<String, String> storage = new HashMap<>();
private volatile ConcurrentMap<String, String> storage = new ConcurrentHashMap<>();
private static final int DB_VERSION = 4;
private static final String DB_NAME = "preferences_storage";
private ThreadLocal<ConcurrentMap<String, String>> workingStorage = new ThreadLocal<>();
private ThreadLocal<SQLiteDatabase> workingDB = new ThreadLocal<>();
private ThreadLocal<List<String>> workingChangedKeys = new ThreadLocal<>();
private Context context = null;
private SQLiteDatabase openDB() {
SQLiteDatabase db = context.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
db.beginTransaction();
try {
if (db.getVersion() < 1) {
createStorageDatabase(db);
} else {
StorageMigrations.upgradeDatabase(db, migrationsHelper);
}
db.setVersion(DB_VERSION);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (db.getVersion() != DB_VERSION) {
throw new RuntimeException("Storage database upgrade failed!");
}
return db;
}
private void createStorageDatabase(SQLiteDatabase db) {
Timber.i("Creating Storage database");
db.execSQL("DROP TABLE IF EXISTS preferences_storage");
db.execSQL("CREATE TABLE preferences_storage " +
"(primkey TEXT PRIMARY KEY ON CONFLICT REPLACE, value TEXT)");
db.setVersion(DB_VERSION);
}
public static Storage getStorage(Context context) {
Storage tmpStorage = storages.get(context);
if (tmpStorage != null) {
Timber.d("Returning already existing Storage");
return tmpStorage;
} else {
Timber.d("Creating provisional storage");
tmpStorage = new Storage(context);
Storage oldStorage = storages.putIfAbsent(context, tmpStorage);
if (oldStorage != null) {
Timber.d("Another thread beat us to creating the Storage, returning that one");
return oldStorage;
} else {
Timber.d("Returning the Storage we created");
return tmpStorage;
}
}
}
private void loadValues() {
long startTime = SystemClock.elapsedRealtime();
Timber.i("Loading preferences from DB into Storage");
Cursor cursor = null;
SQLiteDatabase mDb = null;
try {
mDb = openDB();
cursor = mDb.rawQuery("SELECT primkey, value FROM preferences_storage", null);
while (cursor.moveToNext()) {
String key = cursor.getString(0);
String value = cursor.getString(1);
Timber.d("Loading key '%s', value = '%s'", key, value);
storage.put(key, value);
}
} finally {
Utility.closeQuietly(cursor);
if (mDb != null) {
mDb.close();
}
long endTime = SystemClock.elapsedRealtime();
Timber.i("Preferences load took %d ms", endTime - startTime);
}
}
private Storage(Context context) {
this.context = context;
loadValues();
}
private void keyChange(String key) {
List<String> changedKeys = workingChangedKeys.get();
if (!changedKeys.contains(key)) {
changedKeys.add(key);
}
}
void put(Map<String, String> insertables) {
String sql = "INSERT INTO preferences_storage (primkey, value) VALUES (?, ?)";
SQLiteStatement stmt = workingDB.get().compileStatement(sql);
for (Map.Entry<String, String> entry : insertables.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
stmt.bindString(1, key);
stmt.bindString(2, value);
stmt.execute();
stmt.clearBindings();
liveUpdate(key, value);
}
stmt.close();
}
private void liveUpdate(String key, String value) {
workingStorage.get().put(key, value);
keyChange(key);
}
void remove(String key) {
workingDB.get().delete("preferences_storage", "primkey = ?", new String[] { key });
workingStorage.get().remove(key);
keyChange(key);
}
void doInTransaction(Runnable dbWork) {
ConcurrentMap<String, String> newStorage = new ConcurrentHashMap<>(storage);
workingStorage.set(newStorage);
SQLiteDatabase mDb = openDB();
workingDB.set(mDb);
List<String> changedKeys = new ArrayList<>();
workingChangedKeys.set(changedKeys);
mDb.beginTransaction();
try {
dbWork.run();
mDb.setTransactionSuccessful();
storage = newStorage;
} finally {
workingDB.remove();
workingStorage.remove();
workingChangedKeys.remove();
mDb.endTransaction();
mDb.close();
}
}
public Storage() { }
public boolean isEmpty() {
return storage.isEmpty();
}
public boolean contains(String key) {
// TODO this used to be ConcurrentHashMap#contains which is
// actually containsValue. But looking at the usage of this method,
// it's clear that containsKey is what's intended. Investigate if this
// was a bug previously. Looks like it was only used once, when upgrading
return storage.containsKey(key);
}
public StorageEditor edit() {
return new StorageEditor(this);
}
public Map<String, String> getAll() {
return storage;
}
@ -240,77 +65,8 @@ public class Storage {
return val;
}
private String readValue(SQLiteDatabase mDb, String key) {
Cursor cursor = null;
String value = null;
try {
cursor = mDb.query(
"preferences_storage",
new String[] {"value"},
"primkey = ?",
new String[] {key},
null,
null,
null);
if (cursor.moveToNext()) {
value = cursor.getString(0);
Timber.d("Loading key '%s', value = '%s'", key, value);
}
} finally {
Utility.closeQuietly(cursor);
}
return value;
public void replaceAll(Map<String, String> workingStorage) {
storage.clear();
storage.putAll(workingStorage);
}
private void writeValue(SQLiteDatabase mDb, String key, String value) {
if (value == null) {
mDb.delete("preferences_storage", "primkey = ?", new String[] { key });
return;
}
ContentValues cv = new ContentValues();
cv.put("primkey", key);
cv.put("value", value);
long result = mDb.update("preferences_storage", cv, "primkey = ?", new String[] { key });
if (result == -1) {
Timber.e("Error writing key '%s', value = '%s'", key, value);
}
}
private void insertValue(SQLiteDatabase mDb, String key, String value) {
if (value == null) {
return;
}
ContentValues cv = new ContentValues();
cv.put("primkey", key);
cv.put("value", value);
long result = mDb.insert("preferences_storage", null, cv);
if (result == -1) {
Timber.e("Error writing key '%s', value = '%s'", key, value);
}
}
private StorageMigrationsHelper migrationsHelper = new StorageMigrationsHelper() {
@Override
public void writeValue(@NotNull SQLiteDatabase db, @NotNull String key, String value) {
Storage.this.writeValue(db, key, value);
}
@Override
public String readValue(@NotNull SQLiteDatabase db, @NotNull String key) {
return Storage.this.readValue(db, key);
}
@Override
public void insertValue(@NotNull SQLiteDatabase db, @NotNull String key, @Nullable String value) {
Storage.this.insertValue(db, key, value);
}
};
}

View file

@ -9,19 +9,23 @@ import java.util.Map.Entry;
import android.os.SystemClock;
import com.fsck.k9.preferences.StoragePersister.StoragePersistOperationCallback;
import com.fsck.k9.preferences.StoragePersister.StoragePersistOperations;
import timber.log.Timber;
public class StorageEditor {
private Storage storage;
private StoragePersister storagePersister;
private Map<String, String> changes = new HashMap<>();
private List<String> removals = new ArrayList<>();
Map<String, String> snapshot = new HashMap<>();
private Map<String, String> snapshot = new HashMap<>();
StorageEditor(Storage storage) {
public StorageEditor(Storage storage, StoragePersister storagePersister) {
this.storage = storage;
this.storagePersister = storagePersister;
snapshot.putAll(storage.getAll());
}
@ -52,10 +56,16 @@ public class StorageEditor {
private void commitChanges() {
long startTime = SystemClock.elapsedRealtime();
Timber.i("Committing preference changes");
Runnable committer = new Runnable() {
public void run() {
StoragePersistOperationCallback committer = new StoragePersistOperationCallback() {
@Override
public void beforePersistTransaction(Map<String, String> workingStorage) {
workingStorage.putAll(storage.getAll());
}
@Override
public void persist(StoragePersistOperations ops) {
for (String removeKey : removals) {
storage.remove(removeKey);
ops.remove(removeKey);
}
Map<String, String> insertables = new HashMap<>();
for (Entry<String, String> entry : changes.entrySet()) {
@ -66,13 +76,17 @@ public class StorageEditor {
insertables.put(key, newValue);
}
}
storage.put(insertables);
ops.put(insertables);
}
@Override
public void onPersistTransactionSuccess(Map<String, String> workingStorage) {
storage.replaceAll(workingStorage);
}
};
storage.doInTransaction(committer);
storagePersister.doInTransaction(committer);
long endTime = SystemClock.elapsedRealtime();
Timber.i("Preferences commit took %d ms", endTime - startTime);
}
public StorageEditor putBoolean(String key,

View file

@ -0,0 +1,228 @@
package com.fsck.k9.preferences;
import java.util.HashMap;
import java.util.Map;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.os.SystemClock;
import android.support.annotation.CheckResult;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.preferences.migrations.StorageMigrations;
import com.fsck.k9.preferences.migrations.StorageMigrationsHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import timber.log.Timber;
public class StoragePersister {
private static final int DB_VERSION = 4;
private static final String DB_NAME = "preferences_storage";
private final Context context;
public StoragePersister(Context context) {
this.context = context;
}
private SQLiteDatabase openDB() {
SQLiteDatabase db = context.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
db.beginTransaction();
try {
if (db.getVersion() < 1) {
createStorageDatabase(db);
} else {
StorageMigrations.upgradeDatabase(db, migrationsHelper);
}
db.setVersion(DB_VERSION);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (db.getVersion() != DB_VERSION) {
throw new RuntimeException("Storage database upgrade failed!");
}
return db;
}
private void createStorageDatabase(SQLiteDatabase db) {
Timber.i("Creating Storage database");
db.execSQL("DROP TABLE IF EXISTS preferences_storage");
db.execSQL("CREATE TABLE preferences_storage " +
"(primkey TEXT PRIMARY KEY ON CONFLICT REPLACE, value TEXT)");
db.setVersion(DB_VERSION);
}
void doInTransaction(StoragePersistOperationCallback operationCallback) {
HashMap<String, String> workingStorage = new HashMap<>();
SQLiteDatabase workingDb = openDB();
try {
operationCallback.beforePersistTransaction(workingStorage);
StoragePersistOperations storagePersistOperations = new StoragePersistOperations(workingStorage, workingDb);
workingDb.beginTransaction();
operationCallback.persist(storagePersistOperations);
workingDb.setTransactionSuccessful();
operationCallback.onPersistTransactionSuccess(workingStorage);
} finally {
workingDb.endTransaction();
workingDb.close();
}
}
static class StoragePersistOperations {
private SQLiteDatabase workingDB;
private Map<String, String> workingStorage;
private StoragePersistOperations(Map<String, String> workingStorage, SQLiteDatabase workingDb) {
this.workingDB = workingDb;
this.workingStorage = workingStorage;
}
void put(Map<String, String> insertables) {
String sql = "INSERT INTO preferences_storage (primkey, value) VALUES (?, ?)";
SQLiteStatement stmt = workingDB.compileStatement(sql);
for (Map.Entry<String, String> entry : insertables.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
stmt.bindString(1, key);
stmt.bindString(2, value);
stmt.execute();
stmt.clearBindings();
liveUpdate(key, value);
}
stmt.close();
}
private void liveUpdate(String key, String value) {
workingStorage.put(key, value);
}
void remove(String key) {
workingDB.delete("preferences_storage", "primkey = ?", new String[] { key });
workingStorage.remove(key);
}
}
interface StoragePersistOperationCallback {
void beforePersistTransaction(Map<String, String> workingStorage);
void persist(StoragePersistOperations ops);
void onPersistTransactionSuccess(Map<String, String> workingStorage);
}
@CheckResult
public Map<String, String> loadValues() {
long startTime = SystemClock.elapsedRealtime();
Timber.i("Loading preferences from DB into Storage");
HashMap<String, String> loadedValues = new HashMap<>();
Cursor cursor = null;
SQLiteDatabase mDb = null;
try {
mDb = openDB();
cursor = mDb.rawQuery("SELECT primkey, value FROM preferences_storage", null);
while (cursor.moveToNext()) {
String key = cursor.getString(0);
String value = cursor.getString(1);
Timber.d("Loading key '%s', value = '%s'", key, value);
loadedValues.put(key, value);
}
} finally {
Utility.closeQuietly(cursor);
if (mDb != null) {
mDb.close();
}
long endTime = SystemClock.elapsedRealtime();
Timber.i("Preferences load took %d ms", endTime - startTime);
}
return loadedValues;
}
private String readValue(SQLiteDatabase mDb, String key) {
Cursor cursor = null;
String value = null;
try {
cursor = mDb.query(
"preferences_storage",
new String[] {"value"},
"primkey = ?",
new String[] {key},
null,
null,
null);
if (cursor.moveToNext()) {
value = cursor.getString(0);
Timber.d("Loading key '%s', value = '%s'", key, value);
}
} finally {
Utility.closeQuietly(cursor);
}
return value;
}
private void writeValue(SQLiteDatabase mDb, String key, String value) {
if (value == null) {
mDb.delete("preferences_storage", "primkey = ?", new String[] { key });
return;
}
ContentValues cv = new ContentValues();
cv.put("primkey", key);
cv.put("value", value);
long result = mDb.update("preferences_storage", cv, "primkey = ?", new String[] { key });
if (result == -1) {
Timber.e("Error writing key '%s', value = '%s'", key, value);
}
}
private void insertValue(SQLiteDatabase mDb, String key, String value) {
if (value == null) {
return;
}
ContentValues cv = new ContentValues();
cv.put("primkey", key);
cv.put("value", value);
long result = mDb.insert("preferences_storage", null, cv);
if (result == -1) {
Timber.e("Error writing key '%s', value = '%s'", key, value);
}
}
private StorageMigrationsHelper migrationsHelper = new StorageMigrationsHelper() {
@Override
public void writeValue(@NotNull SQLiteDatabase db, @NotNull String key, String value) {
StoragePersister.this.writeValue(db, key, value);
}
@Override
public String readValue(@NotNull SQLiteDatabase db, @NotNull String key) {
return StoragePersister.this.readValue(db, key);
}
@Override
public void insertValue(@NotNull SQLiteDatabase db, @NotNull String key, @Nullable String value) {
StoragePersister.this.insertValue(db, key, value);
}
};
}

View file

@ -51,7 +51,7 @@ public class MessageProviderTest extends ProviderTestCase2 {
private void createAccount() {
Preferences preferences = Preferences.getPreferences(getMockContext());
Account account = preferences.newAccount();
Account account = preferences.createEmptyAccount();
account.setDescription("TestAccount");
account.setChipColor(10);
account.setStoreUri("imap://user@domain.com/");

View file

@ -100,7 +100,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test(expected = IllegalArgumentException.class)
public void query_forMessagesWithAccountAndWithoutRequiredFields_throwsIllegalArgumentException() {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
Cursor cursor = getProvider().query(
@ -116,7 +116,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test(expected = SQLException.class) //Handle this better?
public void query_forMessagesWithAccountAndRequiredFieldsWithNoOrderBy_throwsSQLiteException() {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
Cursor cursor = getProvider().query(
@ -136,7 +136,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test
public void query_forMessagesWithEmptyAccountAndRequiredFieldsAndOrderBy_providesEmptyResult() {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
Cursor cursor = getProvider().query(
@ -157,7 +157,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test
public void query_forMessagesWithAccountAndRequiredFieldsAndOrderBy_providesResult() throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
DI.get(LocalStoreProvider.class).getInstance(account).getFolder("Inbox").appendMessages(Collections.singletonList(message));
@ -179,7 +179,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test
public void query_forMessagesWithAccountAndRequiredFieldsAndOrderBy_sortsCorrectly() throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
DI.get(LocalStoreProvider.class).getInstance(account)
.getFolder("Inbox").appendMessages(Arrays.asList(message, laterMessage));
@ -205,7 +205,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test
public void query_forThreadedMessages_sortsCorrectly() throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
DI.get(LocalStoreProvider.class).getInstance(account)
.getFolder("Inbox").appendMessages(Arrays.asList(message, laterMessage));
@ -232,7 +232,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test
public void query_forThreadedMessages_showsThreadOfEmailOnce() throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
DI.get(LocalStoreProvider.class).getInstance(account).getFolder("Inbox").appendMessages(Collections.singletonList(message));
DI.get(LocalStoreProvider.class).getInstance(account).getFolder("Inbox").appendMessages(Collections.singletonList(reply));
@ -260,7 +260,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test
public void query_forThreadedMessages_showsThreadOfEmailWithSameSendTimeOnce() throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
DI.get(LocalStoreProvider.class).getInstance(account).getFolder("Inbox").appendMessages(Collections.singletonList(message));
DI.get(LocalStoreProvider.class).getInstance(account).getFolder("Inbox").appendMessages(Collections.singletonList(replyAtSameTime));
@ -288,7 +288,7 @@ public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
@Test
public void query_forAThreadOfMessages_returnsMessage() throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
Account account = Preferences.getPreferences(getContext()).createEmptyAccount();
account.getUuid();
Message message = new MimeMessage();
message.setSubject("Test Subject");

View file

@ -139,9 +139,7 @@ public class RemoteControlService extends CoreService {
K9.setK9Theme(K9RemoteControl.K9_THEME_DARK.equals(theme) ? K9.Theme.DARK : K9.Theme.LIGHT);
}
Storage storage = preferences.getStorage();
StorageEditor editor = storage.edit();
StorageEditor editor = preferences.createStorageEditor();
K9.save(editor);
editor.commit();

View file

@ -18,10 +18,9 @@ class MigrationTo42 {
public static void from41MoveFolderPreferences(MigrationsHelper migrationsHelper) {
try {
LocalStore localStore = migrationsHelper.getLocalStore();
Storage storage = migrationsHelper.getStorage();
long startTime = SystemClock.elapsedRealtime();
StorageEditor editor = storage.edit();
StorageEditor editor = migrationsHelper.getPreferences().createStorageEditor();
List<? extends Folder > folders = localStore.getPersonalNamespaces(true);
for (Folder folder : folders) {

View file

@ -14,7 +14,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.core.BuildConfig;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.MessagingException;
@ -339,6 +339,11 @@ public class StoreSchemaDefinitionTest extends K9RobolectricTest {
return localStore;
}
@Override
public Preferences getPreferences() {
return mock(Preferences.class);
}
@Override
public Storage getStorage() {
return mock(Storage.class);

View file

@ -1611,7 +1611,7 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
new Thread(new Runnable() {
@Override
public void run() {
StorageEditor editor = preferences.getStorage().edit();
StorageEditor editor = preferences.createStorageEditor();
K9.save(editor);
editor.commit();
}

View file

@ -11,9 +11,8 @@ import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.ui.R;
import com.fsck.k9.preferences.Storage;
import com.fsck.k9.preferences.StorageEditor;
import com.fsck.k9.ui.R;
/**
@ -191,8 +190,7 @@ public class FontSizeSettings extends K9PreferenceActivity {
fontSizes.setMessageComposeInput(Integer.parseInt(mMessageComposeInput.getValue()));
Storage storage = Preferences.getPreferences(this).getStorage();
StorageEditor editor = storage.edit();
StorageEditor editor = Preferences.getPreferences(this).createStorageEditor();
fontSizes.save(editor);
editor.commit();
}

View file

@ -850,7 +850,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
K9.setSortAscending(this.sortType, this.sortAscending);
sortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
StorageEditor editor = preferences.getStorage().edit();
StorageEditor editor = preferences.createStorageEditor();
K9.save(editor);
editor.commit();
}

View file

@ -205,7 +205,7 @@ class GeneralSettingsDataStore(
}
private fun saveSettings() {
val editor = preferences.storage.edit()
val editor = preferences.createStorageEditor()
K9.save(editor)
executorService.execute {