Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • polycentric/leveldb-capacitor-plugin
1 result
Show changes
Commits on Source (4)
Showing
with 1705 additions and 209 deletions
[submodule "android/src/main/cpp/leveldb"]
path = android/src/main/cpp/leveldb
url = https://github.com/google/leveldb.git
......@@ -7,6 +7,17 @@
"string_view": "cpp",
"vector": "cpp",
"locale": "cpp",
"__node_handle": "cpp"
"__node_handle": "cpp",
"__bit_reference": "cpp",
"bitset": "cpp",
"deque": "cpp",
"__memory": "cpp",
"limits": "cpp",
"ratio": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"chrono": "cpp",
"filesystem": "cpp",
"random": "cpp"
}
}
\ No newline at end of file
/build
/.cxx
\ No newline at end of file
......@@ -26,6 +26,11 @@ android {
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ''
}
}
}
buildTypes {
release {
......@@ -40,6 +45,12 @@ android {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
}
repositories {
......
package com.getcapacitor.android;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.android", appContext.getPackageName());
}
}
package com.getcapacitor.android;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import java.io.File;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.futo.polycentric.leveldbcapacitor.MobileLevelImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class LevelDBWrapperTest {
private MobileLevelImpl dbWrapper;
@Before
public void setUp() {
dbWrapper = new MobileLevelImpl();
var context = ApplicationProvider.getApplicationContext();
File levelDBDir = new File(context.getFilesDir(), "leveldb");
if (!levelDBDir.exists()) {
levelDBDir.mkdirs();
}
// combine dir and db name
String dbPath = new File(levelDBDir, "testdb").getAbsolutePath();
dbWrapper.db_open("testdb", dbPath);
}
@After
public void tearDown() throws Exception {
dbWrapper.db_clear("testdb");
dbWrapper.db_close("testdb");
}
@Test
public void testPutAndGet() throws Exception {
String key = "foo";
String value = "bar";
String key_b64 = Base64.getEncoder().encodeToString(key.getBytes());
String value_b64 = Base64.getEncoder().encodeToString(value.getBytes());
dbWrapper.db_put("testdb", key_b64, value_b64);
String result = dbWrapper.db_get("testdb", key_b64);
assertEquals(value_b64, result);
}
@Test
public void testDelete() throws Exception {
dbWrapper.db_put("testdb", "key1", "value1");
dbWrapper.db_delete("testdb", "key1");
String value = dbWrapper.db_get("testdb", "key1");
assertNull(value);
}
@Test
public void testClear() throws Exception {
dbWrapper.db_put("testdb", "key1", "value1");
dbWrapper.db_put("testdb", "key2", "value2");
dbWrapper.db_clear("testdb");
assertNull(dbWrapper.db_get("testdb", "key1"));
assertNull(dbWrapper.db_get("testdb", "key2"));
}
@Test
public void testInputThatsBase64WithNullChars() throws Exception {
dbWrapper.db_put("testdb", "key1", "SGVsbG8AIFdvcmxk");
String value = dbWrapper.db_get("testdb", "key1");
assertEquals("SGVsbG8AIFdvcmxk", value);
}
@Test
public void testBatch() throws Exception {
// Testing batch operations with base64 encoded keys and values
String batchKey1 = "batchKey1";
String batchValue1 = "batchValue1";
String batchKey2 = "batchKey2";
String batchValue2 = "batchValue2";
String batchKey1_b64 = Base64.getEncoder().encodeToString(batchKey1.getBytes());
String batchValue1_b64 = Base64.getEncoder().encodeToString(batchValue1.getBytes());
String batchKey2_b64 = Base64.getEncoder().encodeToString(batchKey2.getBytes());
String batchValue2_b64 = Base64.getEncoder().encodeToString(batchValue2.getBytes());
// Perform batch put operations
HashMap<String, String>[] operations = new HashMap[2];
HashMap<String, String> operation1 = new HashMap<>();
operation1.put("type", "put");
operation1.put("key", batchKey1_b64);
operation1.put("value", batchValue1_b64);
operations[0] = operation1;
HashMap<String, String> operation2 = new HashMap<>();
operation2.put("type", "put");
operation2.put("key", batchKey2_b64);
operation2.put("value", batchValue2_b64);
operations[1] = operation2;
dbWrapper.db_batch("testdb", operations);
// Verify the batch put operations
String result1 = dbWrapper.db_get("testdb", batchKey1_b64);
assertEquals(batchValue1_b64, result1);
String result2 = dbWrapper.db_get("testdb", batchKey2_b64);
assertEquals(batchValue2_b64, result2);
}
}
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("leveldb_jni")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_subdirectory(leveldb)
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
LevelDB_JNI.cpp)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
leveldb
log)
//
// Created by Aidan Dunlap on 5/16/24.
//
#include "LevelDB_JNI.h"
#include "leveldb/write_batch.h"
#include "leveldb/db.h"
#include <string>
#include <vector>
#include <map>
std::map<std::string, leveldb::DB*> dbs;
void throwJavaException(JNIEnv* env, const char* message) {
jclass exceptionClass = env->FindClass("java/lang/Exception");
if (exceptionClass == nullptr) {
return;
}
env->ThrowNew(exceptionClass, message);
}
std::string base64_decode(const std::string &in) {
std::string out;
std::vector<int> T(256, -1);
for (int i = 0; i < 64; i++) {
T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
}
int val = 0, valb = -8;
for (unsigned char c : in) {
if (T[c] == -1) break;
val = (val << 6) + T[c];
valb += 6;
if (valb >= 0) {
out.push_back(char((val >> valb) & 0xFF));
valb -= 8;
}
}
return out;
}
std::string base64_encode(const std::string &in) {
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
int val = 0, valb = -6;
for (unsigned char c : in) {
val = (val << 8) + c;
valb += 8;
while (valb >= 0) {
out.push_back(base64_chars[(val >> valb) & 0x3F]);
valb -= 6;
}
}
if (valb > -6) out.push_back(base64_chars[((val << 8) >> (valb + 8)) & 0x3F]);
while (out.size() % 4) out.push_back('=');
return out;
}
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1init(JNIEnv *, jobject) {
return;
}
extern "C"
JNIEXPORT void JNICALL
Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1open(JNIEnv *env, jobject ,
jstring dbName, jstring path) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (db) {
// noop
env->ReleaseStringUTFChars(dbName, db_name_cstr);
return;
}
const char* path_cstr = env->GetStringUTFChars(path, nullptr);
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options, path_cstr, &db);
if (status.ok()) {
dbs[db_name_cstr] = db;
env->ReleaseStringUTFChars(dbName, db_name_cstr);
env->ReleaseStringUTFChars(path, path_cstr);
} else {
// Handle error
env->ReleaseStringUTFChars(dbName, db_name_cstr);
env->ReleaseStringUTFChars(path, path_cstr);
throwJavaException(env, status.ToString().c_str());
}
}
JNIEXPORT jstring JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1get(JNIEnv *env, jobject, jstring dbName, jstring key) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (!db) {
env->ReleaseStringUTFChars(dbName, db_name_cstr);
throwJavaException(env, "Database not found");
return nullptr;
}
const char* key_cstr = env->GetStringUTFChars(key, nullptr);
std::string decoded_key = base64_decode(key_cstr);
std::string value;
leveldb::Status status = db->Get(leveldb::ReadOptions(), decoded_key, &value);
env->ReleaseStringUTFChars(key, key_cstr);
env->ReleaseStringUTFChars(dbName, db_name_cstr);
if (status.ok() && !status.IsNotFound()) {
value = base64_encode(value);
jstring result = env->NewStringUTF(value.c_str());
return result;
} else {
return nullptr;
}
}
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1put(JNIEnv *env, jobject, jstring dbName, jstring key, jstring value) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (!db) {
env->ReleaseStringUTFChars(dbName, db_name_cstr);
throwJavaException(env, "Database not found");
return;
}
const char* key_cstr = env->GetStringUTFChars(key, nullptr);
std::string decoded_key = base64_decode(key_cstr);
const char* value_cstr = env->GetStringUTFChars(value, nullptr);
std::string decoded_value = base64_decode(value_cstr);
leveldb::Status status = db->Put(leveldb::WriteOptions(), decoded_key, decoded_value);
env->ReleaseStringUTFChars(key, key_cstr);
env->ReleaseStringUTFChars(value, value_cstr);
env->ReleaseStringUTFChars(dbName, db_name_cstr);
}
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1delete(JNIEnv *env, jobject, jstring dbName, jstring key) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (!db) {
// Handle no database found error
env->ReleaseStringUTFChars(dbName, db_name_cstr);
throwJavaException(env, "Database not found");
return;
}
const char* key_cstr = env->GetStringUTFChars(key, nullptr);
std::string decoded_key = base64_decode(key_cstr);
leveldb::Status status = db->Delete(leveldb::WriteOptions(), decoded_key);
env->ReleaseStringUTFChars(key, key_cstr);
env->ReleaseStringUTFChars(dbName, db_name_cstr);
}
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1close(JNIEnv *env, jobject, jstring dbName) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (!db) {
env->ReleaseStringUTFChars(dbName, db_name_cstr);
// No error here
return;
}
delete db;
dbs.erase(db_name_cstr);
env->ReleaseStringUTFChars(dbName, db_name_cstr);
}
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1clear(JNIEnv *env, jobject, jstring dbName) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (!db) {
env->ReleaseStringUTFChars(dbName, db_name_cstr);
throwJavaException(env, "Database not found");
return;
}
leveldb::Status status;
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
db->Delete(leveldb::WriteOptions(), it->key());
}
delete it;
env->ReleaseStringUTFChars(dbName, db_name_cstr);
}
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1batch(JNIEnv *env, jobject, jstring dbName, jobjectArray operations) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (!db) {
env->ReleaseStringUTFChars(dbName, db_name_cstr);
throwJavaException(env, "Database not found");
return;
}
int opCount = env->GetArrayLength(operations);
leveldb::WriteBatch batch;
leveldb::Status status;
for (int i = 0; i < opCount; i++) {
jobject operationMap = env->GetObjectArrayElement(operations, i);
jclass mapClass = env->FindClass("java/util/Map");
jmethodID getMethod = env->GetMethodID(mapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
jstring typeKey = env->NewStringUTF("type");
jstring type = (jstring)env->CallObjectMethod(operationMap, getMethod, typeKey);
const char* type_cstr = env->GetStringUTFChars(type, nullptr);
jstring keyKey = env->NewStringUTF("key");
jstring key = (jstring)env->CallObjectMethod(operationMap, getMethod, keyKey);
const char* key_cstr = env->GetStringUTFChars(key, nullptr);
std::string decoded_key = base64_decode(key_cstr);
if (strcmp(type_cstr, "put") == 0) {
jstring valueKey = env->NewStringUTF("value");
jstring value = (jstring)env->CallObjectMethod(operationMap, getMethod, valueKey);
const char* value_cstr = env->GetStringUTFChars(value, nullptr);
std::string decoded_value = base64_decode(value_cstr);
batch.Put(decoded_key, decoded_value);
env->ReleaseStringUTFChars(value, value_cstr);
} else if (strcmp(type_cstr, "del") == 0) {
batch.Delete(decoded_key);
} else {
throwJavaException(env, "Invalid operation type");
}
env->ReleaseStringUTFChars(key, key_cstr);
env->ReleaseStringUTFChars(type, type_cstr);
env->DeleteLocalRef(typeKey);
env->DeleteLocalRef(keyKey);
}
status = db->Write(leveldb::WriteOptions(), &batch);
if (!status.ok()) {
printf("Debug: Write operation failed with status: %s\n", status.ToString().c_str());
throwJavaException(env, status.ToString().c_str());
} else {
printf("Debug: Write operation successful\n");
}
env->ReleaseStringUTFChars(dbName, db_name_cstr);
}
JNIEXPORT jobject JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1iterator(JNIEnv *env, jobject, jstring dbName, jobject options) {
const char* db_name_cstr = env->GetStringUTFChars(dbName, nullptr);
leveldb::DB* db = dbs[db_name_cstr];
if (!db) {
throwJavaException(env, "No database found");
return nullptr;
}
jclass hashMapClass = env->GetObjectClass(options);
jmethodID getMethod = env->GetMethodID(hashMapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
jstring gtKey = env->NewStringUTF("gt");
jstring gteKey = env->NewStringUTF("gte");
jstring ltKey = env->NewStringUTF("lt");
jstring lteKey = env->NewStringUTF("lte");
jstring limitKey = env->NewStringUTF("limit");
jstring reverseKey = env->NewStringUTF("reverse");
jstring gt = (jstring)env->CallObjectMethod(options, getMethod, gtKey);
jstring gte = (jstring)env->CallObjectMethod(options, getMethod, gteKey);
jstring lt = (jstring)env->CallObjectMethod(options, getMethod, ltKey);
jstring lte = (jstring)env->CallObjectMethod(options, getMethod, lteKey);
jobject limitObject = env->CallObjectMethod(options, getMethod, limitKey);
jint limit = 0;
if (limitObject != nullptr) {
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID intValueMethod = env->GetMethodID(integerClass, "intValue", "()I");
limit = env->CallIntMethod(limitObject, intValueMethod);
env->DeleteLocalRef(integerClass);
}
jobject reverseObject = env->CallObjectMethod(options, getMethod, reverseKey);
jboolean reverse = JNI_FALSE;
if (reverseObject != nullptr) {
jclass booleanClass = env->FindClass("java/lang/Boolean");
jmethodID booleanValueMethod = env->GetMethodID(booleanClass, "booleanValue", "()Z");
reverse = env->CallBooleanMethod(reverseObject, booleanValueMethod);
env->DeleteLocalRef(booleanClass);
}
leveldb::Iterator* iterator = db->NewIterator(leveldb::ReadOptions());
jclass arrayListClass = env->FindClass("java/util/ArrayList");
jmethodID arrayListInit = env->GetMethodID(arrayListClass, "<init>", "()V");
jmethodID arrayListAdd = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
jobject resultList = env->NewObject(arrayListClass, arrayListInit);
if (gt) {
const char* gt_cstr = env->GetStringUTFChars(gt, nullptr);
std::string startKey = base64_decode(gt_cstr);
env->ReleaseStringUTFChars(gt, gt_cstr); // Release memory
iterator->Seek(leveldb::Slice(startKey));
if (iterator->Valid() && iterator->key().ToString() == startKey) {
iterator->Next();
}
} else if (gte) {
const char* gte_cstr = env->GetStringUTFChars(gte, nullptr);
std::string startKey = base64_decode(gte_cstr);
env->ReleaseStringUTFChars(gte, gte_cstr); // Release memory
iterator->Seek(leveldb::Slice(startKey));
} else {
iterator->SeekToFirst();
}
std::string endKey;
if (lt) {
const char* lt_cstr = env->GetStringUTFChars(lt, nullptr);
endKey = base64_decode(lt_cstr);
env->ReleaseStringUTFChars(lt, lt_cstr); // Release memory
} else if (lte) {
const char* lte_cstr = env->GetStringUTFChars(lte, nullptr);
endKey = base64_decode(lte_cstr);
env->ReleaseStringUTFChars(lte, lte_cstr); // Release memory
}
int count = 0;
while (iterator->Valid() && ((limit == -1) || count < limit)) {
std::string key = iterator->key().ToString();
bool isLessThan = lt && key < endKey;
bool isLessThanOrEqual = lte && key <= endKey;
bool shouldInclude = (!lt && !lte) || isLessThan || isLessThanOrEqual;
if (shouldInclude) {
std::string value = iterator->value().ToString();
std::string encodedKey = base64_encode(key);
std::string encodedValue = base64_encode(value);
jstring jKey = env->NewStringUTF(encodedKey.c_str());
jstring jValue = env->NewStringUTF(encodedValue.c_str());
jobject entry = env->NewObject(arrayListClass, arrayListInit);
env->CallBooleanMethod(entry, arrayListAdd, jKey);
env->CallBooleanMethod(entry, arrayListAdd, jValue);
env->CallBooleanMethod(resultList, arrayListAdd, entry);
iterator->Next();
count++;
} else {
break;
}
}
if (reverse) {
jclass collectionsClass = env->FindClass("java/util/Collections");
jmethodID reverseMethod = env->GetStaticMethodID(collectionsClass, "reverse", "(Ljava/util/List;)V");
env->CallStaticVoidMethod(collectionsClass, reverseMethod, resultList);
}
// Cleanup
delete iterator;
env->ReleaseStringUTFChars(dbName, db_name_cstr);
return resultList;
}
\ No newline at end of file
//
// Created by Aidan Dunlap on 5/16/24.
//
#ifndef ANDROID_LEVELDB_JNI_H
#define ANDROID_LEVELDB_JNI_H
#include <jni.h>
#include <string>
extern "C" {
//JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1init(JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1open(JNIEnv *, jobject, jstring, jstring);
JNIEXPORT jstring JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1get(JNIEnv *, jobject, jstring, jstring);
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1put(JNIEnv *, jobject, jstring, jstring, jstring);
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1delete(JNIEnv *, jobject, jstring, jstring);
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1close(JNIEnv *, jobject, jstring);
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1clear(JNIEnv *, jobject, jstring);
JNIEXPORT void JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1batch(JNIEnv *, jobject, jstring, jobjectArray);
JNIEXPORT jobject JNICALL Java_org_futo_polycentric_leveldbcapacitor_MobileLevelImpl_db_1iterator(JNIEnv *, jobject, jstring, jobject);
}
#endif //ANDROID_LEVELDB_JNI_H
Subproject commit 068d5ee1a3ac40dabd00d211d5013af44be55bea
// Write C++ code here.
//
// Do not forget to dynamically load the C++ library into your application.
//
// For instance,
//
// In MainActivity.java:
// static {
// System.loadLibrary("main");
// }
//
// Or, in MainActivity.kt:
// companion object {
// init {
// System.loadLibrary("main")
// }
// }
package org.futo.polycentric.leveldbcapacitor;
import android.util.Log;
public class MobileLevel {
public String echo(String value) {
Log.i("Echo", value);
return value;
}
}
package org.futo.polycentric.leveldbcapacitor;
import com.getcapacitor.JSObject;
public class MobileLevelCallbacks {
private final SuccessCallback successCallback;
private final FailureCallback failureCallback;
public interface SuccessCallback {
void onSuccess(JSObject result);
}
public interface FailureCallback {
void onFailure(String error);
}
public MobileLevelCallbacks(SuccessCallback success, FailureCallback failure) {
this.successCallback = success;
this.failureCallback = failure;
}
public void onSuccessWithResult(JSObject result) {
successCallback.onSuccess(result);
}
public void onFailure(String error) {
failureCallback.onFailure(error);
}
}
\ No newline at end of file
package org.futo.polycentric.leveldbcapacitor;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MobileLevelImpl {
// Add your native methods here
// Example native methods declarations
public native void db_open(String dbName, String path);
public native String db_get(String dbName, String key) throws Exception;
public native void db_put(String dbName, String key, String value) throws Exception;
public native void db_delete(String dbName, String key);
public native void db_clear(String dbName) throws Exception;
public native void db_close(String dbName);
public native void db_batch(String dbName, HashMap<String, String>[] operations);
public native ArrayList<ArrayList<String>> db_iterator(String dbName, HashMap<String, Object> options);
static {
System.loadLibrary("leveldb_jni");
}
}
\ No newline at end of file
package org.futo.polycentric.leveldbcapacitor;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
@CapacitorPlugin(name = "MobileLevel")
public class MobileLevelPlugin extends Plugin {
private MobileLevel implementation = new MobileLevel();
private MobileLevelImpl mobileLevel = new MobileLevelImpl();
@PluginMethod
public void open(PluginCall call) {
String dbName = call.getString("dbName");
if (dbName == null) {
call.reject("Missing dbName");
return;
}
File levelDBDir = new File(getContext().getFilesDir(), "leveldb");
if (!levelDBDir.exists()) {
levelDBDir.mkdirs();
}
// combine dir and db name
String dbPath = new File(levelDBDir, dbName).getAbsolutePath();
try {
mobileLevel.db_open(dbName, dbPath);
call.resolve();
} catch (Exception e) {
call.reject(e.getMessage());
}
}
@PluginMethod
public void get(PluginCall call) {
String dbName = call.getString("dbName");
String key = call.getString("key");
if (dbName == null || key == null) {
call.reject("Missing dbName or key");
return;
}
try {
var result = new JSObject();
result.put("value", mobileLevel.db_get(dbName, key));
call.resolve(result);
} catch (Exception e) {
call.reject(e.getMessage());
}
}
@PluginMethod
public void echo(PluginCall call) {
public void put(PluginCall call) {
String dbName = call.getString("dbName");
String key = call.getString("key");
String value = call.getString("value");
if (dbName == null || key == null || value == null) {
call.reject("Missing dbName, key, or value");
return;
}
try {
mobileLevel.db_put(dbName, key, value);
call.resolve();
} catch (Exception e) {
call.reject(e.getMessage());
}
}
@PluginMethod
public void delete(PluginCall call) {
String dbName = call.getString("dbName");
String key = call.getString("key");
if (dbName == null || key == null) {
call.reject("Missing dbName or key");
return;
}
try {
mobileLevel.db_delete(dbName, key);
call.resolve();
} catch (Exception e) {
call.reject(e.getMessage());
}
}
@PluginMethod
public void clear(PluginCall call) {
String dbName = call.getString("dbName");
if (dbName == null) {
call.reject("Missing dbName");
return;
}
try {
mobileLevel.db_clear(dbName);
call.resolve();
} catch (Exception e) {
call.reject(e.getMessage());
}
}
@PluginMethod
public void close(PluginCall call) {
String dbName = call.getString("dbName");
if (dbName == null) {
call.reject("Missing dbName");
return;
}
try {
mobileLevel.db_close(dbName);
call.resolve();
} catch (Exception e) {
call.reject(e.getMessage());
}
}
@PluginMethod
public void batch(PluginCall call) throws JSONException {
String dbName = call.getString("dbName");
JSArray operations = call.getArray("operations");
if (dbName == null || operations == null) {
call.reject("Missing dbName or operations");
return;
}
try {
HashMap<String, String>[] javaOperations = new HashMap[operations.length()];
for (int i = 0; i < operations.length(); i++) {
JSONObject operation = operations.getJSONObject(i); // corrected to getJSONObject(i)
HashMap<String, String> javaOperation = new HashMap<>();
javaOperation.put("type", operation.getString("type"));
javaOperation.put("key", operation.getString("key"));
if (operation.has("value")) {
javaOperation.put("value", operation.getString("value"));
}
javaOperations[i] = javaOperation;
}
mobileLevel.db_batch(dbName, javaOperations);
call.resolve();
} catch (Exception e) {
call.reject(e.getMessage());
}
}
@PluginMethod
public void iterator(PluginCall call) {
String dbName = call.getString("dbName");
JSObject options = call.getObject("options");
if (dbName == null || options == null) {
call.reject("Missing dbName or options");
return;
}
try {
HashMap<String, Object> java_options = new HashMap<>();
for (Iterator<String> it = options.keys(); it.hasNext();) {
String key = it.next();
Object value = options.get(key);
java_options.put(key, value);
}
var output = new JSObject();
// convert the iterator results from an Arraylist<ArrayList<String>> to JSArrays
var results = new JSArray();
List<ArrayList<String>> iteratorResults = mobileLevel.db_iterator(dbName, java_options);
for (List<String> result : iteratorResults) {
var jsResult = new JSArray();
for (String value : result) {
jsResult.put(value);
}
results.put(jsResult);
}
JSObject ret = new JSObject();
ret.put("value", implementation.echo(value));
call.resolve(ret);
output.put("results", results);
call.resolve(output);
} catch (Exception e) {
call.reject(e.getMessage());
}
}
}
esbuild src/index.ts \
--bundle \
--outfile=dist/plugin.js \
--format=iife \
--global-name=capacitorMobileLevel \
--external:@capacitor/core
# build cjs
esbuild src/index.ts \
--bundle \
--outfile=dist/plugin.cjs.js \
--format=cjs \
--external:@capacitor/core
esbuild src/index.ts \
--bundle \
--outfile=dist/esm/plugin.js \
--format=esm \
--external:@capacitor/core
cp dist/esm/plugin.js dist/esm/index.js
\ No newline at end of file
......@@ -197,7 +197,7 @@ NSDictionary * EMPTY_SUCCESS = nil;
if ([type isEqualToString:@"put"]) {
batch.Put(decoded_key, decoded_value);
} else if ([type isEqualToString:@"delete"]) {
} else if ([type isEqualToString:@"del"]) {
batch.Delete(decoded_key);
}
}
......@@ -255,7 +255,7 @@ NSDictionary * EMPTY_SUCCESS = nil;
// Collect results
int count = 0;
int limitInt = [limit intValue];
while (it->Valid() && ((limitInt == 0) || (count < limitInt))) {
while (it->Valid() && ((limitInt == -1) || (count < limitInt))) {
std::string currentKey = it->key().ToString();
bool isLessThan = lt && currentKey < endKey;
bool isLessThanOrEqual = lte && currentKey <= endKey;
......
This diff is collapsed.
......@@ -38,7 +38,7 @@
"prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
"swiftlint": "node-swiftlint",
"docgen": "docgen --api MobileLevelPlugin --output-readme README.md --output-json dist/docs.json",
"build": "npm run clean && tsc && rollup -c rollup.config.js",
"build": "npm run clean && tsc --noEmit && ./build.sh",
"clean": "rimraf ./dist",
"watch": "tsc --watch",
"prepublishOnly": "npm run build"
......@@ -51,11 +51,11 @@
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^1.0.1",
"@ionic/swiftlint-config": "^1.1.2",
"esbuild": "^0.21.4",
"eslint": "^9.1.1",
"prettier": "~2.3.0",
"prettier-plugin-java": "~1.0.2",
"rimraf": "^3.0.2",
"rollup": "^2.32.0",
"swiftlint": "^1.0.1",
"typescript": "^5.4.5"
},
......
export default {
input: 'dist/esm/index.js',
output: [
{
file: 'dist/plugin.js',
format: 'iife',
name: 'capacitorMobileLevel',
globals: {
'@capacitor/core': 'capacitorExports',
},
sourcemap: true,
inlineDynamicImports: true,
},
{
file: 'dist/plugin.cjs.js',
format: 'cjs',
sourcemap: true,
inlineDynamicImports: true,
},
],
external: ['@capacitor/core'],
};