AsyncStorage improvements

Differential Revision: D2475604

committer: Service User <svcscm@fb.com>
This commit is contained in:
Andrei Coman 2015-09-24 02:43:25 -07:00 committed by facebook-github-bot-9
parent 7615d74d14
commit 33cc607c1f
4 changed files with 139 additions and 102 deletions

View File

@ -24,9 +24,9 @@ import com.facebook.react.bridge.ReadableArray;
import org.json.JSONException;
import org.json.JSONObject;
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.KEY_COLUMN;
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.TABLE_CATALYST;
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.VALUE_COLUMN;
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.KEY_COLUMN;
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.TABLE_CATALYST;
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.VALUE_COLUMN;
/**
* Helper for database operations.

View File

@ -9,16 +9,12 @@
package com.facebook.react.modules.storage;
import javax.annotation.Nullable;
import java.util.HashSet;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask;
@ -31,18 +27,19 @@ import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.SetBuilder;
import com.facebook.react.modules.common.ModuleDataCleaner;
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.KEY_COLUMN;
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.TABLE_CATALYST;
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.VALUE_COLUMN;
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.KEY_COLUMN;
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.TABLE_CATALYST;
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.VALUE_COLUMN;
public final class AsyncStorageModule
extends ReactContextBaseJavaModule implements ModuleDataCleaner.Cleanable {
private @Nullable SQLiteDatabase mDb;
private ReactDatabaseSupplier mReactDatabaseSupplier;
private boolean mShuttingDown = false;
public AsyncStorageModule(ReactApplicationContext reactContext) {
super(reactContext);
mReactDatabaseSupplier = new ReactDatabaseSupplier(reactContext);
}
@Override
@ -59,10 +56,6 @@ public final class AsyncStorageModule
@Override
public void onCatalystInstanceDestroy() {
mShuttingDown = true;
if (mDb != null && mDb.isOpen()) {
mDb.close();
mDb = null;
}
}
@Override
@ -74,10 +67,17 @@ public final class AsyncStorageModule
new Callback() {
@Override
public void invoke(Object... args) {
if (args.length > 0) {
throw new RuntimeException("Clearing AsyncLocalStorage failed: " + args[0]);
if (args.length == 0) {
FLog.d(ReactConstants.TAG, "Cleaned AsyncLocalStorage.");
return;
}
FLog.d(ReactConstants.TAG, "Cleaned AsyncLocalStorage.");
// Clearing the database has failed, delete it instead.
if (mReactDatabaseSupplier.deleteDatabase()) {
FLog.d(ReactConstants.TAG, "Deleted Local Database AsyncLocalStorage.");
return;
}
// Everything failed, crash the app
throw new RuntimeException("Clearing and deleting database failed: " + args[0]);
}
});
}
@ -104,7 +104,7 @@ public final class AsyncStorageModule
String[] columns = {KEY_COLUMN, VALUE_COLUMN};
HashSet<String> keysRemaining = SetBuilder.newHashSet();
WritableArray data = Arguments.createArray();
Cursor cursor = Assertions.assertNotNull(mDb).query(
Cursor cursor = mReactDatabaseSupplier.get().query(
TABLE_CATALYST,
columns,
AsyncLocalStorageUtil.buildKeySelection(keys.size()),
@ -171,8 +171,8 @@ public final class AsyncStorageModule
}
String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);";
SQLiteStatement statement = Assertions.assertNotNull(mDb).compileStatement(sql);
mDb.beginTransaction();
SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql);
mReactDatabaseSupplier.get().beginTransaction();
try {
for (int idx=0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) {
@ -193,12 +193,12 @@ public final class AsyncStorageModule
statement.bindString(2, keyValueArray.getArray(idx).getString(1));
statement.execute();
}
mDb.setTransactionSuccessful();
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, "Exception in database multiSet ", e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
} finally {
mDb.endTransaction();
mReactDatabaseSupplier.get().endTransaction();
}
callback.invoke();
}
@ -224,7 +224,7 @@ public final class AsyncStorageModule
}
try {
Assertions.assertNotNull(mDb).delete(
mReactDatabaseSupplier.get().delete(
TABLE_CATALYST,
AsyncLocalStorageUtil.buildKeySelection(keys.size()),
AsyncLocalStorageUtil.buildKeySelectionArgs(keys));
@ -250,7 +250,7 @@ public final class AsyncStorageModule
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
Assertions.assertNotNull(mDb).beginTransaction();
mReactDatabaseSupplier.get().beginTransaction();
try {
for (int idx = 0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) {
@ -269,19 +269,19 @@ public final class AsyncStorageModule
}
if (!AsyncLocalStorageUtil.mergeImpl(
mDb,
mReactDatabaseSupplier.get(),
keyValueArray.getArray(idx).getString(0),
keyValueArray.getArray(idx).getString(1))) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
}
mDb.setTransactionSuccessful();
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
} finally {
mDb.endTransaction();
mReactDatabaseSupplier.get().endTransaction();
}
callback.invoke();
}
@ -296,12 +296,12 @@ public final class AsyncStorageModule
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
if (!mReactDatabaseSupplier.ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
try {
Assertions.assertNotNull(mDb).delete(TABLE_CATALYST, null, null);
mReactDatabaseSupplier.get().delete(TABLE_CATALYST, null, null);
} catch (Exception e) {
FLog.w(ReactConstants.TAG, "Exception in database clear ", e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
@ -325,7 +325,7 @@ public final class AsyncStorageModule
}
WritableArray data = Arguments.createArray();
String[] columns = {KEY_COLUMN};
Cursor cursor = Assertions.assertNotNull(mDb)
Cursor cursor = mReactDatabaseSupplier.get()
.query(TABLE_CATALYST, columns, null, null, null, null, null);
try {
if (cursor.moveToFirst()) {
@ -345,25 +345,9 @@ public final class AsyncStorageModule
}
/**
* Verify the database exists and is open.
* Verify the database is open for reads and writes.
*/
private boolean ensureDatabase() {
if (mShuttingDown) {
return false;
}
if (mDb != null && mDb.isOpen()) {
return true;
}
mDb = initializeDatabase();
return true;
}
/**
* Create and/or open the database.
*/
private SQLiteDatabase initializeDatabase() {
CatalystSQLiteOpenHelper helperForDb =
new CatalystSQLiteOpenHelper(getReactApplicationContext());
return helperForDb.getWritableDatabase();
return !mShuttingDown && mReactDatabaseSupplier.ensureDatabase();
}
}

