diff --git a/README.md b/README.md index 0bb48770..553657e5 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,16 @@ npm install --save react-native-sqlite-storage ... include ':react-native-sqlite-storage' -project(':react-native-sqlite-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android') +project(':react-native-sqlite-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android/storage') + +include ':sqlite-plugin-provider' +project(':sqlite-plugin-provider').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android/sqlite-plugin-provider') + +// for sqlitecipher support: +include ':sql-cipher-plugin' +project(':sql-cipher-plugin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android/sql-cipher-plugin') + + ``` #### Step 3 - Update app Gradle Build @@ -242,6 +251,9 @@ project(':react-native-sqlite-storage').projectDir = new File(rootProject.projec dependencies { ... compile project(':react-native-sqlite-storage') + compile project(':sqlite-plugin-provider') + // To add sqlite cipher support: + compile project(':sql-cipher-plugin') } ``` @@ -265,7 +277,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand .setBundleAssetName("index.android.bundle") // this is dependant on how you name you JS files, example assumes index.android.js .setJSMainModuleName("index.android") // this is dependant on how you name you JS files, example assumes index.android.js .addPackage(new MainReactPackage()) - .addPackage(new SQLitePluginPackage()) // register SQLite Plugin here + .addPackage(new SQLitePluginPackage()) // register SQLite Plugin here. For SQLiteCipher support pass SqliteCipherConnectionProvider to PluginPackage. .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); diff --git a/src/android/build.gradle b/src/android/build.gradle index e42f502a..c4705857 100644 --- a/src/android/build.gradle +++ b/src/android/build.gradle @@ -1,34 +1,16 @@ buildscript { repositories { + mavenLocal() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.0' + classpath 'com.android.tools.build:gradle:2.0.0' } } -apply plugin: 'com.android.library' - -android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" - - defaultConfig { - minSdkVersion 16 - targetSdkVersion 22 - versionCode 1 - versionName "1.0" - } - lintOptions { - abortOnError false +allprojects { + repositories { + jcenter() } } - -repositories { - mavenCentral() -} - -dependencies { - compile 'com.facebook.react:react-native:0.14.+' -} diff --git a/src/android/settings.gradle b/src/android/settings.gradle index 74e11f66..66e94f43 100644 --- a/src/android/settings.gradle +++ b/src/android/settings.gradle @@ -1,2 +1,4 @@ rootProject.name = 'SQLitePlugin' -include ':app' \ No newline at end of file +include ':sqlite-plugin-provider' +include ':sql-cipher-plugin' +include ':storage' diff --git a/src/android/sql-cipher-plugin/.gitignore b/src/android/sql-cipher-plugin/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/src/android/sql-cipher-plugin/.gitignore @@ -0,0 +1 @@ +/build diff --git a/src/android/sql-cipher-plugin/build.gradle b/src/android/sql-cipher-plugin/build.gradle new file mode 100644 index 00000000..975329d6 --- /dev/null +++ b/src/android/sql-cipher-plugin/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.0" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile project(':sqlite-plugin-provider') + compile 'net.zetetic:android-database-sqlcipher:3.5.7@aar' +} diff --git a/src/android/sql-cipher-plugin/proguard-rules.pro b/src/android/sql-cipher-plugin/proguard-rules.pro new file mode 100644 index 00000000..10c22872 --- /dev/null +++ b/src/android/sql-cipher-plugin/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/sergeyd/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/src/android/sql-cipher-plugin/src/main/AndroidManifest.xml b/src/android/sql-cipher-plugin/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e9de8f58 --- /dev/null +++ b/src/android/sql-cipher-plugin/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherConnectionProvider.java b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherConnectionProvider.java new file mode 100644 index 00000000..4900727b --- /dev/null +++ b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherConnectionProvider.java @@ -0,0 +1,37 @@ +package org.pgsqlite.sqlcipher; + +import android.content.Context; + +import org.pgsqlite.sqlite.plugin.Database; +import org.pgsqlite.sqlite.plugin.DatabaseConnectionProvider; + +import net.sqlcipher.DatabaseErrorHandler; +import net.sqlcipher.SQLException; +import net.sqlcipher.database.SQLiteDatabase; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class SqliteCipherConnectionProvider implements DatabaseConnectionProvider { + private static volatile boolean sNativeLibraryLoaded; + + public SqliteCipherConnectionProvider(Context context) { + if (!sNativeLibraryLoaded) { + sNativeLibraryLoaded = true; + SQLiteDatabase.loadLibs(context); + } + } + + @Override + public Database openDatabase(String databasePath, String password, int openFlags) { + return new SqliteCipherDatabase(SQLiteDatabase.openDatabase(databasePath, password, null, + openFlags, null, new DBErrorHandler())); + } + + private class DBErrorHandler implements DatabaseErrorHandler { + @Override + public void onCorruption(SQLiteDatabase dbObj) { + throw new SQLException("Database is corrupted"); + } + } +} diff --git a/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherCursor.java b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherCursor.java new file mode 100644 index 00000000..698cac9c --- /dev/null +++ b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherCursor.java @@ -0,0 +1,71 @@ +package org.pgsqlite.sqlcipher; + +import org.pgsqlite.sqlite.plugin.Cursor; + +import java.io.IOException; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class SqliteCipherCursor implements Cursor { + private final net.sqlcipher.Cursor cursor; + + public SqliteCipherCursor(net.sqlcipher.Cursor cursor) { + this.cursor = cursor; + } + + @Override + public boolean moveToFirst() { + return cursor.moveToFirst(); + } + + @Override + public boolean moveToNext() { + return cursor.moveToNext(); + } + + @Override + public int getColumnCount() { + return cursor.getColumnCount(); + } + + @Override + public String getColumnName(int i) { + return cursor.getColumnName(i); + } + + @Override + public int getType(int i) { + return cursor.getType(i); + } + + @Override + public long getLong(int i) { + return cursor.getLong(i); + } + + @Override + public double getDouble(int i) { + return cursor.getDouble(i); + } + + @Override + public String getString(int i) { + return cursor.getString(i); + } + + @Override + public byte[] getBlob(int i) { + return cursor.getBlob(i); + } + + @Override + public int getCount() { + return cursor.getCount(); + } + + @Override + public void close() throws IOException { + cursor.close(); + } +} diff --git a/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherDatabase.java b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherDatabase.java new file mode 100644 index 00000000..3e083b6d --- /dev/null +++ b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherDatabase.java @@ -0,0 +1,60 @@ +package org.pgsqlite.sqlcipher; + +import net.sqlcipher.database.SQLiteDatabase; + +import org.pgsqlite.sqlite.plugin.Database; +import org.pgsqlite.sqlite.plugin.Cursor; +import org.pgsqlite.sqlite.plugin.SQLStatement; + +import java.io.IOException; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class SqliteCipherDatabase implements Database { + private SQLiteDatabase database; + + SqliteCipherDatabase(SQLiteDatabase database) { + this.database = database; + } + + @Override + public void execSQL(String sql) { + database.execSQL(sql); + } + + @Override + public SQLStatement compileStatement(String sql) { + return new SqliteCipherStatement(database.compileStatement(sql)); + } + + @Override + public Cursor rawQuery(String sql, String[] params) { + return new SqliteCipherCursor(database.rawQuery(sql, params)); + } + + @Override + public boolean isOpen() { + return database.isOpen(); + } + + @Override + public void beginTransaction() { + database.beginTransaction(); + } + + @Override + public void setTransactionSuccessful() { + database.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + database.endTransaction(); + } + + @Override + public void close() throws IOException { + database.close(); + } +} diff --git a/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherStatement.java b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherStatement.java new file mode 100644 index 00000000..34a87870 --- /dev/null +++ b/src/android/sql-cipher-plugin/src/main/java/org/pgsqlite/sqlcipher/SqliteCipherStatement.java @@ -0,0 +1,50 @@ +package org.pgsqlite.sqlcipher; + +import org.pgsqlite.sqlite.plugin.SQLStatement; + +import java.io.IOException; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class SqliteCipherStatement implements SQLStatement { + private final net.sqlcipher.database.SQLiteStatement statement; + public SqliteCipherStatement(net.sqlcipher.database.SQLiteStatement statement) { + this.statement = statement; + } + + @Override + public void bindDouble(int i, double value) { + statement.bindDouble(i, value); + } + + @Override + public void bindString(int i, String value) { + statement.bindString(i, value); + } + + @Override + public void bindNull(int i) { + statement.bindNull(i); + } + + @Override + public void bindLong(int i, long value) { + statement.bindLong(i, value); + } + + @Override + public long executeInsert() { + return statement.executeInsert(); + } + + @Override + public int executeUpdateDelete() { + return statement.executeUpdateDelete(); + } + + @Override + public void close() throws IOException { + statement.close(); + } +} diff --git a/src/android/sqlite-plugin-provider/.gitignore b/src/android/sqlite-plugin-provider/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/src/android/sqlite-plugin-provider/.gitignore @@ -0,0 +1 @@ +/build diff --git a/src/android/sqlite-plugin-provider/build.gradle b/src/android/sqlite-plugin-provider/build.gradle new file mode 100644 index 00000000..fb28c00a --- /dev/null +++ b/src/android/sqlite-plugin-provider/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.0" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} diff --git a/src/android/sqlite-plugin-provider/proguard-rules.pro b/src/android/sqlite-plugin-provider/proguard-rules.pro new file mode 100644 index 00000000..10c22872 --- /dev/null +++ b/src/android/sqlite-plugin-provider/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/sergeyd/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/src/android/sqlite-plugin-provider/src/main/AndroidManifest.xml b/src/android/sqlite-plugin-provider/src/main/AndroidManifest.xml new file mode 100644 index 00000000..76853997 --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/Cursor.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/Cursor.java new file mode 100644 index 00000000..3fca06a2 --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/Cursor.java @@ -0,0 +1,48 @@ +package org.pgsqlite.sqlite.plugin; + +import java.io.Closeable; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public interface Cursor extends Closeable { + + /* + * Values returned by {@link #getType(int)}. + * These should be consistent with the corresponding types defined in CursorWindow.h + */ + /** Value returned by {@link #getType(int)} if the specified column is null */ + int FIELD_TYPE_NULL = 0; + + /** Value returned by {@link #getType(int)} if the specified column type is integer */ + int FIELD_TYPE_INTEGER = 1; + + /** Value returned by {@link #getType(int)} if the specified column type is float */ + int FIELD_TYPE_FLOAT = 2; + + /** Value returned by {@link #getType(int)} if the specified column type is string */ + int FIELD_TYPE_STRING = 3; + + /** Value returned by {@link #getType(int)} if the specified column type is blob */ + int FIELD_TYPE_BLOB = 4; + + boolean moveToFirst(); + + boolean moveToNext(); + + int getColumnCount(); + + String getColumnName(int i); + + int getType(int i); + + long getLong(int i); + + double getDouble(int i); + + String getString(int i); + + byte[] getBlob(int i); + + int getCount(); +} diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/Database.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/Database.java new file mode 100644 index 00000000..e3732b9e --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/Database.java @@ -0,0 +1,17 @@ +package org.pgsqlite.sqlite.plugin; + +import java.io.Closeable; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public interface Database extends Closeable { + void execSQL(String sql); + SQLStatement compileStatement(String sql); + Cursor rawQuery(String sql, String[] params); + boolean isOpen(); + + void beginTransaction(); + void setTransactionSuccessful(); + void endTransaction(); +} diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DatabaseConnectionProvider.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DatabaseConnectionProvider.java new file mode 100644 index 00000000..5310feba --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DatabaseConnectionProvider.java @@ -0,0 +1,40 @@ +package org.pgsqlite.sqlite.plugin; + +import android.content.Context; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public interface DatabaseConnectionProvider { + + int OPEN_READWRITE = 0x00000000; + int OPEN_READONLY = 0x00000001; + + /** + * Open flag: Flag for {@link #openDatabase} to open the database without support for + * localized collators. + * + * {@more} This causes the collator LOCALIZED not to be created. + * You must be consistent when using this flag to use the setting the database was + * created with. If this is set, {@link #setLocale} will do nothing. + */ + int NO_LOCALIZED_COLLATORS = 0x00000010; + + /** + * Open flag: Flag for {@link #openDatabase} to create the database file if it does not + * already exist. + */ + int CREATE_IF_NECESSARY = 0x10000000; + + /** + * Open flag: Flag for {@link #openDatabase} to open the database file with + * write-ahead logging enabled by default. + * Write-ahead logging cannot be used with read-only databases so the value of + * this flag is ignored if the database is opened read-only. + * + */ + int ENABLE_WRITE_AHEAD_LOGGING = 0x20000000; + + Database openDatabase(String databasePath, String password, int openFlags); +} + diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultConnectionProvider.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultConnectionProvider.java new file mode 100644 index 00000000..3d63ca94 --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultConnectionProvider.java @@ -0,0 +1,24 @@ +package org.pgsqlite.sqlite.plugin; + +import android.content.Context; +import android.database.DatabaseErrorHandler; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class DefaultConnectionProvider implements DatabaseConnectionProvider { + + @Override + public Database openDatabase(String databasePath, String password, int openFlags) { + return new DefaultDatabase(SQLiteDatabase.openDatabase(databasePath, null, openFlags, new DBErrorHandler())); + } + + private class DBErrorHandler implements DatabaseErrorHandler { + @Override + public void onCorruption(SQLiteDatabase dbObj) { + throw new SQLException("Database is corrupted"); + } + } +} diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultCursor.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultCursor.java new file mode 100644 index 00000000..f4f19baa --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultCursor.java @@ -0,0 +1,69 @@ +package org.pgsqlite.sqlite.plugin; + +import java.io.IOException; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class DefaultCursor implements Cursor { + private final android.database.Cursor mCursor; + + public DefaultCursor(android.database.Cursor cursor) { + mCursor = cursor; + } + + @Override + public boolean moveToFirst() { + return mCursor.moveToFirst(); + } + + @Override + public boolean moveToNext() { + return mCursor.moveToNext(); + } + + @Override + public int getColumnCount() { + return mCursor.getColumnCount(); + } + + @Override + public String getColumnName(int i) { + return mCursor.getColumnName(i); + } + + @Override + public int getType(int i) { + return mCursor.getType(i); + } + + @Override + public long getLong(int i) { + return mCursor.getLong(i); + } + + @Override + public double getDouble(int i) { + return mCursor.getDouble(i); + } + + @Override + public String getString(int i) { + return mCursor.getString(i); + } + + @Override + public byte[] getBlob(int i) { + return mCursor.getBlob(i); + } + + @Override + public int getCount() { + return mCursor.getCount(); + } + + @Override + public void close() throws IOException { + mCursor.close(); + } +} diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultDatabase.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultDatabase.java new file mode 100644 index 00000000..197aa5e3 --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultDatabase.java @@ -0,0 +1,56 @@ +package org.pgsqlite.sqlite.plugin; + +import android.database.sqlite.SQLiteDatabase; + +import java.io.IOException; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class DefaultDatabase implements Database { + private final SQLiteDatabase database; + + public DefaultDatabase(SQLiteDatabase database) { + this.database = database; + } + + @Override + public void execSQL(String sql) { + database.execSQL(sql); + } + + @Override + public SQLStatement compileStatement(String sql) { + return new DefaultStatement(database.compileStatement(sql)); + } + + @Override + public Cursor rawQuery(String sql, String[] params) { + return new DefaultCursor(database.rawQuery(sql, params)); + } + + @Override + public boolean isOpen() { + return database.isOpen(); + } + + @Override + public void beginTransaction() { + database.beginTransaction(); + } + + @Override + public void setTransactionSuccessful() { + database.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + database.endTransaction(); + } + + @Override + public void close() throws IOException { + database.close(); + } +} diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultStatement.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultStatement.java new file mode 100644 index 00000000..d9b748f3 --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/DefaultStatement.java @@ -0,0 +1,51 @@ +package org.pgsqlite.sqlite.plugin; + +import android.database.sqlite.SQLiteStatement; + +import java.io.IOException; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public class DefaultStatement implements SQLStatement { + private final SQLiteStatement mStatement; + + public DefaultStatement(SQLiteStatement statement) { + mStatement = statement; + } + + @Override + public void bindDouble(int i, double value) { + mStatement.bindDouble(i, value); + } + + @Override + public void bindString(int i, String value) { + mStatement.bindString(i, value); + } + + @Override + public void bindNull(int i) { + mStatement.bindNull(i); + } + + @Override + public void bindLong(int i, long value) { + mStatement.bindLong(i, value); + } + + @Override + public long executeInsert() { + return mStatement.executeInsert(); + } + + @Override + public int executeUpdateDelete() { + return mStatement.executeUpdateDelete(); + } + + @Override + public void close() throws IOException { + mStatement.close(); + } +} diff --git a/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/SQLStatement.java b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/SQLStatement.java new file mode 100644 index 00000000..fcc0994d --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/java/org/pgsqlite/sqlite/plugin/SQLStatement.java @@ -0,0 +1,20 @@ +package org.pgsqlite.sqlite.plugin; + +import java.io.Closeable; + +/** + * Written by Sergei Dryganets Jul 24/2017 + */ +public interface SQLStatement extends Closeable { + void bindDouble(int i, double value); + + void bindString(int i, String value); + + void bindNull(int i); + + void bindLong(int i, long value); + + long executeInsert(); + + int executeUpdateDelete(); +} diff --git a/src/android/sqlite-plugin-provider/src/main/res/values/strings.xml b/src/android/sqlite-plugin-provider/src/main/res/values/strings.xml new file mode 100644 index 00000000..82b4713d --- /dev/null +++ b/src/android/sqlite-plugin-provider/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + SqlitePluginProvider + diff --git a/src/android/storage/build.gradle b/src/android/storage/build.gradle new file mode 100644 index 00000000..52a24fba --- /dev/null +++ b/src/android/storage/build.gradle @@ -0,0 +1,26 @@ + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.0" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + lintOptions { + abortOnError false + } +} + +repositories { + mavenCentral() +} + +dependencies { + compile 'com.facebook.react:react-native:0.14.+' + compile project(':sqlite-plugin-provider') +} diff --git a/src/android/src/main/AndroidManifest.xml b/src/android/storage/src/main/AndroidManifest.xml similarity index 100% rename from src/android/src/main/AndroidManifest.xml rename to src/android/storage/src/main/AndroidManifest.xml diff --git a/src/android/src/main/java/org/pgsqlite/CallbackContext.java b/src/android/storage/src/main/java/org/pgsqlite/CallbackContext.java similarity index 100% rename from src/android/src/main/java/org/pgsqlite/CallbackContext.java rename to src/android/storage/src/main/java/org/pgsqlite/CallbackContext.java diff --git a/src/android/src/main/java/org/pgsqlite/SQLiteArray.java b/src/android/storage/src/main/java/org/pgsqlite/SQLiteArray.java similarity index 100% rename from src/android/src/main/java/org/pgsqlite/SQLiteArray.java rename to src/android/storage/src/main/java/org/pgsqlite/SQLiteArray.java diff --git a/src/android/src/main/java/org/pgsqlite/SQLiteObject.java b/src/android/storage/src/main/java/org/pgsqlite/SQLiteObject.java similarity index 100% rename from src/android/src/main/java/org/pgsqlite/SQLiteObject.java rename to src/android/storage/src/main/java/org/pgsqlite/SQLiteObject.java diff --git a/src/android/src/main/java/org/pgsqlite/SQLitePlugin.java b/src/android/storage/src/main/java/org/pgsqlite/SQLitePlugin.java similarity index 92% rename from src/android/src/main/java/org/pgsqlite/SQLitePlugin.java rename to src/android/storage/src/main/java/org/pgsqlite/SQLitePlugin.java index 9988dfc9..e364f74e 100755 --- a/src/android/src/main/java/org/pgsqlite/SQLitePlugin.java +++ b/src/android/storage/src/main/java/org/pgsqlite/SQLitePlugin.java @@ -8,10 +8,6 @@ package org.pgsqlite; import android.annotation.SuppressLint; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteStatement; import android.content.Context; import android.util.Base64; @@ -39,6 +35,10 @@ import org.json.JSONException; import org.json.JSONArray; import org.json.JSONObject; +import org.pgsqlite.sqlite.plugin.Database; +import org.pgsqlite.sqlite.plugin.DatabaseConnectionProvider; +import org.pgsqlite.sqlite.plugin.Cursor; +import org.pgsqlite.sqlite.plugin.SQLStatement; import java.io.FileOutputStream; import java.io.InputStream; @@ -62,20 +62,20 @@ public class SQLitePlugin extends ReactContextBaseJavaModule { */ static ConcurrentHashMap dbrmap = new ConcurrentHashMap(); - /** - * Linked activity - */ - protected Context context = null; + private Context context = null; /** * Thread pool for database operations */ - protected ExecutorService threadPool; + private ExecutorService threadPool; - public SQLitePlugin(ReactApplicationContext reactContext) { + private DatabaseConnectionProvider provider; + + public SQLitePlugin(ReactApplicationContext reactContext, DatabaseConnectionProvider provider) { super(reactContext); this.context = reactContext.getApplicationContext(); this.threadPool = Executors.newCachedThreadPool(); + this.provider = provider; } /** @@ -231,8 +231,13 @@ private boolean executeAndPossiblyThrow(Action action, JSONArray args, CallbackC case open: o = args.getJSONObject(0); dbname = o.getString("name"); + String password = null; + + if (o.has("key")) { + password = o.getString("key"); + } // open database and start reading its queue - this.startDatabase(dbname, o, cbc); + this.startDatabase(dbname, password, o, cbc); break; case close: @@ -334,10 +339,11 @@ public void closeAllOpenDatabases() { /** * * @param dbname - The name of the database file + * @param password - password used to encrypt database * @param options - options passed in from JS * @param cbc - JS callback context */ - private void startDatabase(String dbname, JSONObject options, CallbackContext cbc) { + private void startDatabase(String dbname, String password, JSONObject options, CallbackContext cbc) { // TODO: is it an issue that we can orphan an existing thread? What should we do here? // If we re-use the existing DBRunner it might be in the process of closing... DBRunner r = dbrmap.get(dbname); @@ -349,7 +355,7 @@ private void startDatabase(String dbname, JSONObject options, CallbackContext cb // than orphaning the old DBRunner. cbc.success("database started"); } else { - r = new DBRunner(dbname, options, cbc); + r = new DBRunner(dbname, password, options, cbc); dbrmap.put(dbname, r); this.getThreadPool().execute(r); } @@ -365,11 +371,11 @@ private void startDatabase(String dbname, JSONObject options, CallbackContext cb * @return instance of SQLite database * @throws Exception */ - private SQLiteDatabase openDatabase(String dbname, String assetFilePath, int openFlags, CallbackContext cbc) throws Exception { + private Database openDatabase(String dbname, String password, String assetFilePath, int openFlags, CallbackContext cbc) throws Exception { InputStream in = null; File dbfile = null; try { - SQLiteDatabase database = this.getDatabase(dbname); + Database database = this.getDatabase(dbname); if (database != null && database.isOpen()) { //this only happens when DBRunner is cycling the db for the locking work around. // otherwise, this should not happen - should be blocked at the execute("open") level @@ -392,7 +398,7 @@ private SQLiteDatabase openDatabase(String dbname, String assetFilePath, int ope File assetFile = new File(filesDir, assetFilePath); in = new FileInputStream(assetFile); FLog.v(TAG, "Located pre-populated DB asset in Files subdirectory: " + assetFile.getCanonicalPath()); - if (openFlags == SQLiteDatabase.OPEN_READONLY) { + if (openFlags == DatabaseConnectionProvider.OPEN_READONLY) { dbfile = assetFile; FLog.v(TAG, "Detected read-only mode request for external asset."); } @@ -400,7 +406,8 @@ private SQLiteDatabase openDatabase(String dbname, String assetFilePath, int ope } if (dbfile == null) { - openFlags = SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.CREATE_IF_NECESSARY; + openFlags = DatabaseConnectionProvider.OPEN_READWRITE | + DatabaseConnectionProvider.CREATE_IF_NECESSARY; dbfile = this.getContext().getDatabasePath(dbname); if (!dbfile.exists() && in != null) { @@ -415,13 +422,13 @@ private SQLiteDatabase openDatabase(String dbname, String assetFilePath, int ope FLog.v(TAG, "Opening sqlite db: " + dbfile.getAbsolutePath()); - SQLiteDatabase mydb = SQLiteDatabase.openDatabase(dbfile.getAbsolutePath(), null, openFlags); + Database mydb = provider.openDatabase(dbfile.getAbsolutePath(), password, openFlags); if (cbc != null) // needed for Android locking/closing workaround cbc.success("database open"); return mydb; - } catch (SQLiteException ex) { + } catch (Exception ex) { if (cbc != null) // needed for Android locking/closing workaround cbc.error("can't open database " + ex); throw ex; @@ -498,11 +505,8 @@ private void closeDatabase(String dbName, CallbackContext cbc) { * @param dbName The name of the database file */ private void closeDatabaseNow(String dbName) { - SQLiteDatabase mydb = this.getDatabase(dbName); - - if (mydb != null) { - mydb.close(); - } + Database mydb = this.getDatabase(dbName); + closeQuietly(mydb); } /** @@ -576,7 +580,7 @@ private boolean deleteDatabaseNow(String dbname) { * * @param dbname The name of the database. */ - private SQLiteDatabase getDatabase(String dbname) { + private Database getDatabase(String dbname) { DBRunner r = dbrmap.get(dbname); return (r == null) ? null : r.mydb; } @@ -594,7 +598,7 @@ private SQLiteDatabase getDatabase(String dbname) { private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonparams, String[] queryIDs, CallbackContext cbc) { - SQLiteDatabase mydb = getDatabase(dbname); + Database mydb = getDatabase(dbname); if (mydb == null) { // not allowed - can only happen if someone has closed (and possibly deleted) a database and then re-used the database @@ -621,7 +625,7 @@ private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonp QueryType queryType = getQueryType(query); if (queryType == QueryType.update || queryType == QueryType.delete) { - SQLiteStatement myStatement = null; + SQLStatement myStatement = null; int rowsAffected = -1; // (assuming invalid) try { @@ -633,7 +637,7 @@ private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonp rowsAffected = myStatement.executeUpdateDelete(); // Indicate valid results: needRawQuery = false; - } catch (SQLiteException ex) { + } catch (Exception ex) { // Indicate problem & stop this query: errorMessage = ex.getMessage(); FLog.e(TAG, "SQLiteStatement.executeUpdateDelete() failed", ex); @@ -652,7 +656,7 @@ private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonp if (queryType == QueryType.insert && jsonparams != null) { needRawQuery = false; - SQLiteStatement myStatement = mydb.compileStatement(query); + SQLStatement myStatement = mydb.compileStatement(query); bindArgsToStatement(myStatement, jsonparams[i]); @@ -669,7 +673,7 @@ private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonp } else { queryResult.put("rowsAffected", 0); } - } catch (SQLiteException ex) { + } catch (Exception ex) { // report error result with the error message // could be constraint violation or some other error errorMessage = ex.getMessage(); @@ -686,7 +690,7 @@ private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonp queryResult = new JSONObject(); queryResult.put("rowsAffected", 0); - } catch (SQLiteException ex) { + } catch (Exception ex) { errorMessage = ex.getMessage(); FLog.e(TAG, "SQLiteDatabase.beginTransaction() failed", ex); } @@ -700,7 +704,7 @@ private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonp queryResult = new JSONObject(); queryResult.put("rowsAffected", 0); - } catch (SQLiteException ex) { + } catch (Exception ex) { errorMessage = ex.getMessage(); FLog.e(TAG, "SQLiteDatabase.setTransactionSuccessful/endTransaction() failed", ex); } @@ -713,7 +717,7 @@ private void executeSqlBatch(String dbname, String[] queryarr, JSONArray[] jsonp queryResult = new JSONObject(); queryResult.put("rowsAffected", 0); - } catch (SQLiteException ex) { + } catch (Exception ex) { errorMessage = ex.getMessage(); FLog.e(TAG, "SQLiteDatabase.endTransaction() failed", ex); } @@ -768,7 +772,7 @@ private QueryType getQueryType(String query) { return QueryType.other; } - private void bindArgsToStatement(SQLiteStatement myStatement, JSONArray sqlArgs) throws JSONException { + private void bindArgsToStatement(SQLStatement myStatement, JSONArray sqlArgs) throws JSONException { for (int i = 0; i < sqlArgs.length(); i++) { if (sqlArgs.get(i) instanceof Float || sqlArgs.get(i) instanceof Double) { myStatement.bindDouble(i + 1, sqlArgs.getDouble(i)); @@ -792,7 +796,7 @@ private void bindArgsToStatement(SQLiteStatement myStatement, JSONArray sqlArgs) * * @return results in string form */ - private JSONObject executeSqlStatementQuery(SQLiteDatabase mydb, + private JSONObject executeSqlStatementQuery(Database mydb, String query, JSONArray paramsAsJson, CallbackContext cbc) throws Exception { JSONObject rowsResult = new JSONObject(); @@ -889,22 +893,25 @@ private void closeQuietly(Closeable closeable) { private class DBRunner implements Runnable { final String dbname; + final String password; final int openFlags; private String assetFilename; private boolean androidLockWorkaround; final BlockingQueue q; final CallbackContext openCbc; - SQLiteDatabase mydb; + Database mydb; - DBRunner(final String dbname, JSONObject options, CallbackContext cbc) { + DBRunner(final String dbname, final String password, JSONObject options, CallbackContext cbc) { this.dbname = dbname; - int openFlags = SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.CREATE_IF_NECESSARY; + this.password = password; + int openFlags = DatabaseConnectionProvider.OPEN_READWRITE | + DatabaseConnectionProvider.CREATE_IF_NECESSARY; try { this.assetFilename = options.has("assetFilename") ? options.getString("assetFilename") : null; if (this.assetFilename != null && this.assetFilename.length() > 0) { boolean readOnly = options.has("readOnly") && options.getBoolean("readOnly"); - openFlags = readOnly ? SQLiteDatabase.OPEN_READONLY : openFlags; + openFlags = readOnly ? DatabaseConnectionProvider.OPEN_READONLY : openFlags; } } catch (Exception ex){ FLog.e(TAG,"Error retrieving assetFilename this.mode from options:",ex); @@ -920,7 +927,7 @@ private class DBRunner implements Runnable { public void run() { try { - this.mydb = openDatabase(dbname, this.assetFilename, this.openFlags, this.openCbc); + this.mydb = openDatabase(dbname, password, this.assetFilename, this.openFlags, this.openCbc); } catch (Exception e) { FLog.e(TAG, "unexpected error, stopping db thread", e); dbrmap.remove(dbname); @@ -939,7 +946,7 @@ public void run() { if (androidLockWorkaround && dbq.queries.length == 1 && dbq.queries[0].equals("COMMIT")) { // FLog.v(TAG, "close and reopen db"); closeDatabaseNow(dbname); - this.mydb = openDatabase(dbname, "", this.openFlags, null); + this.mydb = openDatabase(dbname, password, "", this.openFlags, null); // FLog.v(TAG, "close and reopen db finished"); } diff --git a/src/android/src/main/java/org/pgsqlite/SQLitePluginConverter.java b/src/android/storage/src/main/java/org/pgsqlite/SQLitePluginConverter.java similarity index 100% rename from src/android/src/main/java/org/pgsqlite/SQLitePluginConverter.java rename to src/android/storage/src/main/java/org/pgsqlite/SQLitePluginConverter.java diff --git a/src/android/src/main/java/org/pgsqlite/SQLitePluginPackage.java b/src/android/storage/src/main/java/org/pgsqlite/SQLitePluginPackage.java similarity index 73% rename from src/android/src/main/java/org/pgsqlite/SQLitePluginPackage.java rename to src/android/storage/src/main/java/org/pgsqlite/SQLitePluginPackage.java index be250278..9dde94c5 100644 --- a/src/android/src/main/java/org/pgsqlite/SQLitePluginPackage.java +++ b/src/android/storage/src/main/java/org/pgsqlite/SQLitePluginPackage.java @@ -5,14 +5,15 @@ */ package org.pgsqlite; -import android.app.Activity; - import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; +import org.pgsqlite.sqlite.plugin.DatabaseConnectionProvider; +import org.pgsqlite.sqlite.plugin.DefaultConnectionProvider; + import java.util.ArrayList; import java.util.Collections; @@ -20,15 +21,14 @@ public class SQLitePluginPackage implements ReactPackage { - /** - * @deprecated, use method without activity - * activity parameter is ignored - */ - public SQLitePluginPackage(Activity activity){ - this(); - } + private final DatabaseConnectionProvider provider; public SQLitePluginPackage() { + this(new DefaultConnectionProvider()); + } + + public SQLitePluginPackage(DatabaseConnectionProvider provider) { + this.provider = provider; } @Override @@ -36,7 +36,7 @@ public List createNativeModules( ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - modules.add(new SQLitePlugin(reactContext)); + modules.add(new SQLitePlugin(reactContext, provider)); return modules; }