diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java
index 8ff35c64c..51bf0ec70 100644
--- a/src/com/fsck/k9/Account.java
+++ b/src/com/fsck/k9/Account.java
@@ -35,18 +35,6 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class Account implements BaseAccount
{
- /**
- * @see Account#setLocalStoreMigrationListener(LocalStoreMigrationListener)
- *
- */
- public interface LocalStoreMigrationListener
- {
-
- void onLocalStoreMigration(String oldStoreUri,
- String newStoreUri) throws MessagingException;
-
- }
-
public static final String EXPUNGE_IMMEDIATELY = "EXPUNGE_IMMEDIATELY";
public static final String EXPUNGE_MANUALLY = "EXPUNGE_MANUALLY";
public static final String EXPUNGE_ON_POLL = "EXPUNGE_ON_POLL";
@@ -89,7 +77,6 @@ public class Account implements BaseAccount
* the moment.
*/
private final boolean mIsInUse = false;
- private LocalStoreMigrationListener mLocalStoreMigrationListener;
private String mTransportUri;
private String mDescription;
private String mAlwaysBcc;
@@ -1389,12 +1376,11 @@ public class Account implements BaseAccount
* Never
- * You have to invoke {@link #unlockRead()} when you're
- * done with the storage.
- *
- * You have to invoke {@link #unlockWrite()} when you're
- * done with the storage.
- *
- * You have to invoke {@link #unlockWrite()} when you're
- * done with the storage.
- *
- * Can be instructed to start a transaction if none is currently active in
- * the current thread. Callback will participe in any active transaction (no
- * inner transaction created).
- * null
.
* @throws MessagingException
*/
- public void switchLocalStorage(String newStorageProviderId) throws MessagingException
+ public void switchLocalStorage(final String newStorageProviderId) throws MessagingException
{
- if (this.mLocalStoreMigrationListener != null && !mLocalStorageProviderId.equals(newStorageProviderId))
+ if (!mLocalStorageProviderId.equals(newStorageProviderId))
{
- mLocalStoreMigrationListener.onLocalStoreMigration(mLocalStorageProviderId,
- newStorageProviderId);
+ getLocalStore().switchLocalStorage(newStorageProviderId);
}
}
@@ -1555,19 +1541,6 @@ public class Account implements BaseAccount
return mIsInUse;
}
- /**
- * Set a listener to be informed when the underlying {@link StorageProvider}
- * of the {@link LocalStore} of this account changes. (e.g. via
- * {@link #switchLocalStorage(Context, String)})
- *
- * @param listener
- * @see #switchLocalStorage(Context, String)
- */
- public void setLocalStoreMigrationListener(LocalStoreMigrationListener listener)
- {
- this.mLocalStoreMigrationListener = listener;
- }
-
public synchronized CryptoProvider getCryptoProvider()
{
if (mCryptoProvider == null)
diff --git a/src/com/fsck/k9/helper/Utility.java b/src/com/fsck/k9/helper/Utility.java
index e60ec304d..5b1360434 100644
--- a/src/com/fsck/k9/helper/Utility.java
+++ b/src/com/fsck/k9/helper/Utility.java
@@ -2,10 +2,16 @@
package com.fsck.k9.helper;
import android.text.Editable;
+import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
+
+import com.fsck.k9.K9;
import com.fsck.k9.mail.filter.Base64;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -496,4 +502,126 @@ public class Utility
}
}
+ /**
+ * @param parentDir
+ * @param name
+ * Never null
.
+ */
+ public static void touchFile(final File parentDir, final String name)
+ {
+ final File file = new File(parentDir, name);
+ try
+ {
+ if (!file.exists())
+ {
+ file.createNewFile();
+ }
+ else
+ {
+ file.setLastModified(System.currentTimeMillis());
+ }
+ }
+ catch (Exception e)
+ {
+ Log.d(K9.LOG_TAG, "Unable to touch file: " + file.getAbsolutePath(), e);
+ }
+ }
+
+ /**
+ * @param from
+ * @param to
+ * @return
+ */
+ public static boolean move(final File from, final File to)
+ {
+ if (to.exists())
+ {
+ to.delete();
+ }
+ to.getParentFile().mkdirs();
+
+ try
+ {
+ FileInputStream in = new FileInputStream(from);
+ FileOutputStream out = new FileOutputStream(to);
+ byte[] buffer = new byte[1024];
+ int count = -1;
+ while ((count = in.read(buffer)) > 0)
+ {
+ out.write(buffer, 0, count);
+ }
+ out.close();
+ in.close();
+ from.delete();
+ return true;
+ }
+ catch (Exception e)
+ {
+ Log.w(K9.LOG_TAG, "cannot move " + from.getAbsolutePath() + " to " + to.getAbsolutePath(), e);
+ return false;
+ }
+
+ }
+
+ /**
+ * @param fromDir
+ * @param toDir
+ */
+ public static void moveRecursive(final File fromDir, final File toDir)
+ {
+ if (!fromDir.exists())
+ {
+ return;
+ }
+ if (!fromDir.isDirectory())
+ {
+ if (toDir.exists())
+ {
+ if (!toDir.delete())
+ {
+ Log.w(K9.LOG_TAG, "cannot delete already existing file/directory " + toDir.getAbsolutePath());
+ }
+ }
+ if (!fromDir.renameTo(toDir))
+ {
+ Log.w(K9.LOG_TAG, "cannot rename " + fromDir.getAbsolutePath() + " to " + toDir.getAbsolutePath() + " - moving instead");
+ move(fromDir, toDir);
+ }
+ return;
+ }
+ if (!toDir.exists() || !toDir.isDirectory())
+ {
+ if (toDir.exists() )
+ {
+ toDir.delete();
+ }
+ if (!toDir.mkdirs())
+ {
+ Log.w(K9.LOG_TAG, "cannot create directory " + toDir.getAbsolutePath());
+ }
+ }
+ File[] files = fromDir.listFiles();
+ for (File file : files)
+ {
+ if (file.isDirectory())
+ {
+ moveRecursive(file, new File(toDir, file.getName()));
+ file.delete();
+ }
+ else
+ {
+ File target = new File(toDir, file.getName());
+ if (!file.renameTo(target))
+ {
+ Log.w(K9.LOG_TAG, "cannot rename " + file.getAbsolutePath() + " to " + target.getAbsolutePath() + " - moving instead");
+ move(file, target);
+ }
+ }
+ }
+ if (!fromDir.delete())
+ {
+ Log.w(K9.LOG_TAG, "cannot delete " + fromDir.getAbsolutePath());
+ }
+ }
+
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 1cfad3753..9199dd130 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -3,7 +3,6 @@ package com.fsck.k9.mail.store;
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -23,9 +22,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import org.apache.commons.io.IOUtils;
@@ -35,7 +31,6 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
-import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
@@ -45,7 +40,6 @@ import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
-import com.fsck.k9.Account.LocalStoreMigrationListener;
import com.fsck.k9.controller.MessageRemovalListener;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.Regex;
@@ -57,10 +51,10 @@ import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.Store;
-import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.filter.Base64OutputStream;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
@@ -68,6 +62,8 @@ import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
+import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
+import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.provider.AttachmentProvider;
@@ -76,46 +72,9 @@ import com.fsck.k9.provider.AttachmentProvider;
* Implements a SQLite database backed local store for Messages.
*
*/
-public class LocalStore extends Store implements Serializable, LocalStoreMigrationListener
+public class LocalStore extends Store implements Serializable
{
- /**
- * Callback interface for DB operations. Concept is similar to Spring
- * HibernateCallback.
- *
- * @param null
.
- * @return Any relevant data. Can be null
.
- * @throws WrappedException
- * @throws UnavailableStorageException
- */
- T doDbWork(SQLiteDatabase db) throws WrappedException, UnavailableStorageException;
- }
-
- /**
- * Workaround exception wrapper used to keep the inner exception generated
- * in a {@link DbCallback}.
- */
- protected static class WrappedException extends RuntimeException
- {
- /**
- *
- */
- private static final long serialVersionUID = 8184421232587399369L;
-
- public WrappedException(final Exception cause)
- {
- super(cause);
- }
- }
-
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
/**
@@ -123,35 +82,10 @@ public class LocalStore extends Store implements Serializable, LocalStoreMigrati
*/
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static final int DB_VERSION = 39;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN, Flag.FLAGGED };
private static final int MAX_SMART_HTMLIFY_MESSAGE_LENGTH = 1024 * 256 ;
- private String mStorageProviderId;
-
- private SQLiteDatabase mDb;
-
- {
- final ReadWriteLock lock = new ReentrantReadWriteLock(true);
- mReadLock = lock.readLock();
- mWriteLock = lock.writeLock();
- }
-
- /**
- * Reentrant read lock
- */
- protected final Lock mReadLock;
-
- /**
- * Reentrant write lock (if you lock it 2x from the same thread, you have to
- * unlock it 2x to release it)
- */
- protected final Lock mWriteLock;
-
- private Application mApplication;
- private String uUid = null;
-
private static Setnull
.
- *
- * @throws UnavailableStorageException
- * If storage can't be locked because it is not available.
- */
- protected void lockWrite(final String providerId) throws UnavailableStorageException
- {
- mWriteLock.lock();
- try
+ @Override
+ public int getVersion()
{
- StorageManager.getInstance(mApplication).lockProvider(providerId);
+ return DB_VERSION;
}
- catch (UnavailableStorageException e)
+
+ @Override
+ public void doDbUpgrade(final SQLiteDatabase db)
{
- mWriteLock.unlock();
- throw e;
- }
- catch (RuntimeException e)
- {
- mWriteLock.unlock();
- throw e;
- }
- }
-
- protected void unlockWrite()
- {
- unlockWrite(mStorageProviderId);
- }
-
- protected void unlockWrite(final String providerId)
- {
- StorageManager.getInstance(mApplication).unlockProvider(providerId);
- mWriteLock.unlock();
- }
-
- /**
- * Execute a DB callback in a shared context (doesn't prevent concurrent
- * shared executions), taking care of locking the DB storage.
- *
- * true
the callback must be executed in a
- * transactional context.
- * @param callback
- * Never null
.
- *
- * @param null
.
- * @return DB file.
- * @throws UnavailableStorageException
- */
- protected File prepareStorage(final String providerId) throws UnavailableStorageException
- {
- final StorageManager storageManager = StorageManager.getInstance(mApplication);
-
- final File databaseFile;
- final File databaseParentDir;
- databaseFile = storageManager.getDatabase(uUid, providerId);
- databaseParentDir = databaseFile.getParentFile();
- if (databaseParentDir.isFile())
- {
- // should be safe to inconditionally delete clashing file: user is not supposed to mess with our directory
- databaseParentDir.delete();
- }
- if (!databaseParentDir.exists())
- {
- if (!databaseParentDir.mkdirs())
- {
- // Android seems to be unmounting the storage...
- throw new UnavailableStorageException("Unable to access: " + databaseParentDir);
- }
- touchFile(databaseParentDir, ".nomedia");
- }
-
- final File attachmentDir;
- final File attachmentParentDir;
- attachmentDir = storageManager
- .getAttachmentDirectory(uUid, providerId);
- attachmentParentDir = attachmentDir.getParentFile();
- if (!attachmentParentDir.exists())
- {
- attachmentParentDir.mkdirs();
- touchFile(attachmentParentDir, ".nomedia");
- }
- if (!attachmentDir.exists())
- {
- attachmentDir.mkdirs();
- }
- return databaseFile;
- }
-
- /**
- * @param parentDir
- * @param name
- * Never null
.
- */
- protected void touchFile(final File parentDir, String name)
- {
- final File file = new File(parentDir, name);
- try
- {
- if (!file.exists())
- {
- file.createNewFile();
- }
- else
- {
- file.setLastModified(System.currentTimeMillis());
- }
- }
- catch (Exception e)
- {
- Log.d(K9.LOG_TAG, "Unable to touch file: " + file.getAbsolutePath(), e);
- }
- }
-
- private void doDbUpgrade(final SQLiteDatabase db, final Application application)
- {
Log.i(K9.LOG_TAG, String.format("Upgrading database from version %d to version %d",
db.getVersion(), DB_VERSION));
- AttachmentProvider.clear(application);
+ AttachmentProvider.clear(mApplication);
try
{
@@ -804,6 +332,7 @@ public class LocalStore extends Store implements Serializable, LocalStoreMigrati
// Log.e(K9.LOG_TAG, "Exception while force pruning attachments during DB update", me);
//}
}
+ }
public long getSize() throws UnavailableStorageException
{
@@ -811,9 +340,9 @@ public class LocalStore extends Store implements Serializable, LocalStoreMigrati
final StorageManager storageManager = StorageManager.getInstance(mApplication);
final File attachmentDirectory = storageManager.getAttachmentDirectory(uUid,
- mStorageProviderId);
+ database.getStorageProviderId());
- return execute(false, new DbCallback>()
+ database.execute(false, new DbCallback
>()
{
@Override
public List extends Folder> doDbWork(final SQLiteDatabase db) throws WrappedException
@@ -1002,108 +531,14 @@ public class LocalStore extends Store implements Serializable, LocalStoreMigrati
{
}
- /**
- * Delete the entire Store and it's backing database.
- * @throws UnavailableStorageException
- */
public void delete() throws UnavailableStorageException
{
- lockWrite();
- try
- {
- try
- {
- mDb.close();
- }
- catch (Exception e)
- {
-
- }
- final StorageManager storageManager = StorageManager.getInstance(mApplication);
- try
- {
- final File attachmentDirectory = storageManager.getAttachmentDirectory(uUid, mStorageProviderId);
- final File[] attachments = attachmentDirectory.listFiles();
- for (File attachment : attachments)
- {
- if (attachment.exists())
- {
- attachment.delete();
- }
- }
- if (attachmentDirectory.exists())
- {
- attachmentDirectory.delete();
- }
- }
- catch (Exception e)
- {
- }
- try
- {
- storageManager.getDatabase(uUid, mStorageProviderId).delete();
- }
- catch (Exception e)
- {
- Log.i(K9.LOG_TAG, "LocalStore: delete(): Unable to delete backing DB file", e);
- }
-
- // stop waiting for mount/unmount events
- StorageManager.getInstance(mApplication).removeListener(mStorageListener);
- }
- finally
- {
- unlockWrite();
- }
+ database.delete();
}
public void recreate() throws UnavailableStorageException
{
- lockWrite();
- try
- {
- try
- {
- mDb.close();
- }
- catch (Exception e)
- {
-
- }
- final StorageManager storageManager = StorageManager.getInstance(mApplication);
- try
- {
- final File attachmentDirectory = storageManager.getAttachmentDirectory(uUid, mStorageProviderId);
- final File[] attachments = attachmentDirectory.listFiles();
- for (File attachment : attachments)
- {
- if (attachment.exists())
- {
- attachment.delete();
- }
- }
- if (attachmentDirectory.exists())
- {
- attachmentDirectory.delete();
- }
- }
- catch (Exception e)
- {
- }
- try
- {
- storageManager.getDatabase(uUid, mStorageProviderId).delete();
- }
- catch (Exception e)
- {
-
- }
- openOrCreateDataspace(mApplication);
- }
- finally
- {
- unlockWrite();
- }
+ database.recreate();
}
public void pruneCachedAttachments() throws MessagingException
@@ -1116,7 +551,7 @@ public class LocalStore extends Store implements Serializable, LocalStoreMigrati
*/
public void pruneCachedAttachments(final boolean force) throws MessagingException
{
- execute(false, new DbCallback
null
.
+ * @return Any relevant data. Can be null
.
+ * @throws WrappedException
+ * @throws UnavailableStorageException
+ */
+ T doDbWork(SQLiteDatabase db) throws WrappedException, UnavailableStorageException;
+ }
+
+ public static interface SchemaDefinition
+ {
+ int getVersion();
+
+ /**
+ * @param db Never null
.
+ */
+ void doDbUpgrade(SQLiteDatabase db);
+ }
+
+ /**
+ * Workaround exception wrapper used to keep the inner exception generated
+ * in a {@link DbCallback}.
+ */
+ protected static class WrappedException extends RuntimeException
+ {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 8184421232587399369L;
+
+ public WrappedException(final Exception cause)
+ {
+ super(cause);
+ }
+ }
+
+ /**
+ * Open the DB on mount and close the DB on unmount
+ */
+ private class StorageListener implements StorageManager.StorageListener
+ {
+ @Override
+ public void onUnmount(final String providerId)
+ {
+ if (!providerId.equals(mStorageProviderId))
+ {
+ return;
+ }
+
+ if (K9.DEBUG)
+ {
+ Log.d(K9.LOG_TAG, "LockableDatabase: Closing DB " + uUid + " due to unmount event on StorageProvider: " + providerId);
+ }
+
+ try
+ {
+ lockWrite();
+ try
+ {
+ mDb.close();
+ }
+ finally
+ {
+ unlockWrite();
+ }
+ }
+ catch (UnavailableStorageException e)
+ {
+ Log.w(K9.LOG_TAG, "Unable to writelock on unmount", e);
+ }
+ }
+
+ @Override
+ public void onMount(final String providerId)
+ {
+ if (!providerId.equals(mStorageProviderId))
+ {
+ return;
+ }
+
+ if (K9.DEBUG)
+ {
+ Log.d(K9.LOG_TAG, "LockableDatabase: Opening DB " + uUid + " due to mount event on StorageProvider: " + providerId);
+ }
+
+ try
+ {
+ openOrCreateDataspace(mApplication);
+ }
+ catch (UnavailableStorageException e)
+ {
+ Log.e(K9.LOG_TAG, "Unable to open DB on mount", e);
+ }
+ }
+ }
+
+ private String mStorageProviderId;
+
+ private SQLiteDatabase mDb;
+ /**
+ * Reentrant read lock
+ */
+ private final Lock mReadLock;
+ /**
+ * Reentrant write lock (if you lock it 2x from the same thread, you have to
+ * unlock it 2x to release it)
+ */
+ private final Lock mWriteLock;
+
+ {
+ final ReadWriteLock lock = new ReentrantReadWriteLock(true);
+ mReadLock = lock.readLock();
+ mWriteLock = lock.writeLock();
+ }
+
+ private final StorageListener mStorageListener = new StorageListener();
+
+ private Application mApplication;
+
+ /**
+ * {@link ThreadLocal} to check whether a DB transaction is occuring in the
+ * current {@link Thread}.
+ *
+ * @see #execute(boolean, DbCallback)
+ */
+ private ThreadLocalnull
.
+ * @param uUid
+ * Never null
.
+ * @param schemaDefinition
+ * Never null
+ * You have to invoke {@link #unlockRead()} when you're
+ * done with the storage.
+ *
+ * You have to invoke {@link #unlockWrite()} when you're + * done with the storage. + *
+ * + * @throws UnavailableStorageException + * If storage can't be locked because it is not available. + */ + protected void lockWrite() throws UnavailableStorageException + { + lockWrite(mStorageProviderId); + } + + /** + * Lock the storage for exclusive access (other threads aren't allowed to + * run simultaneously) + * + *+ * You have to invoke {@link #unlockWrite()} when you're + * done with the storage. + *
+ * + * @param providerId + * Nevernull
.
+ *
+ * @throws UnavailableStorageException
+ * If storage can't be locked because it is not available.
+ */
+ protected void lockWrite(final String providerId) throws UnavailableStorageException
+ {
+ mWriteLock.lock();
+ try
+ {
+ getStorageManager().lockProvider(providerId);
+ }
+ catch (UnavailableStorageException e)
+ {
+ mWriteLock.unlock();
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ mWriteLock.unlock();
+ throw e;
+ }
+ }
+
+ protected void unlockWrite()
+ {
+ unlockWrite(mStorageProviderId);
+ }
+
+ protected void unlockWrite(final String providerId)
+ {
+ getStorageManager().unlockProvider(providerId);
+ mWriteLock.unlock();
+ }
+
+ /**
+ * Execute a DB callback in a shared context (doesn't prevent concurrent
+ * shared executions), taking care of locking the DB storage.
+ *
+ * + * Can be instructed to start a transaction if none is currently active in + * the current thread. Callback will participe in any active transaction (no + * inner transaction created). + *
+ * + * @param transactional + *true
the callback must be executed in a
+ * transactional context.
+ * @param callback
+ * Never null
.
+ *
+ * @param null
.
+ * @throws MessagingException
+ */
+ public void switchProvider(final String newProviderId) throws MessagingException
+ {
+ if (newProviderId.equals(mStorageProviderId))
+ {
+ Log.v(K9.LOG_TAG, "LockableDatabase: Ignoring provider switch request as they are equal: " + newProviderId);
+ return;
+ }
+
+ final String oldProviderId = mStorageProviderId;
+ lockWrite(oldProviderId);
+ try
+ {
+ lockWrite(newProviderId);
+ try
+ {
+ try
+ {
+ mDb.close();
+ }
+ catch (Exception e)
+ {
+ Log.i(K9.LOG_TAG, "Unable to close DB on local store migration", e);
+ }
+
+ final StorageManager storageManager = getStorageManager();
+
+ // create new path
+ prepareStorage(newProviderId);
+
+ // move all database files
+ Utility.moveRecursive(storageManager.getDatabase(uUid, oldProviderId), storageManager.getDatabase(uUid, newProviderId));
+ // move all attachment files
+ Utility.moveRecursive(storageManager.getAttachmentDirectory(uUid, oldProviderId), storageManager.getAttachmentDirectory(uUid, newProviderId));
+
+ mStorageProviderId = newProviderId;
+
+ // re-initialize this class with the new Uri
+ openOrCreateDataspace(mApplication);
+ }
+ finally
+ {
+ unlockWrite(newProviderId);
+ }
+ }
+ finally
+ {
+ unlockWrite(oldProviderId);
+ }
+ }
+
+ public void open() throws UnavailableStorageException
+ {
+ lockWrite();
+ try
+ {
+ openOrCreateDataspace(mApplication);
+ }
+ finally
+ {
+ unlockWrite();
+ }
+ StorageManager.getInstance(mApplication).addListener(mStorageListener);
+ }
+
+ /**
+ *
+ * @param application
+ * @throws UnavailableStorageException
+ */
+ protected void openOrCreateDataspace(final Application application) throws UnavailableStorageException
+ {
+
+ lockWrite();
+ try
+ {
+ final File databaseFile = prepareStorage(mStorageProviderId);
+ try
+ {
+ mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
+ }
+ catch (SQLiteException e)
+ {
+ // try to gracefully handle DB corruption - see issue 2537
+ Log.w(K9.LOG_TAG, "Unable to open DB " + databaseFile + " - removing file and retrying", e);
+ databaseFile.delete();
+ mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
+ }
+ if (mDb.getVersion() != mSchemaDefinition.getVersion())
+ {
+ mSchemaDefinition.doDbUpgrade(mDb);
+ }
+ }
+ finally
+ {
+ unlockWrite();
+ }
+ }
+
+ /**
+ * @param providerId
+ * Never null
.
+ * @return DB file.
+ * @throws UnavailableStorageException
+ */
+ protected File prepareStorage(final String providerId) throws UnavailableStorageException
+ {
+ final StorageManager storageManager = getStorageManager();
+
+ final File databaseFile;
+ final File databaseParentDir;
+ databaseFile = storageManager.getDatabase(uUid, providerId);
+ databaseParentDir = databaseFile.getParentFile();
+ if (databaseParentDir.isFile())
+ {
+ // should be safe to inconditionally delete clashing file: user is not supposed to mess with our directory
+ databaseParentDir.delete();
+ }
+ if (!databaseParentDir.exists())
+ {
+ if (!databaseParentDir.mkdirs())
+ {
+ // Android seems to be unmounting the storage...
+ throw new UnavailableStorageException("Unable to access: " + databaseParentDir);
+ }
+ Utility.touchFile(databaseParentDir, ".nomedia");
+ }
+
+ final File attachmentDir;
+ final File attachmentParentDir;
+ attachmentDir = storageManager
+ .getAttachmentDirectory(uUid, providerId);
+ attachmentParentDir = attachmentDir.getParentFile();
+ if (!attachmentParentDir.exists())
+ {
+ attachmentParentDir.mkdirs();
+ Utility.touchFile(attachmentParentDir, ".nomedia");
+ }
+ if (!attachmentDir.exists())
+ {
+ attachmentDir.mkdirs();
+ }
+ return databaseFile;
+ }
+
+ /**
+ * Delete the backing database.
+ *
+ * @throws UnavailableStorageException
+ */
+ public void delete() throws UnavailableStorageException
+ {
+ delete(false);
+ }
+
+ public void recreate() throws UnavailableStorageException
+ {
+ delete(true);
+ }
+
+ /**
+ * @param recreate
+ * true
if the DB should be recreated after delete
+ * @throws UnavailableStorageException
+ */
+ private void delete(final boolean recreate) throws UnavailableStorageException
+ {
+ lockWrite();
+ try
+ {
+ try
+ {
+ mDb.close();
+ }
+ catch (Exception e)
+ {
+
+ }
+ final StorageManager storageManager = getStorageManager();
+ try
+ {
+ final File attachmentDirectory = storageManager.getAttachmentDirectory(uUid, mStorageProviderId);
+ final File[] attachments = attachmentDirectory.listFiles();
+ for (File attachment : attachments)
+ {
+ if (attachment.exists())
+ {
+ attachment.delete();
+ }
+ }
+ if (attachmentDirectory.exists())
+ {
+ attachmentDirectory.delete();
+ }
+ }
+ catch (Exception e)
+ {
+ }
+ try
+ {
+ storageManager.getDatabase(uUid, mStorageProviderId).delete();
+ }
+ catch (Exception e)
+ {
+ Log.i(K9.LOG_TAG, "LockableDatabase: delete(): Unable to delete backing DB file", e);
+ }
+
+ if (recreate)
+ {
+ openOrCreateDataspace(mApplication);
+ }
+ else
+ {
+ // stop waiting for mount/unmount events
+ getStorageManager().removeListener(mStorageListener);
+ }
+ }
+ finally
+ {
+ unlockWrite();
+ }
+ }
+
+}