mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
refactor(AsyncStorage): move android files from react-native-github
Summary: ## Changelog: [Android] [Removed] - Removed AsyncStorage module Reviewed By: lunaleaps Differential Revision: D40175995 fbshipit-source-id: b583579b8c2fa6c502f265ffe464b81672bd7da5
This commit is contained in:
committed by
Facebook GitHub Bot
parent
83e2126b57
commit
5738fe6426
-125
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.storage;
|
||||
|
||||
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;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/** Helper for database operations. */
|
||||
public class AsyncLocalStorageUtil {
|
||||
|
||||
/**
|
||||
* Build the String required for an SQL select statement: WHERE key IN (?, ?, ..., ?) without
|
||||
* 'WHERE' and with selectionCount '?'
|
||||
*/
|
||||
/* package */ static String buildKeySelection(int selectionCount) {
|
||||
String[] list = new String[selectionCount];
|
||||
Arrays.fill(list, "?");
|
||||
return KEY_COLUMN + " IN (" + TextUtils.join(", ", list) + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the String[] arguments needed for an SQL selection, i.e.: {a, b, c} to be used in the SQL
|
||||
* select statement: WHERE key in (?, ?, ?)
|
||||
*/
|
||||
/* package */ static String[] buildKeySelectionArgs(ReadableArray keys, int start, int count) {
|
||||
String[] selectionArgs = new String[count];
|
||||
for (int keyIndex = 0; keyIndex < count; keyIndex++) {
|
||||
selectionArgs[keyIndex] = keys.getString(start + keyIndex);
|
||||
}
|
||||
return selectionArgs;
|
||||
}
|
||||
|
||||
/** Returns the value of the given key, or null if not found. */
|
||||
public static @Nullable String getItemImpl(SQLiteDatabase db, String key) {
|
||||
String[] columns = {VALUE_COLUMN};
|
||||
String[] selectionArgs = {key};
|
||||
|
||||
Cursor cursor =
|
||||
db.query(TABLE_CATALYST, columns, KEY_COLUMN + "=?", selectionArgs, null, null, null);
|
||||
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return null;
|
||||
} else {
|
||||
return cursor.getString(0);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the value for the key given, returns true if successful, false otherwise. */
|
||||
/* package */ static boolean setItemImpl(SQLiteDatabase db, String key, String value) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(KEY_COLUMN, key);
|
||||
contentValues.put(VALUE_COLUMN, value);
|
||||
|
||||
long inserted =
|
||||
db.insertWithOnConflict(
|
||||
TABLE_CATALYST, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
|
||||
return (-1 != inserted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual merge of the (key, value) pair with the value stored in the database. NB: This
|
||||
* assumes that a database lock is already in effect!
|
||||
*
|
||||
* @return the errorCode of the operation
|
||||
*/
|
||||
/* package */ static boolean mergeImpl(SQLiteDatabase db, String key, String value)
|
||||
throws JSONException {
|
||||
String oldValue = getItemImpl(db, key);
|
||||
String newValue;
|
||||
|
||||
if (oldValue == null) {
|
||||
newValue = value;
|
||||
} else {
|
||||
JSONObject oldJSON = new JSONObject(oldValue);
|
||||
JSONObject newJSON = new JSONObject(value);
|
||||
deepMergeInto(oldJSON, newJSON);
|
||||
newValue = oldJSON.toString();
|
||||
}
|
||||
|
||||
return setItemImpl(db, key, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two {@link JSONObject}s. The newJSON object will be merged with the oldJSON object by
|
||||
* either overriding its values, or merging them (if the values of the same key in both objects
|
||||
* are of type {@link JSONObject}). oldJSON will contain the result of this merge.
|
||||
*/
|
||||
private static void deepMergeInto(JSONObject oldJSON, JSONObject newJSON) throws JSONException {
|
||||
Iterator<?> keys = newJSON.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = (String) keys.next();
|
||||
|
||||
JSONObject newJSONObject = newJSON.optJSONObject(key);
|
||||
JSONObject oldJSONObject = oldJSON.optJSONObject(key);
|
||||
if (newJSONObject != null && oldJSONObject != null) {
|
||||
deepMergeInto(oldJSONObject, newJSONObject);
|
||||
oldJSON.put(key, oldJSONObject);
|
||||
} else {
|
||||
oldJSON.put(key, newJSON.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.storage;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
/** Helper class for database errors. */
|
||||
public class AsyncStorageErrorUtil {
|
||||
|
||||
/** Create Error object to be passed back to the JS callback. */
|
||||
/* package */ static WritableMap getError(@Nullable String key, String errorMessage) {
|
||||
WritableMap errorMap = Arguments.createMap();
|
||||
errorMap.putString("message", errorMessage);
|
||||
if (key != null) {
|
||||
errorMap.putString("key", key);
|
||||
}
|
||||
return errorMap;
|
||||
}
|
||||
|
||||
/* package */ static WritableMap getInvalidKeyError(@Nullable String key) {
|
||||
return getError(key, "Invalid key");
|
||||
}
|
||||
|
||||
/* package */ static WritableMap getInvalidValueError(@Nullable String key) {
|
||||
return getError(key, "Invalid Value");
|
||||
}
|
||||
|
||||
/* package */ static WritableMap getDBError(@Nullable String key) {
|
||||
return getError(key, "Database Error");
|
||||
}
|
||||
}
|
||||
-433
@@ -1,433 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.storage;
|
||||
|
||||
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;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.os.AsyncTask;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.fbreact.specs.NativeAsyncSQLiteDBStorageSpec;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.GuardedAsyncTask;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.modules.common.ModuleDataCleaner;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@ReactModule(name = AsyncStorageModule.NAME)
|
||||
public final class AsyncStorageModule extends NativeAsyncSQLiteDBStorageSpec
|
||||
implements ModuleDataCleaner.Cleanable {
|
||||
|
||||
public static final String NAME = "AsyncSQLiteDBStorage";
|
||||
|
||||
// SQL variable number limit, defined by SQLITE_LIMIT_VARIABLE_NUMBER:
|
||||
// https://raw.githubusercontent.com/android/platform_external_sqlite/master/dist/sqlite3.c
|
||||
private static final int MAX_SQL_KEYS = 999;
|
||||
|
||||
private ReactDatabaseSupplier mReactDatabaseSupplier;
|
||||
private boolean mShuttingDown = false;
|
||||
|
||||
// Adapted from
|
||||
// https://android.googlesource.com/platform/frameworks/base.git/+/1488a3a19d4681a41fb45570c15e14d99db1cb66/core/java/android/os/AsyncTask.java#237
|
||||
private class SerialExecutor implements Executor {
|
||||
private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
|
||||
private Runnable mActive;
|
||||
private final Executor executor;
|
||||
|
||||
SerialExecutor(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public synchronized void execute(final Runnable r) {
|
||||
mTasks.offer(
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
scheduleNext();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (mActive == null) {
|
||||
scheduleNext();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void scheduleNext() {
|
||||
if ((mActive = mTasks.poll()) != null) {
|
||||
executor.execute(mActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final SerialExecutor executor;
|
||||
|
||||
public AsyncStorageModule(ReactApplicationContext reactContext) {
|
||||
this(reactContext, AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
AsyncStorageModule(ReactApplicationContext reactContext, Executor executor) {
|
||||
super(reactContext);
|
||||
this.executor = new SerialExecutor(executor);
|
||||
mReactDatabaseSupplier = ReactDatabaseSupplier.getInstance(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
mShuttingDown = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
mShuttingDown = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSensitiveData() {
|
||||
// Clear local storage. If fails, crash, since the app is potentially in a bad state and could
|
||||
// cause a privacy violation. We're still not recovering from this well, but at least the error
|
||||
// will be reported to the server.
|
||||
mReactDatabaseSupplier.clearAndCloseDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of keys, this returns a map of (key, value) pairs for the keys found, and (key,
|
||||
* null) for the keys that haven't been found.
|
||||
*/
|
||||
@Override
|
||||
public void multiGet(final ReadableArray keys, final Callback callback) {
|
||||
if (keys == null) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null), null);
|
||||
return;
|
||||
}
|
||||
|
||||
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
if (!ensureDatabase()) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null), null);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] columns = {KEY_COLUMN, VALUE_COLUMN};
|
||||
HashSet<String> keysRemaining = new HashSet<>();
|
||||
WritableArray data = Arguments.createArray();
|
||||
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
|
||||
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
|
||||
Cursor cursor =
|
||||
mReactDatabaseSupplier
|
||||
.get()
|
||||
.query(
|
||||
TABLE_CATALYST,
|
||||
columns,
|
||||
AsyncLocalStorageUtil.buildKeySelection(keyCount),
|
||||
AsyncLocalStorageUtil.buildKeySelectionArgs(keys, keyStart, keyCount),
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
keysRemaining.clear();
|
||||
try {
|
||||
if (cursor.getCount() != keys.size()) {
|
||||
// some keys have not been found - insert them with null into the final array
|
||||
for (int keyIndex = keyStart; keyIndex < keyStart + keyCount; keyIndex++) {
|
||||
keysRemaining.add(keys.getString(keyIndex));
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
WritableArray row = Arguments.createArray();
|
||||
row.pushString(cursor.getString(0));
|
||||
row.pushString(cursor.getString(1));
|
||||
data.pushArray(row);
|
||||
keysRemaining.remove(cursor.getString(0));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
|
||||
return;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
for (String key : keysRemaining) {
|
||||
WritableArray row = Arguments.createArray();
|
||||
row.pushString(key);
|
||||
row.pushNull();
|
||||
data.pushArray(row);
|
||||
}
|
||||
keysRemaining.clear();
|
||||
}
|
||||
|
||||
callback.invoke(null, data);
|
||||
}
|
||||
}.executeOnExecutor(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts multiple (key, value) pairs. If one or more of the pairs cannot be inserted, this will
|
||||
* return AsyncLocalStorageFailure, but all other pairs will have been inserted. The insertion
|
||||
* will replace conflicting (key, value) pairs.
|
||||
*/
|
||||
@Override
|
||||
public void multiSet(final ReadableArray keyValueArray, final Callback callback) {
|
||||
if (keyValueArray.size() == 0) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null));
|
||||
return;
|
||||
}
|
||||
|
||||
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
if (!ensureDatabase()) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
||||
return;
|
||||
}
|
||||
|
||||
String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);";
|
||||
SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql);
|
||||
WritableMap error = null;
|
||||
try {
|
||||
mReactDatabaseSupplier.get().beginTransaction();
|
||||
for (int idx = 0; idx < keyValueArray.size(); idx++) {
|
||||
if (keyValueArray.getArray(idx).size() != 2) {
|
||||
error = AsyncStorageErrorUtil.getInvalidValueError(null);
|
||||
return;
|
||||
}
|
||||
if (keyValueArray.getArray(idx).getString(0) == null) {
|
||||
error = AsyncStorageErrorUtil.getInvalidKeyError(null);
|
||||
return;
|
||||
}
|
||||
if (keyValueArray.getArray(idx).getString(1) == null) {
|
||||
error = AsyncStorageErrorUtil.getInvalidValueError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
statement.clearBindings();
|
||||
statement.bindString(1, keyValueArray.getArray(idx).getString(0));
|
||||
statement.bindString(2, keyValueArray.getArray(idx).getString(1));
|
||||
statement.execute();
|
||||
}
|
||||
mReactDatabaseSupplier.get().setTransactionSuccessful();
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
mReactDatabaseSupplier.get().endTransaction();
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
if (error == null) {
|
||||
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error != null) {
|
||||
callback.invoke(error);
|
||||
} else {
|
||||
callback.invoke();
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(executor);
|
||||
}
|
||||
|
||||
/** Removes all rows of the keys given. */
|
||||
@Override
|
||||
public void multiRemove(final ReadableArray keys, final Callback callback) {
|
||||
if (keys.size() == 0) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null));
|
||||
return;
|
||||
}
|
||||
|
||||
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
if (!ensureDatabase()) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
||||
return;
|
||||
}
|
||||
|
||||
WritableMap error = null;
|
||||
try {
|
||||
mReactDatabaseSupplier.get().beginTransaction();
|
||||
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
|
||||
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
|
||||
mReactDatabaseSupplier
|
||||
.get()
|
||||
.delete(
|
||||
TABLE_CATALYST,
|
||||
AsyncLocalStorageUtil.buildKeySelection(keyCount),
|
||||
AsyncLocalStorageUtil.buildKeySelectionArgs(keys, keyStart, keyCount));
|
||||
}
|
||||
mReactDatabaseSupplier.get().setTransactionSuccessful();
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
mReactDatabaseSupplier.get().endTransaction();
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
if (error == null) {
|
||||
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error != null) {
|
||||
callback.invoke(error);
|
||||
} else {
|
||||
callback.invoke();
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of (key, value) pairs, this will merge the given values with the stored values
|
||||
* of the given keys, if they exist.
|
||||
*/
|
||||
@Override
|
||||
public void multiMerge(final ReadableArray keyValueArray, final Callback callback) {
|
||||
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
if (!ensureDatabase()) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
||||
return;
|
||||
}
|
||||
WritableMap error = null;
|
||||
try {
|
||||
mReactDatabaseSupplier.get().beginTransaction();
|
||||
for (int idx = 0; idx < keyValueArray.size(); idx++) {
|
||||
if (keyValueArray.getArray(idx).size() != 2) {
|
||||
error = AsyncStorageErrorUtil.getInvalidValueError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyValueArray.getArray(idx).getString(0) == null) {
|
||||
error = AsyncStorageErrorUtil.getInvalidKeyError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyValueArray.getArray(idx).getString(1) == null) {
|
||||
error = AsyncStorageErrorUtil.getInvalidValueError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AsyncLocalStorageUtil.mergeImpl(
|
||||
mReactDatabaseSupplier.get(),
|
||||
keyValueArray.getArray(idx).getString(0),
|
||||
keyValueArray.getArray(idx).getString(1))) {
|
||||
error = AsyncStorageErrorUtil.getDBError(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mReactDatabaseSupplier.get().setTransactionSuccessful();
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
mReactDatabaseSupplier.get().endTransaction();
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
if (error == null) {
|
||||
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error != null) {
|
||||
callback.invoke(error);
|
||||
} else {
|
||||
callback.invoke();
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(executor);
|
||||
}
|
||||
|
||||
/** Clears the database. */
|
||||
@Override
|
||||
public void clear(final Callback callback) {
|
||||
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
if (!mReactDatabaseSupplier.ensureDatabase()) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mReactDatabaseSupplier.clear();
|
||||
callback.invoke();
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(executor);
|
||||
}
|
||||
|
||||
/** Returns an array with all keys from the database. */
|
||||
@Override
|
||||
public void getAllKeys(final Callback callback) {
|
||||
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
if (!ensureDatabase()) {
|
||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null), null);
|
||||
return;
|
||||
}
|
||||
WritableArray data = Arguments.createArray();
|
||||
String[] columns = {KEY_COLUMN};
|
||||
Cursor cursor =
|
||||
mReactDatabaseSupplier
|
||||
.get()
|
||||
.query(TABLE_CATALYST, columns, null, null, null, null, null);
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
data.pushString(cursor.getString(0));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
|
||||
return;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
callback.invoke(null, data);
|
||||
}
|
||||
}.executeOnExecutor(executor);
|
||||
}
|
||||
|
||||
/** Verify the database is open for reads and writes. */
|
||||
private boolean ensureDatabase() {
|
||||
return !mShuttingDown && mReactDatabaseSupplier.ensureDatabase();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_root_target", "react_native_target", "rn_android_library")
|
||||
|
||||
rn_android_library(
|
||||
name = "storage",
|
||||
srcs = glob(["**/*.java"]),
|
||||
autoglob = False,
|
||||
labels = [
|
||||
"pfh:ReactNative_CommonInfrastructurePlaceholder",
|
||||
],
|
||||
language = "JAVA",
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
deps = [
|
||||
react_native_dep("third-party/android/androidx:annotation"),
|
||||
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
|
||||
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
|
||||
react_native_dep("third-party/java/jsr-305:jsr-305"),
|
||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
react_native_target("java/com/facebook/react/common:common"),
|
||||
react_native_target("java/com/facebook/react/module/annotations:annotations"),
|
||||
react_native_target("java/com/facebook/react/modules/common:common"),
|
||||
],
|
||||
exported_deps = [react_native_root_target(":FBReactNativeSpec")],
|
||||
)
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
|
||||
/**
|
||||
* Database supplier of the database used by react native. This creates, opens and deletes the
|
||||
* database as necessary.
|
||||
*/
|
||||
public class ReactDatabaseSupplier extends SQLiteOpenHelper {
|
||||
|
||||
// VisibleForTesting
|
||||
public static final String DATABASE_NAME = "RKStorage";
|
||||
|
||||
private 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 static @Nullable ReactDatabaseSupplier sReactDatabaseSupplierInstance;
|
||||
|
||||
private Context mContext;
|
||||
private @Nullable SQLiteDatabase mDb;
|
||||
private long mMaximumDatabaseSize = 6L * 1024L * 1024L; // 6 MB in bytes
|
||||
|
||||
private ReactDatabaseSupplier(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static ReactDatabaseSupplier getInstance(Context context) {
|
||||
if (sReactDatabaseSupplierInstance == null) {
|
||||
sReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context.getApplicationContext());
|
||||
}
|
||||
return sReactDatabaseSupplierInstance;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
// This is a sane limit to protect the user from the app storing too much data in the database.
|
||||
// This also protects the database from filling up the disk cache and becoming malformed
|
||||
// (endTransaction() calls will throw an exception, not rollback, and leave the db malformed).
|
||||
mDb.setMaximumSize(mMaximumDatabaseSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Create and/or open the database. */
|
||||
public synchronized SQLiteDatabase get() {
|
||||
ensureDatabase();
|
||||
return mDb;
|
||||
}
|
||||
|
||||
public synchronized void clearAndCloseDatabase() throws RuntimeException {
|
||||
try {
|
||||
clear();
|
||||
closeDatabase();
|
||||
FLog.d(ReactConstants.TAG, "Cleaned " + DATABASE_NAME);
|
||||
} catch (Exception e) {
|
||||
// Clearing the database has failed, delete it instead.
|
||||
if (deleteDatabase()) {
|
||||
FLog.d(ReactConstants.TAG, "Deleted Local Database " + DATABASE_NAME);
|
||||
return;
|
||||
}
|
||||
// Everything failed, throw
|
||||
throw new RuntimeException("Clearing and deleting database " + DATABASE_NAME + " failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ synchronized void clear() {
|
||||
get().delete(TABLE_CATALYST, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum size the database will grow to. The maximum size cannot be set below the
|
||||
* current size.
|
||||
*/
|
||||
public synchronized void setMaximumSize(long size) {
|
||||
mMaximumDatabaseSize = size;
|
||||
if (mDb != null) {
|
||||
mDb.setMaximumSize(mMaximumDatabaseSize);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized boolean deleteDatabase() {
|
||||
closeDatabase();
|
||||
return mContext.deleteDatabase(DATABASE_NAME);
|
||||
}
|
||||
|
||||
private synchronized void closeDatabase() {
|
||||
if (mDb != null && mDb.isOpen()) {
|
||||
mDb.close();
|
||||
mDb = null;
|
||||
}
|
||||
}
|
||||
|
||||
// For testing purposes only!
|
||||
public static void deleteInstance() {
|
||||
sReactDatabaseSupplierInstance = null;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ rn_android_library(
|
||||
react_native_target("java/com/facebook/react/modules/permissions:permissions"),
|
||||
react_native_target("java/com/facebook/react/modules/share:share"),
|
||||
react_native_target("java/com/facebook/react/modules/statusbar:statusbar"),
|
||||
react_native_target("java/com/facebook/react/modules/storage:storage"),
|
||||
react_native_target("java/com/facebook/react/modules/sound:sound"),
|
||||
react_native_target("java/com/facebook/react/modules/toast:toast"),
|
||||
react_native_target("java/com/facebook/react/modules/vibration:vibration"),
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.facebook.react.modules.permissions.PermissionsModule;
|
||||
import com.facebook.react.modules.share.ShareModule;
|
||||
import com.facebook.react.modules.sound.SoundManagerModule;
|
||||
import com.facebook.react.modules.statusbar.StatusBarModule;
|
||||
import com.facebook.react.modules.storage.AsyncStorageModule;
|
||||
import com.facebook.react.modules.toast.ToastModule;
|
||||
import com.facebook.react.modules.vibration.VibrationModule;
|
||||
import com.facebook.react.modules.websocket.WebSocketModule;
|
||||
@@ -69,7 +68,6 @@ import java.util.Map;
|
||||
AppStateModule.class,
|
||||
BlobModule.class,
|
||||
FileReaderModule.class,
|
||||
AsyncStorageModule.class,
|
||||
ClipboardModule.class,
|
||||
DialogModule.class,
|
||||
FrescoModule.class,
|
||||
@@ -111,8 +109,6 @@ public class MainReactPackage extends TurboReactPackage {
|
||||
return new BlobModule(context);
|
||||
case FileReaderModule.NAME:
|
||||
return new FileReaderModule(context);
|
||||
case AsyncStorageModule.NAME:
|
||||
return new AsyncStorageModule(context);
|
||||
case ClipboardModule.NAME:
|
||||
return new ClipboardModule(context);
|
||||
case DialogModule.NAME:
|
||||
@@ -193,7 +189,6 @@ public class MainReactPackage extends TurboReactPackage {
|
||||
AppStateModule.class,
|
||||
BlobModule.class,
|
||||
FileReaderModule.class,
|
||||
AsyncStorageModule.class,
|
||||
ClipboardModule.class,
|
||||
DialogModule.class,
|
||||
FrescoModule.class,
|
||||
|
||||
Reference in New Issue
Block a user