View File

@ -1,53 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.modules.storage;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
// VisibleForTesting
public class CatalystSQLiteOpenHelper extends SQLiteOpenHelper {
// VisibleForTesting
public static final String DATABASE_NAME = "RKStorage";
static final int DATABASE_VERSION = 1;
static final String TABLE_CATALYST = "catalystLocalStorage";
static final String KEY_COLUMN = "key";
static final String VALUE_COLUMN = "value";
static final String VERSION_TABLE_CREATE =
"CREATE TABLE " + TABLE_CATALYST + " (" +
KEY_COLUMN + " TEXT PRIMARY KEY, " +
VALUE_COLUMN + " TEXT NOT NULL" +
")";
private Context mContext;
public CatalystSQLiteOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(VERSION_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO: t5494781 implement data migration
if (oldVersion != newVersion) {
mContext.deleteDatabase(DATABASE_NAME);
onCreate(db);
}
}
}

View File

@ -0,0 +1,106 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.modules.storage;
import javax.annotation.Nullable;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
// VisibleForTesting
public class ReactDatabaseSupplier extends SQLiteOpenHelper {
// VisibleForTesting
public static final String DATABASE_NAME = "RKStorage";
static final int DATABASE_VERSION = 1;
private static final int SLEEP_TIME_MS = 30;
static final String TABLE_CATALYST = "catalystLocalStorage";
static final String KEY_COLUMN = "key";
static final String VALUE_COLUMN = "value";
static final String VERSION_TABLE_CREATE =
"CREATE TABLE " + TABLE_CATALYST + " (" +
KEY_COLUMN + " TEXT PRIMARY KEY, " +
VALUE_COLUMN + " TEXT NOT NULL" +
")";
private Context mContext;
private @Nullable SQLiteDatabase mDb;
public ReactDatabaseSupplier(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(VERSION_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
deleteDatabase();
onCreate(db);
}
}
/**
* Verify the database exists and is open.
*/
/* package */ synchronized boolean ensureDatabase() {
if (mDb != null && mDb.isOpen()) {
return true;
}
// Sometimes retrieving the database fails. We do 2 retries: first without database deletion
// and then with deletion.
SQLiteException lastSQLiteException = null;
for (int tries = 0; tries < 2; tries++) {
try {
if (tries > 0) {
deleteDatabase();
}
mDb = getWritableDatabase();
break;
} catch (SQLiteException e) {
lastSQLiteException = e;
}
// Wait before retrying.
try {
Thread.sleep(SLEEP_TIME_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (mDb == null) {
throw lastSQLiteException;
}
return true;
}
/**
* Create and/or open the database.
*/
/* package */ synchronized SQLiteDatabase get() {
ensureDatabase();
return mDb;
}
/* package */ synchronized boolean deleteDatabase() {
if (mDb != null && mDb.isOpen()) {
mDb.close();
mDb = null;
}
return mContext.deleteDatabase(DATABASE_NAME);
}
}