Copying embedded Realm files using AssetManager from JNI

This commit is contained in:
Nabil Hachicha 2016-05-11 19:56:59 +01:00
parent 15953e13b9
commit 895c6a3056
8 changed files with 81 additions and 85 deletions

View File

@ -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();

View File

@ -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<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.<NativeModule>singletonList(new RealmReactModule(reactContext));
@ -35,65 +24,4 @@ public class RealmReactPackage implements ReactPackage {
public List<ViewManager> 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();
}
}
}
}
}

View File

@ -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

View File

@ -18,6 +18,7 @@
#include <jni.h>
#include <android/log.h>
#include <android/asset_manager_jni.h>
#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);

View File

@ -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

View File

@ -18,9 +18,23 @@
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <cstdio>
#include <android/log.h>
#include <android/asset_manager.h>
#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)

View File

@ -19,6 +19,7 @@
#pragma once
#include <string>
#include <android/asset_manager.h>
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();

View File

@ -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);
}
});