From 40bdf999256e6c4898e1adfb22c8ca34a7752318 Mon Sep 17 00:00:00 2001 From: Dan Applebaum Date: Fri, 4 Feb 2011 12:26:14 +0000 Subject: [PATCH] Fixes Issue 2934 Fixes Issue 2935 Provides for storing Folder Settings in the central Preferences Storage as a back-up to the settings stored on each folder. In this way, even if the LocalStore DB is recreated or otherwise lost, Folder Settings can be recovered. 1) Does not change the methodology used to read settings while running, nor the changes in r3107 & r3116 which tremendously improve Accounts list loading time. 2) Loads Folder Settings from Preferences and stores on the folder only when creating a new LocalFolder 3) Saves Folder Settings to Preferences and the DB row every time the Folder Settings are changed. 4) When upgrading from DB version 41 to 42 or later, copies all settings for existing folders from the DB storage to the Preferences Storage. 5) Transactional bulk folder creation and single pass local folder existence check during "Refresh folders" operation drastically reduces time spent when refreshing folders from the remote store. 6) Uses prepared statement during Editor commit to reduce Preference storing time. Probably needs a reversion of r3239, but I'm unfamiliar with translations, so am leaving that to others' discretion. --- .../k9/activity/setup/FolderSettings.java | 10 +- .../k9/controller/MessagingController.java | 23 +- src/com/fsck/k9/mail/store/LocalStore.java | 300 ++++++++++++++---- src/com/fsck/k9/preferences/Editor.java | 4 +- src/com/fsck/k9/preferences/Storage.java | 33 +- 5 files changed, 289 insertions(+), 81 deletions(-) diff --git a/src/com/fsck/k9/activity/setup/FolderSettings.java b/src/com/fsck/k9/activity/setup/FolderSettings.java index 3533eef88..29b6d51b5 100644 --- a/src/com/fsck/k9/activity/setup/FolderSettings.java +++ b/src/com/fsck/k9/activity/setup/FolderSettings.java @@ -143,14 +143,6 @@ public class FolderSettings extends K9PreferenceActivity public void onResume() { super.onResume(); - try - { - mFolder.refresh(Preferences.getPreferences(this)); - } - catch (MessagingException me) - { - Log.e(K9.LOG_TAG, "Could not refresh folder preferences for folder " + mFolder.getName(), me); - } } private void saveSettings() throws MessagingException @@ -164,6 +156,8 @@ public class FolderSettings extends K9PreferenceActivity mFolder.setSyncClass(FolderClass.valueOf(mSyncClass.getValue())); mFolder.setPushClass(FolderClass.valueOf(mPushClass.getValue())); + mFolder.save(); + FolderClass newPushClass = mFolder.getPushClass(); FolderClass newDisplayClass = mFolder.getDisplayClass(); diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 3878a8bae..286afdb99 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -533,15 +533,24 @@ public class MessagingController implements Runnable LocalStore localStore = account.getLocalStore(); HashSet remoteFolderNames = new HashSet(); - for (int i = 0, count = remoteFolders.size(); i < count; i++) + List foldersToCreate = new LinkedList(); + + localFolders = localStore.getPersonalNamespaces(false); + HashSet localFolderNames = new HashSet(); + for (Folder localFolder : localFolders) { - LocalFolder localFolder = localStore.getFolder(remoteFolders.get(i).getName()); - if (!localFolder.exists()) - { - localFolder.create(FolderType.HOLDS_MESSAGES, account.getDisplayCount()); - } - remoteFolderNames.add(remoteFolders.get(i).getName()); + localFolderNames.add(localFolder.getName()); } + for (Folder remoteFolder : remoteFolders) + { + if (localFolderNames.contains(remoteFolder.getName()) == false) + { + LocalFolder localFolder = localStore.getFolder(remoteFolder.getName()); + foldersToCreate.add(localFolder); + } + remoteFolderNames.add(remoteFolder.getName()); + } + localStore.createFolders(foldersToCreate, account.getDisplayCount()); localFolders = localStore.getPersonalNamespaces(false); diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index deab7ed5f..a68e861b2 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteStatement; import android.net.Uri; import android.util.Log; @@ -101,7 +102,7 @@ public class LocalStore extends Store implements Serializable static private String GET_FOLDER_COLS = "id, name, unread_count, visible_limit, last_updated, status, push_state, last_pushed, flagged_count, integrate, top_group, poll_class, push_class, display_class"; - protected static final int DB_VERSION = 41; + protected static final int DB_VERSION = 42; protected String uUid = null; @@ -133,6 +134,11 @@ public class LocalStore extends Store implements Serializable database.switchProvider(newStorageProviderId); } + protected SharedPreferences getPreferences() + { + return Preferences.getPreferences(mApplication).getPreferences(); + } + private class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition { @Override @@ -334,8 +340,7 @@ public class LocalStore extends Store implements Serializable try { - SharedPreferences prefs = Preferences.getPreferences(mApplication).getPreferences(); - SharedPreferences.Editor editor = prefs.edit(); + SharedPreferences prefs = getPreferences(); cursor = db.rawQuery("SELECT id, name FROM folders", null); while (cursor.moveToNext()) { @@ -343,16 +348,13 @@ public class LocalStore extends Store implements Serializable { int id = cursor.getInt(0); String name = cursor.getString(1); - update41Metadata(db, prefs, editor, id, name); + update41Metadata(db, prefs, id, name); } catch (Exception e) { Log.e(K9.LOG_TAG," error trying to ugpgrade a folder class: " +e); } } - editor.commit(); - - } @@ -369,6 +371,32 @@ public class LocalStore extends Store implements Serializable } } } + if (db.getVersion() == 41) + { + try + { + long startTime = System.currentTimeMillis(); + SharedPreferences.Editor editor = getPreferences().edit(); + + List folders = getPersonalNamespaces(true); + for (Folder folder : folders) + { + if (folder instanceof LocalFolder) + { + LocalFolder lFolder = (LocalFolder)folder; + lFolder.save(editor); + } + } + + editor.commit(); + long endTime = System.currentTimeMillis(); + Log.i(K9.LOG_TAG, "Putting folder preferences for " + folders.size() + " folders back into Preferences took " + (endTime - startTime) + " ms"); + } + catch (Exception e) + { + Log.e(K9.LOG_TAG, "Could not replace Preferences in upgrade from DB_VERSION 41", e); + } + } } } @@ -400,8 +428,7 @@ public class LocalStore extends Store implements Serializable //} } - - private void update41Metadata(final SQLiteDatabase db, SharedPreferences prefs, SharedPreferences.Editor editor, int id, String name) + private void update41Metadata(final SQLiteDatabase db, SharedPreferences prefs, int id, String name) { @@ -448,15 +475,6 @@ public class LocalStore extends Store implements Serializable db.execSQL("UPDATE folders SET integrate = ?, top_group = ?, poll_class=?, push_class =?, display_class = ? WHERE id = ?", new Object[] { integrate, inTopGroup,syncClass,pushClass,displayClass, id }); - // now that we managed to update this folder, obliterate the old data - - editor.remove(uUid+"."+name + ".displayMode"); - editor.remove(uUid+"."+name + ".syncMode"); - editor.remove(uUid+"."+name + ".pushMode"); - editor.remove(uUid+"."+name + ".inTopGroup"); - editor.remove(uUid+"."+name + ".integrate"); - - } } @@ -1286,6 +1304,64 @@ public class LocalStore extends Store implements Serializable public int size; } + public void createFolders(final List foldersToCreate, final int visibleLimit) throws UnavailableStorageException + { + database.execute(true, new DbCallback() + { + @Override + public Void doDbWork(final SQLiteDatabase db) throws WrappedException + { + for (LocalFolder folder : foldersToCreate) + { + String name = folder.getName(); + final LocalFolder.PreferencesHolder prefHolder = folder.new PreferencesHolder(); + + // When created, special folders should always be displayed + // inbox should be integrated + // and the inbox and drafts folders should be syncced by default + if (mAccount.isSpecialFolder(name)) + { + prefHolder.inTopGroup = true; + prefHolder.displayClass = LocalFolder.FolderClass.FIRST_CLASS; + if (name.equalsIgnoreCase(K9.INBOX)) + { + prefHolder.integrate = true; + prefHolder.pushClass = LocalFolder.FolderClass.FIRST_CLASS; + } + else + { + prefHolder.pushClass = LocalFolder.FolderClass.INHERITED; + + } + if ( name.equalsIgnoreCase(K9.INBOX) || + name.equalsIgnoreCase(mAccount.getDraftsFolderName()) ) + { + prefHolder.syncClass = LocalFolder.FolderClass.FIRST_CLASS; + } + else + { + prefHolder.syncClass = LocalFolder.FolderClass.NO_CLASS; + } + } + folder.refresh(name, prefHolder); // Recover settings from Preferences + + db.execSQL("INSERT INTO folders (name, visible_limit, top_group, display_class, poll_class, push_class, integrate) VALUES (?, ?, ?, ?, ?, ?, ?)", new Object[] + { + name, + visibleLimit, + prefHolder.inTopGroup ? 1 : 0, + prefHolder.displayClass.name(), + prefHolder.syncClass.name(), + prefHolder.pushClass.name(), + prefHolder.integrate ? 1 : 0, + }); + + } + return null; + } + }); + } + public class LocalFolder extends Folder implements Serializable { /** @@ -1297,6 +1373,7 @@ public class LocalStore extends Store implements Serializable private int mUnreadMessageCount = -1; private int mFlaggedMessageCount = -1; private int mVisibleLimit = -1; + private String prefId = null; private FolderClass mDisplayClass = FolderClass.NO_CLASS; private FolderClass mSyncClass = FolderClass.INHERITED; private FolderClass mPushClass = FolderClass.SECOND_CLASS; @@ -1486,55 +1563,22 @@ public class LocalStore extends Store implements Serializable { throw new MessagingException("Folder " + mName + " already exists."); } - database.execute(false, new DbCallback() - { - @Override - public Void doDbWork(final SQLiteDatabase db) throws WrappedException - { - db.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[] - { - mName, - visibleLimit - }); - - - return null; - } - }); - - // When created, special folders should always be displayed - // inbox should be integrated - // and the inbox and drafts folders should be syncced by default - if (mAccount.isSpecialFolder(mName)) - { - LocalFolder f = new LocalFolder(mName); - f.open(OpenMode.READ_WRITE); - f.setInTopGroup(true); - f.setDisplayClass(FolderClass.FIRST_CLASS); - if (mName.equalsIgnoreCase(K9.INBOX)) - { - f.setIntegrate(true); - f.setPushClass(FolderClass.FIRST_CLASS); - } - else - { - f.setPushClass(FolderClass.INHERITED); - - } - if ( mName.equalsIgnoreCase(K9.INBOX) || - mName.equalsIgnoreCase(mAccount.getDraftsFolderName()) ) - { - f.setSyncClass(FolderClass.FIRST_CLASS); - } - else - { - f.setSyncClass(FolderClass.NO_CLASS); - } - } + List foldersToCreate = new ArrayList(1); + foldersToCreate.add(this); + LocalStore.this.createFolders(foldersToCreate, visibleLimit); return true; } + private class PreferencesHolder + { + FolderClass displayClass = mDisplayClass; + FolderClass syncClass = mSyncClass; + FolderClass pushClass = mPushClass; + boolean inTopGroup = mInTopGroup; + boolean integrate = mIntegrate; + } + @Override public void close() { @@ -1712,6 +1756,7 @@ public class LocalStore extends Store implements Serializable throw (MessagingException) e.getCause(); } } + public String getPushState() { return mPushState; @@ -1788,6 +1833,134 @@ public class LocalStore extends Store implements Serializable updateFolderColumn( "integrate", mIntegrate ? 1 : 0 ); } + private String getPrefId(String name) + { + if (prefId == null) + { + prefId = uUid + "." + name; + } + + return prefId; + } + + private String getPrefId() throws MessagingException + { + open(OpenMode.READ_WRITE); + return getPrefId(mName); + + } + + public void delete() throws MessagingException + { + String id = getPrefId(); + + SharedPreferences.Editor editor = LocalStore.this.getPreferences().edit(); + + editor.remove(id + ".displayMode"); + editor.remove(id + ".syncMode"); + editor.remove(id + ".pushMode"); + editor.remove(id + ".inTopGroup"); + editor.remove(id + ".integrate"); + + editor.commit(); + } + + public void save() throws MessagingException + { + SharedPreferences.Editor editor = LocalStore.this.getPreferences().edit(); + save(editor); + editor.commit(); + } + + public void save(SharedPreferences.Editor editor) throws MessagingException + { + String id = getPrefId(); + + // there can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX + if (mDisplayClass == FolderClass.NO_CLASS && !K9.INBOX.equals(getName())) + { + editor.remove(id + ".displayMode"); + } + else + { + editor.putString(id + ".displayMode", mDisplayClass.name()); + } + + if (mSyncClass == FolderClass.INHERITED && !K9.INBOX.equals(getName())) + { + editor.remove(id + ".syncMode"); + } + else + { + editor.putString(id + ".syncMode", mSyncClass.name()); + } + + if (mPushClass == FolderClass.SECOND_CLASS && !K9.INBOX.equals(getName())) + { + editor.remove(id + ".pushMode"); + } + else + { + editor.putString(id + ".pushMode", mPushClass.name()); + } + editor.putBoolean(id + ".inTopGroup", mInTopGroup); + + editor.putBoolean(id + ".integrate", mIntegrate); + + } + + public void refresh(String name, PreferencesHolder prefHolder) + { + String id = getPrefId(name); + + SharedPreferences preferences = LocalStore.this.getPreferences(); + + try + { + prefHolder.displayClass = FolderClass.valueOf(preferences.getString(id + ".displayMode", + prefHolder.displayClass.name())); + } + catch (Exception e) + { + Log.e(K9.LOG_TAG, "Unable to load displayMode for " + getName(), e); + } + if (prefHolder.displayClass == FolderClass.NONE) + { + prefHolder.displayClass = FolderClass.NO_CLASS; + } + + try + { + prefHolder.syncClass = FolderClass.valueOf(preferences.getString(id + ".syncMode", + prefHolder.syncClass.name())); + } + catch (Exception e) + { + Log.e(K9.LOG_TAG, "Unable to load syncMode for " + getName(), e); + + } + if (prefHolder.syncClass == FolderClass.NONE) + { + prefHolder.syncClass = FolderClass.INHERITED; + } + + try + { + prefHolder.pushClass = FolderClass.valueOf(preferences.getString(id + ".pushMode", + prefHolder.pushClass.name())); + } + catch (Exception e) + { + Log.e(K9.LOG_TAG, "Unable to load pushMode for " + getName(), e); + } + if (prefHolder.pushClass == FolderClass.NONE) + { + prefHolder.pushClass = FolderClass.INHERITED; + } + prefHolder.inTopGroup = preferences.getBoolean(id + ".inTopGroup", prefHolder.inTopGroup); + prefHolder.integrate = preferences.getBoolean(id + ".integrate", prefHolder.integrate); + + } @Override public void fetch(final Message[] messages, final FetchProfile fp, final MessageRetrievalListener listener) @@ -3371,7 +3544,6 @@ public class LocalStore extends Store implements Serializable private boolean mToMe = false; private boolean mCcMe = false; - private boolean mHeadersLoaded = false; private boolean mMessageDirty = false; diff --git a/src/com/fsck/k9/preferences/Editor.java b/src/com/fsck/k9/preferences/Editor.java index 434e68067..bdbedf39b 100644 --- a/src/com/fsck/k9/preferences/Editor.java +++ b/src/com/fsck/k9/preferences/Editor.java @@ -97,6 +97,7 @@ public class Editor implements android.content.SharedPreferences.Editor { storage.remove(removeKey); } + Map insertables = new HashMap(); for (Entry entry : changes.entrySet()) { String key = entry.getKey(); @@ -104,9 +105,10 @@ public class Editor implements android.content.SharedPreferences.Editor String oldValue = snapshot.get(key); if (removeAll || removals.contains(key) || !newValue.equals(oldValue)) { - storage.put(key, newValue); + insertables.put(key, newValue); } } + storage.put(insertables); } }; storage.doInTransaction(committer); diff --git a/src/com/fsck/k9/preferences/Storage.java b/src/com/fsck/k9/preferences/Storage.java index b1825e7ce..40b2f7893 100644 --- a/src/com/fsck/k9/preferences/Storage.java +++ b/src/com/fsck/k9/preferences/Storage.java @@ -4,7 +4,9 @@ import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import android.util.Log; import com.fsck.k9.K9; @@ -254,11 +256,40 @@ public class Storage implements SharedPreferences } protected void put(String key, String value) + { + ContentValues cv = generateCV(key, value); + workingDB.get().insert("preferences_storage", "primkey", cv); + liveUpdate(key, value); + } + + protected void put(Map insertables) + { + String sql = "insert into preferences_storage (primkey, value) VALUES (?, ?)"; + SQLiteStatement stmt = workingDB.get().compileStatement(sql); + + for (Map.Entry 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 ContentValues generateCV(String key, String value) { ContentValues cv = new ContentValues(); cv.put("primkey", key); cv.put("value", value); - workingDB.get().insert("preferences_storage", "primkey", cv); + return cv; + } + + private void liveUpdate(String key, String value) + { workingStorage.get().put(key, value); keyChange(key);