diff --git a/src/net/sqlcipher/DatabaseErrorHandler.java b/src/net/sqlcipher/DatabaseErrorHandler.java new file mode 100644 index 00000000..811568fe --- /dev/null +++ b/src/net/sqlcipher/DatabaseErrorHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import net.sqlcipher.database.SQLiteDatabase; + +/** + * An interface to let the apps define the actions to take when the following errors are detected + * database corruption + */ +public interface DatabaseErrorHandler { + + /** + * defines the method to be invoked when database corruption is detected. + * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption + * is detected. + */ + void onCorruption(SQLiteDatabase dbObj); +} diff --git a/src/net/sqlcipher/DefaultDatabaseErrorHandler.java b/src/net/sqlcipher/DefaultDatabaseErrorHandler.java new file mode 100644 index 00000000..45f8a50e --- /dev/null +++ b/src/net/sqlcipher/DefaultDatabaseErrorHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher.database; + +import java.io.File; +import java.util.List; + +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteException; + +import android.util.Log; +import android.util.Pair; + +/** + * Default class used to define the actions to take when the database corruption is reported + * by sqlite. + *

+ * If null is specified for DatabaeErrorHandler param in the above calls, then this class is used + * as the default {@link DatabaseErrorHandler}. + */ +public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler { + + private static final String TAG = "DefaultDatabaseErrorHandler"; + + /** + * defines the default method to be invoked when database corruption is detected. + * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption + * is detected. + */ + public void onCorruption(SQLiteDatabase dbObj) { + Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath()); + + if (dbObj.isOpen()) { + try { + dbObj.close(); + } catch (SQLiteException e) { + /* ignore */ + } + } + + deleteDatabaseFile(dbObj.getPath()); + } + + private void deleteDatabaseFile(String fileName) { + if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) { + return; + } + Log.e(TAG, "deleting the database file: " + fileName); + try { + new File(fileName).delete(); + } catch (Exception e) { + /* print warning and ignore exception */ + Log.w(TAG, "delete failed: " + e.getMessage()); + } + } +} diff --git a/src/net/sqlcipher/database/SQLiteDatabase.java b/src/net/sqlcipher/database/SQLiteDatabase.java index 7933d439..f69a5077 100644 --- a/src/net/sqlcipher/database/SQLiteDatabase.java +++ b/src/net/sqlcipher/database/SQLiteDatabase.java @@ -895,6 +895,11 @@ public Cursor newCursor(SQLiteDatabase db, * @throws SQLiteException if the database cannot be opened */ public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) { + return openDatabase(path, password, factory, flags, databaseHook, new DefaultDatabaseErrorHandler()); + } + + public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { SQLiteDatabase sqliteDatabase = null; try { // Open the database. @@ -906,15 +911,8 @@ public static SQLiteDatabase openDatabase(String path, String password, CursorFa sqliteDatabase.enableSqlProfiling(path); } } catch (SQLiteDatabaseCorruptException e) { - // Try to recover from this, if we can. - // TODO: should we do this for other open failures? - Log.e(TAG, "Deleting and re-creating corrupt database " + path, e); - // EventLog.writeEvent(EVENT_DB_CORRUPT, path); - if (!path.equalsIgnoreCase(":memory")) { - // delete is only for non-memory database files - new File(path).delete(); - } - sqliteDatabase = new SQLiteDatabase(path, password, factory, flags, databaseHook); + errorHandler.onCorruption(sqliteDatabase); + sqliteDatabase = openDatabase(path, password, factory, flags, databaseHook, errorHandler); } ActiveDatabases.getInstance().mActiveDatabases.add( new WeakReference(sqliteDatabase)); @@ -928,6 +926,11 @@ public static SQLiteDatabase openOrCreateDatabase(File file, String password, Cu public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook); } + + public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook, + DatabaseErrorHandler errorHandler) { + return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler); + } /** * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY). diff --git a/src/net/sqlcipher/database/SQLiteOpenHelper.java b/src/net/sqlcipher/database/SQLiteOpenHelper.java index c37cf0a4..b69a9a2b 100644 --- a/src/net/sqlcipher/database/SQLiteOpenHelper.java +++ b/src/net/sqlcipher/database/SQLiteOpenHelper.java @@ -19,6 +19,7 @@ import java.io.File; import android.content.Context; +import net.sqlcipher.database.DefaultDatabaseErrorHandler; import net.sqlcipher.database.SQLiteDatabase.CursorFactory; import android.util.Log; @@ -42,24 +43,53 @@ public abstract class SQLiteOpenHelper { private SQLiteDatabase mDatabase = null; private boolean mIsInitializing = false; + private final DatabaseErrorHandler mErrorHandler; + + /** + * Create a helper object to create, open, and/or manage a database. + * This method always returns very quickly. The database is not actually + * created or opened until one of {@link #getWritableDatabase} or + * {@link #getReadableDatabase} is called. + * + * @param context to use to open or create the database + * @param name of the database file, or null for an in-memory database + * @param factory to use for creating cursor objects, or null for the default + * @param version number of the database (starting at 1); if the database is older, + * {@link #onUpgrade} will be used to upgrade the database; if the database is + * newer, {@link #onDowngrade} will be used to downgrade the database + */ + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { + this(context, name, factory, version, new DefaultDatabaseErrorHandler()); + } + /** * Create a helper object to create, open, and/or manage a database. * The database is not actually created or opened until one of * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. * + *

Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be + * used to handle corruption when sqlite reports database corruption.

+ * * @param context to use to open or create the database * @param name of the database file, or null for an in-memory database * @param factory to use for creating cursor objects, or null for the default * @param version number of the database (starting at 1); if the database is older, * {@link #onUpgrade} will be used to upgrade the database + * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database + * corruption. */ - public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { + public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, + DatabaseErrorHandler errorHandler) { if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); + if (errorHandler == null) { + throw new IllegalArgumentException("DatabaseErrorHandler param value can't be null."); + } mContext = context; mName = name; mFactory = factory; mNewVersion = version; + mErrorHandler = errorHandler; } /** @@ -104,7 +134,7 @@ public synchronized SQLiteDatabase getWritableDatabase(String password) { if (!dbPathFile.exists()) dbPathFile.getParentFile().mkdirs(); - db = SQLiteDatabase.openOrCreateDatabase(path, password, mFactory); + db = SQLiteDatabase.openOrCreateDatabase(path, password, mFactory, null, mErrorHandler); // db = SQLiteDatabase.openDatabase(path,mFactory , SQLiteDatabase.OPEN_READWRITE);