From 895c6a3056d5206b0f094267628f435c930c0fa8 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 11 May 2016 19:56:59 +0100 Subject: [PATCH] Copying embedded Realm files using AssetManager from JNI --- .../java/io/realm/react/RealmReactModule.java | 15 ++-- .../io/realm/react/RealmReactPackage.java | 72 ------------------- .../android/src/main/jni/Application.mk | 1 + .../io_realm_react_RealmReactModule.cpp | 10 ++- src/android/io_realm_react_RealmReactModule.h | 2 +- src/android/platform.cpp | 55 +++++++++++++- src/platform.hpp | 4 ++ tests/js/object-tests.js | 7 +- 8 files changed, 81 insertions(+), 85 deletions(-) diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java index 30effc37..e4f92864 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java @@ -1,5 +1,6 @@ package io.realm.react; +import android.content.res.AssetManager; import android.util.Log; import com.facebook.react.bridge.ReactApplicationContext; @@ -10,8 +11,8 @@ import java.io.IOException; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -25,6 +26,11 @@ public class RealmReactModule extends ReactContextBaseJavaModule { private static boolean sentAnalytics = false; private AndroidWebServer webServer; + // used to create a native AssetManager in C++ in order to load file from APK + // Note: We keep a VM reference to the assetManager to prevent its being + // garbage collected while the native object is in use. + //http://developer.android.com/ndk/reference/group___asset.html#gadfd6537af41577735bcaee52120127f4 + private final AssetManager assetManager; static { SoLoader.loadLibrary("realmreact"); @@ -33,8 +39,7 @@ public class RealmReactModule extends ReactContextBaseJavaModule { public RealmReactModule(ReactApplicationContext reactContext) { super(reactContext); - // copy any embedded Realm files from assets to the internal storage - RealmReactPackage.copyRealmsFromAsset(reactContext); + assetManager = reactContext.getResources().getAssets(); String fileDir; try { @@ -43,7 +48,7 @@ public class RealmReactModule extends ReactContextBaseJavaModule { throw new IllegalStateException(e); } - setDefaultRealmFileDirectory(fileDir); + setDefaultRealmFileDirectory(fileDir, assetManager); // Attempt to send analytics info only once, and only if allowed to do so. if (!sentAnalytics && RealmAnalytics.shouldExecute()) { @@ -170,7 +175,7 @@ public class RealmReactModule extends ReactContextBaseJavaModule { private native void clearContextInjectedFlag(); // fileDir: path of the internal storage of the application - private native void setDefaultRealmFileDirectory(String fileDir); + private native void setDefaultRealmFileDirectory(String fileDir, AssetManager assets); // responsible for creating the rpcServer that will accept the chrome Websocket command private native long setupChromeDebugModeRealmJsContext(); diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java b/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java index c940044e..1fd021df 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java @@ -1,26 +1,15 @@ package io.realm.react; -import android.content.Context; -import android.content.res.AssetManager; - 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 java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; import java.util.Collections; import java.util.List; public class RealmReactPackage implements ReactPackage { - private final static String REALM_FILE_FILTER = ".realm"; - @Override public List createNativeModules(ReactApplicationContext reactContext) { return Collections.singletonList(new RealmReactModule(reactContext)); @@ -35,65 +24,4 @@ public class RealmReactPackage implements ReactPackage { public List createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } - - public static void copyRealmsFromAsset(final Context context) { - try { - final AssetManager assets = context.getAssets(); - String[] list = assets.list(""); - File file = null; - for (String asset : list) { - //FIXME instead check if the file exist with the .management extension - // ex: dates-v3.realm.management - if (asset.endsWith(REALM_FILE_FILTER) && !(file = new File(context.getFilesDir(), asset)).exists()) { - copyFromAsset(assets, asset, file); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - private static void copyFromAsset(AssetManager assets, String realmFile, File destination) { - - ReadableByteChannel inChannel = null; - FileChannel outChannel = null; - boolean newFileCreated = false; - try { - newFileCreated = destination.createNewFile(); - inChannel = Channels.newChannel(assets.open(realmFile)); - FileOutputStream fileOutputStream = new FileOutputStream(destination); - outChannel = fileOutputStream.getChannel(); - - long offset = 0; - long quantum = 1024 * 1024; - long count; - while ((count = outChannel.transferFrom(inChannel, offset, quantum)) > 0) { - offset += count; - } - - } catch (IOException e) { - e.printStackTrace(); - // try to remove the empty file created - if (newFileCreated) { - destination.delete(); - } - - } finally { - if (inChannel != null) { - try { - inChannel.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - if (outChannel != null) { - try { - outChannel.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - } \ No newline at end of file diff --git a/react-native/android/src/main/jni/Application.mk b/react-native/android/src/main/jni/Application.mk index 0d0df637..aea47831 100644 --- a/react-native/android/src/main/jni/Application.mk +++ b/react-native/android/src/main/jni/Application.mk @@ -16,5 +16,6 @@ APP_CPPFLAGS += -DREALM_HAVE_CONFIG # Make sure every shared lib includes a .note.gnu.build-id header APP_LDFLAGS := -Wl,--build-id APP_LDFLAGS += -llog +APP_LDFLAGS += -landroid NDK_TOOLCHAIN_VERSION := 4.9 diff --git a/src/android/io_realm_react_RealmReactModule.cpp b/src/android/io_realm_react_RealmReactModule.cpp index f94761f7..b6bb714c 100644 --- a/src/android/io_realm_react_RealmReactModule.cpp +++ b/src/android/io_realm_react_RealmReactModule.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "io_realm_react_RealmReactModule.h" #include "rpc.hpp" @@ -29,10 +30,17 @@ static RPCServer *s_rpc_server; extern bool realmContextInjected; JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_setDefaultRealmFileDirectory - (JNIEnv *env, jclass, jstring fileDir) + (JNIEnv *env, jclass, jstring fileDir, jobject javaAssetManager) { __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "setDefaultRealmFileDirectory"); + // Get the assetManager in case we want to copy files from the APK (assets) + AAssetManager *assetManager = AAssetManager_fromJava(env, javaAssetManager); + if (assetManager == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "JSRealm", "Error loading the AssetManager"); + } + realm::set_asset_manager(assetManager); + // Setting the internal storage path for the application const char* strFileDir = env->GetStringUTFChars(fileDir, NULL); realm::set_default_realm_file_directory(strFileDir); diff --git a/src/android/io_realm_react_RealmReactModule.h b/src/android/io_realm_react_RealmReactModule.h index fb6404ee..f285d404 100644 --- a/src/android/io_realm_react_RealmReactModule.h +++ b/src/android/io_realm_react_RealmReactModule.h @@ -13,7 +13,7 @@ extern "C" { * Method: setDefaultRealmFileDirectory */ JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_setDefaultRealmFileDirectory - (JNIEnv *, jclass, jstring); + (JNIEnv *, jclass, jstring, jobject); /* * Class: io_realm_react_RealmReactModule diff --git a/src/android/platform.cpp b/src/android/platform.cpp index 1ce4a029..98f8c941 100644 --- a/src/android/platform.cpp +++ b/src/android/platform.cpp @@ -18,9 +18,23 @@ #include #include - +#include +#include +#include +#include #include "../platform.hpp" +#define REALM_FILE_FILTER ".realm" +#define REALM_FILE_FILTER_LEN strlen(REALM_FILE_FILTER) +AAssetManager* androidAssetManager; +inline bool isRealmFile(const char *str) +{ + size_t lenstr = strlen(str); + if (REALM_FILE_FILTER_LEN > lenstr) + return false; + return strncmp(str + lenstr - REALM_FILE_FILTER_LEN, REALM_FILE_FILTER, REALM_FILE_FILTER_LEN) == 0; +} + std::string s_default_realm_directory; namespace realm { @@ -30,13 +44,50 @@ namespace realm { s_default_realm_directory = dir; } + void set_asset_manager(AAssetManager* assetManager) + { + androidAssetManager = assetManager; + } + std::string default_realm_file_directory() { return s_default_realm_directory; } - void ensure_directory_exists_for_file(const std::string &fileName) + void ensure_directory_exists_for_file(const std::string &file) { + + } + + void copy_bundled_realm_files() + { + AAssetDir* assetDir = AAssetManager_openDir(androidAssetManager, ""); + + const char* filename = (const char*)NULL; + while ((filename = AAssetDir_getNextFileName(assetDir)) != NULL) + { + if (isRealmFile(filename)) + { + AAsset* asset = AAssetManager_open(androidAssetManager, filename, AASSET_MODE_STREAMING); + + char buf[BUFSIZ]; + int nb_read = 0; + + const char* destFilename = (s_default_realm_directory + '/' + filename).c_str(); + if(access(destFilename, F_OK ) == -1 ) { + // file doesn't exist, copy + FILE* out = fopen(destFilename, "w"); + while ((nb_read = AAsset_read(asset, buf, BUFSIZ)) > 0) + { + fwrite(buf, nb_read, 1, out); + } + fclose(out); + } + + AAsset_close(asset); + } + } + AAssetDir_close(assetDir); } void remove_realm_files_from_directory(const std::string &directory) diff --git a/src/platform.hpp b/src/platform.hpp index 7f497a79..568be954 100644 --- a/src/platform.hpp +++ b/src/platform.hpp @@ -19,6 +19,7 @@ #pragma once #include +#include extern std::string s_default_realm_directory; @@ -31,6 +32,9 @@ namespace realm { // set the directory where realm files should be stored void set_default_realm_file_directory(std::string dir); +// set the AssetManager used to access bundled files within the APK +void set_asset_manager(AAssetManager* assetManager); + // return the directory in which realm files can/should be written to std::string default_realm_file_directory(); diff --git a/tests/js/object-tests.js b/tests/js/object-tests.js index 85816c7d..46ad4b8b 100644 --- a/tests/js/object-tests.js +++ b/tests/js/object-tests.js @@ -474,15 +474,14 @@ module.exports = BaseTest.extend({ Realm.copyBundledRealmFiles(); var DateSchema = { - name: 'MyDate', + name: 'Date', properties: { currentDate: 'date' } }; - var realm = new Realm({path: 'dates-v4.realm', schema: [DateSchema]}); + var realm = new Realm({path: 'dates-v3.realm', schema: [DateSchema]}); - TestCase.assertEqual(realm.objects('MyDate').length, 1); - // TestCase.assertEqual(realm.objects('Date')[0].currentDate.getTime(), 1462500087955); + TestCase.assertEqual(realm.objects('Date')[0].currentDate.getTime(), 819867600000); } });