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.
This commit is contained in:
Dan Applebaum 2011-02-04 12:26:14 +00:00 committed by Jesse Vincent
parent ea3619b733
commit 40bdf99925
5 changed files with 289 additions and 81 deletions

View file

@ -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();

View file

@ -533,15 +533,24 @@ public class MessagingController implements Runnable
LocalStore localStore = account.getLocalStore();
HashSet<String> remoteFolderNames = new HashSet<String>();
for (int i = 0, count = remoteFolders.size(); i < count; i++)
List<LocalFolder> foldersToCreate = new LinkedList<LocalFolder>();
localFolders = localStore.getPersonalNamespaces(false);
HashSet<String> localFolderNames = new HashSet<String>();
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);

View file

@ -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<? extends Folder> 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<LocalFolder> foldersToCreate, final int visibleLimit) throws UnavailableStorageException
{
database.execute(true, new DbCallback<Void>()
{
@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<Void>()
{
@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<LocalFolder> foldersToCreate = new ArrayList<LocalFolder>(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;

View file

@ -97,6 +97,7 @@ public class Editor implements android.content.SharedPreferences.Editor
{
storage.remove(removeKey);
}
Map<String, String> insertables = new HashMap<String, String>();
for (Entry<String, String> 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);

View file

@ -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<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 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);