diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 839d58a39..bd8956faa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -100,6 +100,9 @@ play { dependencies { androidTestImplementation(Libs.leakcanary_android_instrumentation) + implementation("com.getkeepsafe.relinker:relinker:1.4.5") + api(fileTree(mapOf("include" to "*.aar", "dir" to "libs"))) + implementation(files("/home/hp-pc03/Desktop/lib-release.aar")) } task("generateVersionCodeAndName") { val file = File("VERSION_INFO") diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt index b0783bbf1..e4d4af0b3 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt @@ -25,7 +25,7 @@ import androidx.test.uiautomator.UiDevice import org.junit.Assert import org.junit.Before import org.junit.Test -import org.kiwix.kiwixlib.JNIKiwixReader +import org.kiwix.libzim.Archive import org.kiwix.kiwixmobile.BaseActivityTest import org.kiwix.kiwixmobile.core.NightModeConfig import org.kiwix.kiwixmobile.core.reader.ZimFileReader @@ -70,12 +70,12 @@ class MimeTypeTest : BaseActivityTest() { } val zimFileReader = ZimFileReader( zimFile, - JNIKiwixReader(zimFile.canonicalPath), + Archive(zimFile.canonicalPath), NightModeConfig(SharedPreferenceUtil(context), context) ) zimFileReader.getRandomArticleUrl()?.let { - val mimeType = zimFileReader.readContentAndMimeType(it) - if (mimeType.contains("^([^ ]+).*$") || mimeType.contains(";")) { + val mimeType = zimFileReader.getMimeTypeFromUrl(it) + if (mimeType?.contains("^([^ ]+).*$") == true || mimeType?.contains(";") == true) { Assert.fail( "Unable to get mime type from zim file. File = " + " $zimFile and url of article = $it" diff --git a/app/src/main/java/org/kiwix/kiwixmobile/webserver/KiwixServer.kt b/app/src/main/java/org/kiwix/kiwixmobile/webserver/KiwixServer.kt index 6457ddab6..4f05c7389 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/webserver/KiwixServer.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/webserver/KiwixServer.kt @@ -19,26 +19,31 @@ package org.kiwix.kiwixmobile.webserver import android.util.Log -import org.kiwix.kiwixlib.JNIKiwixException -import org.kiwix.kiwixlib.JNIKiwixServer -import org.kiwix.kiwixlib.Library +import org.kiwix.libkiwix.Book +import org.kiwix.libkiwix.JNIKiwixException +import org.kiwix.libkiwix.Library +import org.kiwix.libkiwix.Server +import org.kiwix.libzim.Archive import javax.inject.Inject private const val TAG = "KiwixServer" -class KiwixServer @Inject constructor(private val jniKiwixServer: JNIKiwixServer) { +class KiwixServer @Inject constructor(private val jniKiwixServer: Server) { class Factory @Inject constructor() { fun createKiwixServer(selectedBooksPath: ArrayList): KiwixServer { val kiwixLibrary = Library() selectedBooksPath.forEach { path -> try { - kiwixLibrary.addBook(path) + val book = Book().apply { + update(Archive(path)) + } + kiwixLibrary.addBook(book) } catch (e: JNIKiwixException) { Log.v(TAG, "Couldn't add book with path:{ $path }") } } - return KiwixServer(JNIKiwixServer(kiwixLibrary)) + return KiwixServer(Server(kiwixLibrary)) } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index fbc954a7c..ca3aa315b 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -60,12 +60,16 @@ dependencies { implementation(Libs.threetenabp) // Get kiwixlib online if it is not populated locally - if (!shouldUseLocalVersion()) { - api(Libs.kiwixlib) - } else { - implementation("com.getkeepsafe.relinker:relinker:1.4.5") - api(fileTree(mapOf("include" to "*.aar", "dir" to "libs"))) - } + implementation("com.getkeepsafe.relinker:relinker:1.4.5") + api(fileTree(mapOf("include" to "*.aar", "dir" to "libs"))) + implementation(files("/home/hp-pc03/Desktop/lib-release.aar")) + // if (!shouldUseLocalVersion()) { + // api(Libs.kiwixlib) + // } else { + // implementation("com.getkeepsafe.relinker:relinker:1.4.5") + // api(fileTree(mapOf("include" to "*.aar", "dir" to "libs"))) + // implementation(files("/home/hp-pc03/Desktop/lib-release.aar")) + // } // Document File implementation(Libs.select_folder_document_file) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/JNIInitialiser.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/JNIInitialiser.kt index 49a7aa3a2..45da877d7 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/JNIInitialiser.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/JNIInitialiser.kt @@ -19,7 +19,7 @@ package org.kiwix.kiwixmobile.core import android.content.Context import android.util.Log -import org.kiwix.kiwixlib.JNIKiwix +import org.kiwix.libkiwix.JNIKiwix import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX import java.io.File import java.io.FileOutputStream @@ -27,7 +27,7 @@ import javax.inject.Inject internal class JNIInitialiser @Inject constructor(context: Context, jniKiwix: JNIKiwix) { init { - loadICUData(context)?.let(jniKiwix::setDataDirectory) + // loadICUData(context)?.let(jniKiwix::setDataDirectory) } private fun loadICUData(context: Context): String? { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt index 88830b5e7..ba1001d12 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt @@ -20,20 +20,11 @@ package org.kiwix.kiwixmobile.core.di.modules import android.content.Context import dagger.Module import dagger.Provides -import org.kiwix.kiwixlib.JNIKiwix -import org.kiwix.kiwixlib.JNIKiwixSearcher +import org.kiwix.libkiwix.JNIKiwix import javax.inject.Singleton @Module class JNIModule { @Provides @Singleton fun providesJNIKiwix(context: Context): JNIKiwix = JNIKiwix(context) - - @Provides @Singleton fun providesJNIKiwixSearcher(): JNIKiwixSearcher? { - return try { - JNIKiwixSearcher() - } catch (ignore: UnsatisfiedLinkError) { - null - } - } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt index 20a4330d0..9df7183f1 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt @@ -21,15 +21,11 @@ import android.annotation.SuppressLint import android.content.res.AssetFileDescriptor import android.net.Uri import android.os.ParcelFileDescriptor +import android.util.Base64 import android.util.Log import androidx.core.net.toUri import io.reactivex.Completable import io.reactivex.schedulers.Schedulers -import org.kiwix.kiwixlib.DirectAccessInfo -import org.kiwix.kiwixlib.JNIKiwixException -import org.kiwix.kiwixlib.JNIKiwixInt -import org.kiwix.kiwixlib.JNIKiwixReader -import org.kiwix.kiwixlib.JNIKiwixString import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.NightModeConfig import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book @@ -38,6 +34,13 @@ import org.kiwix.kiwixmobile.core.main.UNINITIALISE_HTML import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Companion.CONTENT_PREFIX import org.kiwix.kiwixmobile.core.search.SearchSuggestion import org.kiwix.kiwixmobile.core.utils.files.FileUtils +import org.kiwix.libkiwix.JNIKiwixException +import org.kiwix.libzim.Archive +import org.kiwix.libzim.DirectAccessInfo +import org.kiwix.libzim.EntryNotFoundException +import org.kiwix.libzim.Item +import org.kiwix.libzim.SuggestionSearch +import org.kiwix.libzim.SuggestionSearcher import java.io.File import java.io.FileInputStream import java.io.IOException @@ -51,8 +54,9 @@ private const val TAG = "ZimFileReader" class ZimFileReader constructor( val zimFile: File, - private val jniKiwixReader: JNIKiwixReader = JNIKiwixReader(zimFile.canonicalPath), - private val nightModeConfig: NightModeConfig + private val jniKiwixReader: Archive = Archive(zimFile.canonicalPath), + private val nightModeConfig: NightModeConfig, + private val suggestionSearcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader) ) { interface Factory { fun create(file: File): ZimFileReader? @@ -61,7 +65,9 @@ class ZimFileReader constructor( Factory { override fun create(file: File) = try { - ZimFileReader(file, nightModeConfig = nightModeConfig) + ZimFileReader(file, nightModeConfig = nightModeConfig).also { + Log.e(TAG, "create: ${file.path}") + } } catch (ignore: JNIKiwixException) { null } @@ -72,17 +78,42 @@ class ZimFileReader constructor( * Note that the value returned is NOT unique for each zim file. Versions of the same wiki * (complete, nopic, novid, etc) may return the same title. */ - val title: String get() = jniKiwixReader.title ?: "No Title Found" - val mainPage: String get() = jniKiwixReader.mainPage - val id: String get() = jniKiwixReader.id - val fileSize: Int get() = jniKiwixReader.fileSize - val creator: String get() = jniKiwixReader.creator - val publisher: String get() = jniKiwixReader.publisher - val name: String get() = jniKiwixReader.name?.takeIf(String::isNotEmpty) ?: id - val date: String get() = jniKiwixReader.date - val description: String get() = jniKiwixReader.description - val favicon: String? get() = jniKiwixReader.favicon - val language: String get() = jniKiwixReader.language + val title: String + get() = + try { + if (jniKiwixReader.mainEntry.isRedirect) + jniKiwixReader.mainEntry.getItem(true).title + else + jniKiwixReader.mainEntry.title + } catch (entryNotFound: EntryNotFoundException) { + "No Title Found" + } + val mainPage: String? + get() = + try { + if (jniKiwixReader.mainEntry.isRedirect) + jniKiwixReader.mainEntry.getItem(true).path + else + jniKiwixReader.mainEntry.path + } catch (entryNotFound: EntryNotFoundException) { + null + } + val id: String get() = jniKiwixReader.uuid + val fileSize: Long get() = jniKiwixReader.filesize + val creator: String get() = jniKiwixReader.getMetadata("Creator") + val publisher: String get() = jniKiwixReader.getMetadata("Publisher") + val name: String get() = jniKiwixReader.getMetadata("Name")?.takeIf(String::isNotEmpty) ?: id + val date: String get() = jniKiwixReader.getMetadata("Date") + val description: String get() = jniKiwixReader.getMetadata("Description") + val favicon: String? + get() = if (jniKiwixReader.hasIllustration(48)) + Base64.encodeToString( + jniKiwixReader.getIllustrationItem(48).data.data, + Base64.DEFAULT + ) + else + null + val language: String get() = jniKiwixReader.getMetadata("Language") val tags: String get() = "${getContent("M/Tags")}" private val mediaCount: Int? get() = try { @@ -97,23 +128,28 @@ class ZimFileReader constructor( null } - fun searchSuggestions(prefix: String, count: Int) = - jniKiwixReader.searchSuggestions(prefix, count) + fun searchSuggestions(prefix: String): SuggestionSearch = + suggestionSearcher.suggest(prefix) - fun getNextSuggestion(): SearchSuggestion? { - val title = JNIKiwixString() - val url = JNIKiwixString() - - return if (jniKiwixReader.getNextSuggestion(title, url)) - SearchSuggestion(title.value, url.value) - else null + fun getNextSuggestion(suggestionSearch: SuggestionSearch?): SearchSuggestion? { + val suggestionIterator = + suggestionSearch?.getResults(0, suggestionSearch.estimatedMatches.toInt()) + if (suggestionIterator != null) { + while (suggestionIterator.hasNext()) { + val suggestionItem = suggestionIterator.next() + return SearchSuggestion(suggestionItem.title, suggestionItem.path) + } + } + return null } fun getPageUrlFrom(title: String): String? = - valueOfJniStringAfter { jniKiwixReader.getPageUrlFromTitle(title, it) } + if (jniKiwixReader.hasEntryByTitle(title)) + jniKiwixReader.getEntryByTitle(title).path + else + null - fun getRandomArticleUrl(): String? = - valueOfJniStringAfter(jniKiwixReader::getRandomPage) + fun getRandomArticleUrl(): String? = jniKiwixReader.randomEntry.path fun load(uri: String): InputStream? { val extension = uri.substringAfterLast(".") @@ -127,8 +163,8 @@ class ZimFileReader constructor( return loadContent(uri) } - fun readContentAndMimeType(uri: String): String = getContentAndMimeType(uri) - .second.truncateMimeType.also { + fun getMimeTypeFromUrl(uri: String): String? = getItem(uri)?.mimetype + ?.truncateMimeType.also { Log.d(TAG, "getting mimetype for $uri = $it") } @@ -141,9 +177,20 @@ class ZimFileReader constructor( } private fun toRedirect(url: String) = - "$CONTENT_PREFIX${ - jniKiwixReader.checkUrl(url.toUri().filePath).replaceWithEncodedString - }".toUri() + "$CONTENT_PREFIX${getActualUrl(url)}".toUri() + + private fun getActualUrl(url: String): String { + val actualPath = url.toUri().filePath + val redirectPath = if (jniKiwixReader.hasEntryByPath(actualPath)) { + jniKiwixReader.getEntryByPath(actualPath) + .getItem(true) + .path + .replaceWithEncodedString + } else { + "" + } + return redirectPath + } private fun loadContent(uri: String) = try { @@ -154,14 +201,19 @@ class ZimFileReader constructor( } private fun loadAsset(uri: String): InputStream? { - val infoPair = jniKiwixReader.getDirectAccessInformation(uri.filePath) + val article = if (jniKiwixReader.hasEntryByPath(uri.filePath)) { + jniKiwixReader.getEntryByPath(uri.filePath).getItem(true) + } else { + null + } + val infoPair = article?.directAccessInformation if (infoPair == null || !File(infoPair.filename).exists()) { return loadAssetFromCache(uri) } return AssetFileDescriptor( infoPair.parcelFileDescriptor, infoPair.offset, - jniKiwixReader.getArticleSize(uri.filePath) + article.size ).createInputStream() } @@ -170,11 +222,11 @@ class ZimFileReader constructor( return File( FileUtils.getFileCacheDir(CoreApp.instance), uri.substringAfterLast("/") - ).apply { writeBytes(getContent(uri)) } + ).apply { getContent(uri)?.let(::writeBytes) } .inputStream() } - private fun getContent(url: String) = getContentAndMimeType(url).let { (content, _) -> content } + private fun getContent(url: String) = getItem(url)?.data?.data @SuppressLint("CheckResult") private fun streamZimContentToPipe(uri: String, outputStream: OutputStream) { @@ -184,11 +236,11 @@ class ZimFileReader constructor( if (uri.endsWith(UNINITIALISER_ADDRESS)) { it.write(UNINITIALISE_HTML.toByteArray()) } else { - getContentAndMimeType(uri).let { (content: ByteArray, mimeType: String) -> - if ("text/css" == mimeType && nightModeConfig.isNightModeActive()) { + getItem(uri)?.let { item -> + if ("text/css" == item.mimetype && nightModeConfig.isNightModeActive()) { it.write(INVERT_IMAGES_VIDEO.toByteArray()) } - it.write(content) + it.write(item.data.data) } } } @@ -200,21 +252,15 @@ class ZimFileReader constructor( .subscribe({ }, Throwable::printStackTrace) } - private fun getContentAndMimeType(uri: String) = with(JNIKiwixString()) { - getContent(url = JNIKiwixString(uri.filePath), mime = this) to value - } - - private fun getContent( - url: JNIKiwixString = JNIKiwixString(), - jniKiwixString: JNIKiwixString = JNIKiwixString(), - mime: JNIKiwixString = JNIKiwixString(), - size: JNIKiwixInt = JNIKiwixInt() - ) = jniKiwixReader.getContent(url, jniKiwixString, mime, size).also { - Log.d(TAG, "reading ${url.value}(mime: ${mime.value}, size: ${size.value}) finished.") - } - - private fun valueOfJniStringAfter(jniStringFunction: (JNIKiwixString) -> Boolean) = - JNIKiwixString().takeIf { jniStringFunction(it) }?.value + private fun getItem(url: String): Item? = + if (jniKiwixReader.hasEntryByPath(getActualUrl(url))) { + jniKiwixReader.getEntryByPath(getActualUrl(url)).getItem(true).also { + Log.e(TAG, "getItem: $url") + } + } else { + Log.e(TAG, "getItem else: $url") + null + } @Suppress("ExplicitThis") // this@ZimFileReader.name is required fun toBook() = Book().apply { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt index b3fc957d7..5c0218228 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt @@ -50,7 +50,7 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F fun load(url: String, requestHeaders: Map): WebResourceResponse { val data = zimFileReader?.load(url) return WebResourceResponse( - zimFileReader?.readContentAndMimeType(url), + zimFileReader?.getMimeTypeFromUrl(url), Charsets.UTF_8.name(), data ) @@ -80,7 +80,7 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F val zimFileTitle get() = zimFileReader?.title val mainPage get() = zimFileReader?.mainPage val id get() = zimFileReader?.id - val fileSize get() = zimFileReader?.fileSize ?: 0 + val fileSize get() = zimFileReader?.fileSize ?: 0L val creator get() = zimFileReader?.creator val publisher get() = zimFileReader?.publisher val name get() = zimFileReader?.name diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchResultGenerator.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchResultGenerator.kt index 3924cd100..74ec4e32e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchResultGenerator.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchResultGenerator.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.yield import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.ZimSearchResultListItem +import org.kiwix.libzim.SuggestionSearch import javax.inject.Inject interface SearchResultGenerator { @@ -46,13 +47,16 @@ class ZimSearchResultGenerator @Inject constructor() : SearchResultGenerator { reader: ZimFileReader? ) = reader.also { yield() } - ?.searchSuggestions(searchTerm, 200) + ?.searchSuggestions(searchTerm) .also { yield() } - .run { suggestionResults(reader) } + .run { suggestionResults(reader, this) } - private suspend fun suggestionResults(reader: ZimFileReader?) = createList { + private suspend fun suggestionResults( + reader: ZimFileReader?, + suggestionSearch: SuggestionSearch? + ) = createList { yield() - reader?.getNextSuggestion() + reader?.getNextSuggestion(suggestionSearch) ?.let { ZimSearchResultListItem(it.title) } } .distinct() diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/ZimSearchResultGeneratorTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/ZimSearchResultGeneratorTest.kt index dc9a37840..1d4379e4f 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/ZimSearchResultGeneratorTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/ZimSearchResultGeneratorTest.kt @@ -48,14 +48,14 @@ internal class ZimSearchResultGeneratorTest { val validTitle = "title" val searchTerm = " " val item = mockk() - every { zimFileReader.searchSuggestions(" ", 200) } returns true - every { zimFileReader.getNextSuggestion() } returnsMany listOf(item, item, null) + every { zimFileReader.searchSuggestions(" ") } returns true + every { zimFileReader.getNextSuggestion(suggestionSearch) } returnsMany listOf(item, item, null) every { item.title } returns validTitle runBlocking { assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader)) .isEqualTo(listOf(ZimSearchResultListItem(validTitle))) verify { - zimFileReader.searchSuggestions(searchTerm, 200) + zimFileReader.searchSuggestions(searchTerm) } } }