diff --git a/src/android/gen_kiwix.sh b/src/android/gen_kiwix.sh new file mode 100755 index 000000000..c6fb46b5c --- /dev/null +++ b/src/android/gen_kiwix.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +BUILD_PATH=$(pwd) + +javac -d $BUILD_PATH/src/android $1 $2 $3 $4 + +cd $BUILD_PATH/src/android +javah -jni org.kiwix.kiwixlib.JNIKiwix +cd $BUILD_PATH diff --git a/src/android/kiwix.cpp b/src/android/kiwix.cpp new file mode 100644 index 000000000..dee61f79e --- /dev/null +++ b/src/android/kiwix.cpp @@ -0,0 +1,486 @@ +#include +#include "org_kiwix_kiwixlib_JNIKiwix.h" + +#include +#include + +#include +#include + +#include "unicode/putil.h" +#include "reader.h" +#include "xapianSearcher.h" +#include "common/base64.h" + +#include +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "kiwix", __VA_ARGS__) + +#include +#include +#include +#include +#include + +/* global variables */ +kiwix::Reader *reader = NULL; +kiwix::XapianSearcher *searcher = NULL; + +static pthread_mutex_t readerLock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t searcherLock = PTHREAD_MUTEX_INITIALIZER; + +/* c2jni type conversion functions */ +jboolean c2jni(const bool &val) { + return val ? JNI_TRUE : JNI_FALSE; +} + +jstring c2jni(const std::string &val, JNIEnv *env) { + return env->NewStringUTF(val.c_str()); +} + +jint c2jni(const int val) { + return (jint)val; +} + +jint c2jni(const unsigned val) { + return (unsigned)val; +} + +/* jni2c type conversion functions */ +bool jni2c(const jboolean &val) { + return val == JNI_TRUE; +} + +std::string jni2c(const jstring &val, JNIEnv *env) { + return std::string(env->GetStringUTFChars(val, 0)); +} + +int jni2c(const jint val) { + return (int)val; +} + +/* Method to deal with variable passed by reference */ +void setStringObjValue(const std::string &value, const jobject obj, JNIEnv *env) { + jclass objClass = env->GetObjectClass(obj); + jfieldID objFid = env->GetFieldID(objClass, "value", "Ljava/lang/String;"); + env->SetObjectField(obj, objFid, c2jni(value, env)); +} + +void setIntObjValue(const int value, const jobject obj, JNIEnv *env) { + jclass objClass = env->GetObjectClass(obj); + jfieldID objFid = env->GetFieldID(objClass, "value", "I"); + env->SetIntField(obj, objFid, value); +} + +void setBoolObjValue(const bool value, const jobject obj, JNIEnv *env) { + jclass objClass = env->GetObjectClass(obj); + jfieldID objFid = env->GetFieldID(objClass, "value", "Z"); + env->SetIntField(obj, objFid, c2jni(value)); +} + +/* Kiwix library functions */ +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getMainPage(JNIEnv *env, jobject obj) { + jstring url; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cUrl = reader->getMainPageUrl(); + url = c2jni(cUrl, env); + } catch (...) { + std::cerr << "Unable to get ZIM main page" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return url; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getId(JNIEnv *env, jobject obj) { + jstring id; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cId = reader->getId(); + id = c2jni(cId, env); + } catch (...) { + std::cerr << "Unable to get ZIM id" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return id; +} + +JNIEXPORT jint JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getFileSize(JNIEnv *env, jobject obj) { + jint size; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + int cSize = reader->getFileSize(); + size = c2jni(cSize); + } catch (...) { + std::cerr << "Unable to get ZIM file size" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return size; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getCreator(JNIEnv *env, jobject obj) { + jstring creator; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cCreator = reader->getCreator(); + creator = c2jni(cCreator, env); + } catch (...) { + std::cerr << "Unable to get ZIM creator" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return creator; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getPublisher(JNIEnv *env, jobject obj) { + jstring publisher; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cPublisher = reader->getPublisher(); + publisher = c2jni(cPublisher, env); + } catch (...) { + std::cerr << "Unable to get ZIM creator" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return publisher; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getName(JNIEnv *env, jobject obj) { + jstring name; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cName = reader->getName(); + name = c2jni(cName, env); + } catch (...) { + std::cerr << "Unable to get ZIM name" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return name; +} + + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getFavicon(JNIEnv *env, jobject obj) { + jstring favicon; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cContent; + std::string cMime; + reader->getFavicon(cContent, cMime); + favicon = c2jni(base64_encode(reinterpret_cast(cContent.c_str()), cContent.length()), env); + } catch (...) { + std::cerr << "Unable to get ZIM favicon" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return favicon; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getDate(JNIEnv *env, jobject obj) { + jstring date; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cDate = reader->getDate(); + date = c2jni(cDate, env); + } catch (...) { + std::cerr << "Unable to get ZIM date" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return date; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getLanguage(JNIEnv *env, jobject obj) { + jstring language; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cLanguage = reader->getLanguage(); + language = c2jni(cLanguage, env); + } catch (...) { + std::cerr << "Unable to get ZIM language" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return language; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getMimeType(JNIEnv *env, jobject obj, jstring url) { + jstring mimeType; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + std::string cUrl = jni2c(url, env); + try { + std::string cMimeType; + reader->getMimeTypeByUrl(cUrl, cMimeType); + mimeType = c2jni(cMimeType, env); + } catch (...) { + std::cerr << "Unable to get mime-type for url " << cUrl << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return mimeType; +} + +JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_loadZIM(JNIEnv *env, jobject obj, jstring path) { + jboolean retVal = JNI_TRUE; + std::string cPath = jni2c(path, env); + + pthread_mutex_lock(&readerLock); + try { + if (reader != NULL) delete reader; + reader = new kiwix::Reader(cPath); + } catch (...) { + std::cerr << "Unable to load ZIM " << cPath << std::endl; + reader = NULL; + retVal = JNI_FALSE; + } + pthread_mutex_unlock(&readerLock); + + return retVal; +} + +JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getContent(JNIEnv *env, jobject obj, jstring url, jobject mimeTypeObj, jobject sizeObj) { + + /* Default values */ + setStringObjValue("", mimeTypeObj, env); + setIntObjValue(0, sizeObj, env); + jbyteArray data = env->NewByteArray(0); + + /* Retrieve the content */ + if (reader != NULL) { + std::string cUrl = jni2c(url, env); + std::string cData; + std::string cMimeType; + unsigned int cSize = 0; + + pthread_mutex_lock(&readerLock); + try { + if (reader->getContentByUrl(cUrl, cData, cSize, cMimeType)) { + data = env->NewByteArray(cSize); + env->SetByteArrayRegion(data, 0, cSize, reinterpret_cast(cData.c_str())); + setStringObjValue(cMimeType, mimeTypeObj, env); + setIntObjValue(cSize, sizeObj, env); + } + } catch (...) { + std::cerr << "Unable to get content for url " << cUrl << std::endl; + } + pthread_mutex_unlock(&readerLock); + } + + return data; +} + +JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_searchSuggestions +(JNIEnv *env, jobject obj, jstring prefix, jint count) { + jboolean retVal = JNI_FALSE; + std::string cPrefix = jni2c(prefix, env); + unsigned int cCount = jni2c(count); + + pthread_mutex_lock(&readerLock); + try { + if (reader != NULL) { + if (reader->searchSuggestionsSmart(cPrefix, cCount)) { + retVal = JNI_TRUE; + } + } + } catch (...) { + std::cerr << "Unable to search suggestions for pattern " << cPrefix << std::endl; + } + pthread_mutex_unlock(&readerLock); + + return retVal; +} + +JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getNextSuggestion +(JNIEnv *env, jobject obj, jobject titleObj) { + jboolean retVal = JNI_FALSE; + std::string cTitle; + + pthread_mutex_lock(&readerLock); + try { + if (reader != NULL) { + if (reader->getNextSuggestion(cTitle)) { + setStringObjValue(cTitle, titleObj, env); + retVal = JNI_TRUE; + } + } + } catch (...) { + std::cerr << "Unable to get next suggestion" << std::endl; + } + pthread_mutex_unlock(&readerLock); + + return retVal; +} + +JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getPageUrlFromTitle +(JNIEnv *env, jobject obj, jstring title, jobject urlObj) { + jboolean retVal = JNI_FALSE; + std::string cTitle = jni2c(title, env); + std::string cUrl; + + pthread_mutex_lock(&readerLock); + try { + if (reader != NULL) { + if (reader->getPageUrlFromTitle(cTitle, cUrl)) { + setStringObjValue(cUrl, urlObj, env); + retVal = JNI_TRUE; + } + } + } catch (...) { + std::cerr << "Unable to get URL for title " << cTitle << std::endl; + } + pthread_mutex_unlock(&readerLock); + + return retVal; +} + +JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getTitle +(JNIEnv *env , jobject obj, jobject titleObj) { + jboolean retVal = JNI_FALSE; + std::string cTitle; + + pthread_mutex_lock(&readerLock); + try { + if (reader != NULL) { + std::string cTitle = reader->getTitle(); + setStringObjValue(cTitle, titleObj, env); + retVal = JNI_TRUE; + } + } catch (...) { + std::cerr << "Unable to get ZIM title" << std::endl; + } + pthread_mutex_unlock(&readerLock); + + return retVal; + +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getDescription(JNIEnv *env, jobject obj) { + jstring description; + + pthread_mutex_lock(&readerLock); + if (reader != NULL) { + try { + std::string cDescription = reader->getDescription(); + description = c2jni(cDescription, env); + } catch (...) { + std::cerr << "Unable to get ZIM description" << std::endl; + } + } + pthread_mutex_unlock(&readerLock); + + return description; +} + +JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getRandomPage +(JNIEnv *env, jobject obj, jobject urlObj) { + jboolean retVal = JNI_FALSE; + std::string cUrl; + + pthread_mutex_lock(&readerLock); + try { + if (reader != NULL) { + std::string cUrl = reader->getRandomPageUrl(); + setStringObjValue(cUrl, urlObj, env); + retVal = JNI_TRUE; + } + } catch (...) { + std::cerr << "Unable to get random page" << std::endl; + } + pthread_mutex_unlock(&readerLock); + + return retVal; +} + +JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_setDataDirectory + (JNIEnv *env, jobject obj, jstring dirStr) { + std::string cPath = jni2c(dirStr, env); + + pthread_mutex_lock(&readerLock); + try { + u_setDataDirectory(cPath.c_str()); + } catch (...) { + std::cerr << "Unable to set data directory " << cPath << std::endl; + } + pthread_mutex_unlock(&readerLock); +} + +JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_loadFulltextIndex(JNIEnv *env, jobject obj, jstring path) { + jboolean retVal = JNI_TRUE; + std::string cPath = jni2c(path, env); + + pthread_mutex_lock(&searcherLock); + searcher = NULL; + try { + if (searcher != NULL) delete searcher; + searcher = new kiwix::XapianSearcher(cPath); + } catch (...) { + searcher = NULL; + retVal = JNI_FALSE; + std::cerr << "Unable to load full text index " << cPath << std::endl; + } + pthread_mutex_unlock(&searcherLock); + + return retVal; +} + +JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_indexedQuery + (JNIEnv *env, jclass obj, jstring query, jint count) { + std::string cQuery = jni2c(query, env); + unsigned int cCount = jni2c(count); + std::string url; + std::string title; + std::string result; + unsigned int score; + + pthread_mutex_lock(&searcherLock); + try { + if (searcher != NULL) { + searcher->search(cQuery, 0, count); + while (searcher->getNextResult(url, title, score) && + !title.empty() && + !url.empty()) { + result += title + "\n"; + } + } + } catch (...) { + std::cerr << "Unable to make indexed query " << cQuery << std::endl; + } + pthread_mutex_unlock(&searcherLock); + + return env->NewStringUTF(result.c_str()); +} + + diff --git a/src/android/meson.build b/src/android/meson.build new file mode 100644 index 000000000..b221f9299 --- /dev/null +++ b/src/android/meson.build @@ -0,0 +1,13 @@ + +jni_generator = find_program('gen_kiwix.sh') + +kiwix_jni = custom_target('jni', + input: ['org/kiwix/kiwixlib/JNIKiwix.java', + 'org/kiwix/kiwixlib/JNIKiwixInt.java', + 'org/kiwix/kiwixlib/JNIKiwixString.java', + 'org/kiwix/kiwixlib/JNIKiwixBool.java'], + output: ['org_kiwix_kiwixlib_JNIKiwix.h'], + command:[jni_generator, '@INPUT@'] +) + +kiwix_sources += ['android/kiwix.cpp', kiwix_jni] diff --git a/src/android/org/kiwix/kiwixlib/JNIKiwix.java b/src/android/org/kiwix/kiwixlib/JNIKiwix.java new file mode 100644 index 000000000..e925d65a0 --- /dev/null +++ b/src/android/org/kiwix/kiwixlib/JNIKiwix.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.kiwix.kiwixlib; + +import org.kiwix.kiwixlib.JNIKiwixString; +import org.kiwix.kiwixlib.JNIKiwixBool; +import org.kiwix.kiwixlib.JNIKiwixInt; + +public class JNIKiwix { + + static { + System.loadLibrary("kiwix"); + } + + public native String getMainPage(); + + public native String getId(); + + public native String getLanguage(); + + public native String getMimeType(String url); + + public native boolean loadZIM(String path); + + public native boolean loadFulltextIndex(String path); + + public native byte[] getContent(String url, JNIKiwixString mimeType, JNIKiwixInt size); + + public native boolean searchSuggestions(String prefix, int count); + + public native boolean getNextSuggestion(JNIKiwixString title); + + public native boolean getPageUrlFromTitle(String title, JNIKiwixString url); + + public native boolean getTitle(JNIKiwixString title); + + public native String getDescription(); + + public native String getDate(); + + public native String getFavicon(); + + public native String getCreator(); + + public native String getPublisher(); + + public native String getName(); + + public native int getFileSize(); + + public native int getArticleCount(); + + public native int getMediaCount(); + + public native boolean getRandomPage(JNIKiwixString url); + + public native void setDataDirectory(String icuDataDir); + + public static native String indexedQuery(String db, int count); +} diff --git a/src/android/org/kiwix/kiwixlib/JNIKiwixBool.java b/src/android/org/kiwix/kiwixlib/JNIKiwixBool.java new file mode 100644 index 000000000..7cf645460 --- /dev/null +++ b/src/android/org/kiwix/kiwixlib/JNIKiwixBool.java @@ -0,0 +1,25 @@ +/* + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.kiwix.kiwixlib; + +public class JNIKiwixBool { + + public boolean value; +} diff --git a/src/android/org/kiwix/kiwixlib/JNIKiwixInt.java b/src/android/org/kiwix/kiwixlib/JNIKiwixInt.java new file mode 100644 index 000000000..49509c257 --- /dev/null +++ b/src/android/org/kiwix/kiwixlib/JNIKiwixInt.java @@ -0,0 +1,26 @@ +/* + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.kiwix.kiwixlib; + +public class JNIKiwixInt { + + public int value; +} + diff --git a/src/android/org/kiwix/kiwixlib/JNIKiwixString.java b/src/android/org/kiwix/kiwixlib/JNIKiwixString.java new file mode 100644 index 000000000..c73e8b925 --- /dev/null +++ b/src/android/org/kiwix/kiwixlib/JNIKiwixString.java @@ -0,0 +1,25 @@ +/* + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.kiwix.kiwixlib; + +public class JNIKiwixString { + + public String value; +} diff --git a/src/meson.build b/src/meson.build index 969ee670c..06496ca4f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -23,6 +23,8 @@ endif if not get_option('android') kiwix_sources += ['indexer.cpp'] +else + subdir('android') endif if has_ctpp2_dep