Refactored the code to support the java-libkiwix 11.0.0

This commit is contained in:
MohitMali 2023-07-13 19:16:39 +05:30
parent 8469e18e23
commit 1476452085
10 changed files with 148 additions and 95 deletions

View File

@ -100,6 +100,9 @@ play {
dependencies { dependencies {
androidTestImplementation(Libs.leakcanary_android_instrumentation) 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") { task("generateVersionCodeAndName") {
val file = File("VERSION_INFO") val file = File("VERSION_INFO")

View File

@ -25,7 +25,7 @@ import androidx.test.uiautomator.UiDevice
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.kiwix.kiwixlib.JNIKiwixReader import org.kiwix.libzim.Archive
import org.kiwix.kiwixmobile.BaseActivityTest import org.kiwix.kiwixmobile.BaseActivityTest
import org.kiwix.kiwixmobile.core.NightModeConfig import org.kiwix.kiwixmobile.core.NightModeConfig
import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimFileReader
@ -70,12 +70,12 @@ class MimeTypeTest : BaseActivityTest() {
} }
val zimFileReader = ZimFileReader( val zimFileReader = ZimFileReader(
zimFile, zimFile,
JNIKiwixReader(zimFile.canonicalPath), Archive(zimFile.canonicalPath),
NightModeConfig(SharedPreferenceUtil(context), context) NightModeConfig(SharedPreferenceUtil(context), context)
) )
zimFileReader.getRandomArticleUrl()?.let { zimFileReader.getRandomArticleUrl()?.let {
val mimeType = zimFileReader.readContentAndMimeType(it) val mimeType = zimFileReader.getMimeTypeFromUrl(it)
if (mimeType.contains("^([^ ]+).*$") || mimeType.contains(";")) { if (mimeType?.contains("^([^ ]+).*$") == true || mimeType?.contains(";") == true) {
Assert.fail( Assert.fail(
"Unable to get mime type from zim file. File = " + "Unable to get mime type from zim file. File = " +
" $zimFile and url of article = $it" " $zimFile and url of article = $it"

View File

@ -19,26 +19,31 @@
package org.kiwix.kiwixmobile.webserver package org.kiwix.kiwixmobile.webserver
import android.util.Log import android.util.Log
import org.kiwix.kiwixlib.JNIKiwixException import org.kiwix.libkiwix.Book
import org.kiwix.kiwixlib.JNIKiwixServer import org.kiwix.libkiwix.JNIKiwixException
import org.kiwix.kiwixlib.Library import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Server
import org.kiwix.libzim.Archive
import javax.inject.Inject import javax.inject.Inject
private const val TAG = "KiwixServer" 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() { class Factory @Inject constructor() {
fun createKiwixServer(selectedBooksPath: ArrayList<String>): KiwixServer { fun createKiwixServer(selectedBooksPath: ArrayList<String>): KiwixServer {
val kiwixLibrary = Library() val kiwixLibrary = Library()
selectedBooksPath.forEach { path -> selectedBooksPath.forEach { path ->
try { try {
kiwixLibrary.addBook(path) val book = Book().apply {
update(Archive(path))
}
kiwixLibrary.addBook(book)
} catch (e: JNIKiwixException) { } catch (e: JNIKiwixException) {
Log.v(TAG, "Couldn't add book with path:{ $path }") Log.v(TAG, "Couldn't add book with path:{ $path }")
} }
} }
return KiwixServer(JNIKiwixServer(kiwixLibrary)) return KiwixServer(Server(kiwixLibrary))
} }
} }

View File

@ -60,12 +60,16 @@ dependencies {
implementation(Libs.threetenabp) implementation(Libs.threetenabp)
// Get kiwixlib online if it is not populated locally // Get kiwixlib online if it is not populated locally
if (!shouldUseLocalVersion()) { implementation("com.getkeepsafe.relinker:relinker:1.4.5")
api(Libs.kiwixlib) api(fileTree(mapOf("include" to "*.aar", "dir" to "libs")))
} else { implementation(files("/home/hp-pc03/Desktop/lib-release.aar"))
implementation("com.getkeepsafe.relinker:relinker:1.4.5") // if (!shouldUseLocalVersion()) {
api(fileTree(mapOf("include" to "*.aar", "dir" to "libs"))) // 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 // Document File
implementation(Libs.select_folder_document_file) implementation(Libs.select_folder_document_file)

View File

@ -19,7 +19,7 @@ package org.kiwix.kiwixmobile.core
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import org.kiwix.kiwixlib.JNIKiwix import org.kiwix.libkiwix.JNIKiwix
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@ -27,7 +27,7 @@ import javax.inject.Inject
internal class JNIInitialiser @Inject constructor(context: Context, jniKiwix: JNIKiwix) { internal class JNIInitialiser @Inject constructor(context: Context, jniKiwix: JNIKiwix) {
init { init {
loadICUData(context)?.let(jniKiwix::setDataDirectory) // loadICUData(context)?.let(jniKiwix::setDataDirectory)
} }
private fun loadICUData(context: Context): String? { private fun loadICUData(context: Context): String? {

View File

@ -20,20 +20,11 @@ package org.kiwix.kiwixmobile.core.di.modules
import android.content.Context import android.content.Context
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import org.kiwix.kiwixlib.JNIKiwix import org.kiwix.libkiwix.JNIKiwix
import org.kiwix.kiwixlib.JNIKiwixSearcher
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
class JNIModule { class JNIModule {
@Provides @Singleton @Provides @Singleton
fun providesJNIKiwix(context: Context): JNIKiwix = JNIKiwix(context) fun providesJNIKiwix(context: Context): JNIKiwix = JNIKiwix(context)
@Provides @Singleton fun providesJNIKiwixSearcher(): JNIKiwixSearcher? {
return try {
JNIKiwixSearcher()
} catch (ignore: UnsatisfiedLinkError) {
null
}
}
} }

View File

@ -21,15 +21,11 @@ import android.annotation.SuppressLint
import android.content.res.AssetFileDescriptor import android.content.res.AssetFileDescriptor
import android.net.Uri import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Base64
import android.util.Log import android.util.Log
import androidx.core.net.toUri import androidx.core.net.toUri
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.schedulers.Schedulers 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.CoreApp
import org.kiwix.kiwixmobile.core.NightModeConfig import org.kiwix.kiwixmobile.core.NightModeConfig
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book 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.reader.ZimFileReader.Companion.CONTENT_PREFIX
import org.kiwix.kiwixmobile.core.search.SearchSuggestion import org.kiwix.kiwixmobile.core.search.SearchSuggestion
import org.kiwix.kiwixmobile.core.utils.files.FileUtils 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.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.IOException import java.io.IOException
@ -51,8 +54,9 @@ private const val TAG = "ZimFileReader"
class ZimFileReader constructor( class ZimFileReader constructor(
val zimFile: File, val zimFile: File,
private val jniKiwixReader: JNIKiwixReader = JNIKiwixReader(zimFile.canonicalPath), private val jniKiwixReader: Archive = Archive(zimFile.canonicalPath),
private val nightModeConfig: NightModeConfig private val nightModeConfig: NightModeConfig,
private val suggestionSearcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader)
) { ) {
interface Factory { interface Factory {
fun create(file: File): ZimFileReader? fun create(file: File): ZimFileReader?
@ -61,7 +65,9 @@ class ZimFileReader constructor(
Factory { Factory {
override fun create(file: File) = override fun create(file: File) =
try { try {
ZimFileReader(file, nightModeConfig = nightModeConfig) ZimFileReader(file, nightModeConfig = nightModeConfig).also {
Log.e(TAG, "create: ${file.path}")
}
} catch (ignore: JNIKiwixException) { } catch (ignore: JNIKiwixException) {
null 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 * 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. * (complete, nopic, novid, etc) may return the same title.
*/ */
val title: String get() = jniKiwixReader.title ?: "No Title Found" val title: String
val mainPage: String get() = jniKiwixReader.mainPage get() =
val id: String get() = jniKiwixReader.id try {
val fileSize: Int get() = jniKiwixReader.fileSize if (jniKiwixReader.mainEntry.isRedirect)
val creator: String get() = jniKiwixReader.creator jniKiwixReader.mainEntry.getItem(true).title
val publisher: String get() = jniKiwixReader.publisher else
val name: String get() = jniKiwixReader.name?.takeIf(String::isNotEmpty) ?: id jniKiwixReader.mainEntry.title
val date: String get() = jniKiwixReader.date } catch (entryNotFound: EntryNotFoundException) {
val description: String get() = jniKiwixReader.description "No Title Found"
val favicon: String? get() = jniKiwixReader.favicon }
val language: String get() = jniKiwixReader.language 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")}" val tags: String get() = "${getContent("M/Tags")}"
private val mediaCount: Int? private val mediaCount: Int?
get() = try { get() = try {
@ -97,23 +128,28 @@ class ZimFileReader constructor(
null null
} }
fun searchSuggestions(prefix: String, count: Int) = fun searchSuggestions(prefix: String): SuggestionSearch =
jniKiwixReader.searchSuggestions(prefix, count) suggestionSearcher.suggest(prefix)
fun getNextSuggestion(): SearchSuggestion? { fun getNextSuggestion(suggestionSearch: SuggestionSearch?): SearchSuggestion? {
val title = JNIKiwixString() val suggestionIterator =
val url = JNIKiwixString() suggestionSearch?.getResults(0, suggestionSearch.estimatedMatches.toInt())
if (suggestionIterator != null) {
return if (jniKiwixReader.getNextSuggestion(title, url)) while (suggestionIterator.hasNext()) {
SearchSuggestion(title.value, url.value) val suggestionItem = suggestionIterator.next()
else null return SearchSuggestion(suggestionItem.title, suggestionItem.path)
}
}
return null
} }
fun getPageUrlFrom(title: String): String? = fun getPageUrlFrom(title: String): String? =
valueOfJniStringAfter { jniKiwixReader.getPageUrlFromTitle(title, it) } if (jniKiwixReader.hasEntryByTitle(title))
jniKiwixReader.getEntryByTitle(title).path
else
null
fun getRandomArticleUrl(): String? = fun getRandomArticleUrl(): String? = jniKiwixReader.randomEntry.path
valueOfJniStringAfter(jniKiwixReader::getRandomPage)
fun load(uri: String): InputStream? { fun load(uri: String): InputStream? {
val extension = uri.substringAfterLast(".") val extension = uri.substringAfterLast(".")
@ -127,8 +163,8 @@ class ZimFileReader constructor(
return loadContent(uri) return loadContent(uri)
} }
fun readContentAndMimeType(uri: String): String = getContentAndMimeType(uri) fun getMimeTypeFromUrl(uri: String): String? = getItem(uri)?.mimetype
.second.truncateMimeType.also { ?.truncateMimeType.also {
Log.d(TAG, "getting mimetype for $uri = $it") Log.d(TAG, "getting mimetype for $uri = $it")
} }
@ -141,9 +177,20 @@ class ZimFileReader constructor(
} }
private fun toRedirect(url: String) = private fun toRedirect(url: String) =
"$CONTENT_PREFIX${ "$CONTENT_PREFIX${getActualUrl(url)}".toUri()
jniKiwixReader.checkUrl(url.toUri().filePath).replaceWithEncodedString
}".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) = private fun loadContent(uri: String) =
try { try {
@ -154,14 +201,19 @@ class ZimFileReader constructor(
} }
private fun loadAsset(uri: String): InputStream? { 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()) { if (infoPair == null || !File(infoPair.filename).exists()) {
return loadAssetFromCache(uri) return loadAssetFromCache(uri)
} }
return AssetFileDescriptor( return AssetFileDescriptor(
infoPair.parcelFileDescriptor, infoPair.parcelFileDescriptor,
infoPair.offset, infoPair.offset,
jniKiwixReader.getArticleSize(uri.filePath) article.size
).createInputStream() ).createInputStream()
} }
@ -170,11 +222,11 @@ class ZimFileReader constructor(
return File( return File(
FileUtils.getFileCacheDir(CoreApp.instance), FileUtils.getFileCacheDir(CoreApp.instance),
uri.substringAfterLast("/") uri.substringAfterLast("/")
).apply { writeBytes(getContent(uri)) } ).apply { getContent(uri)?.let(::writeBytes) }
.inputStream() .inputStream()
} }
private fun getContent(url: String) = getContentAndMimeType(url).let { (content, _) -> content } private fun getContent(url: String) = getItem(url)?.data?.data
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private fun streamZimContentToPipe(uri: String, outputStream: OutputStream) { private fun streamZimContentToPipe(uri: String, outputStream: OutputStream) {
@ -184,11 +236,11 @@ class ZimFileReader constructor(
if (uri.endsWith(UNINITIALISER_ADDRESS)) { if (uri.endsWith(UNINITIALISER_ADDRESS)) {
it.write(UNINITIALISE_HTML.toByteArray()) it.write(UNINITIALISE_HTML.toByteArray())
} else { } else {
getContentAndMimeType(uri).let { (content: ByteArray, mimeType: String) -> getItem(uri)?.let { item ->
if ("text/css" == mimeType && nightModeConfig.isNightModeActive()) { if ("text/css" == item.mimetype && nightModeConfig.isNightModeActive()) {
it.write(INVERT_IMAGES_VIDEO.toByteArray()) it.write(INVERT_IMAGES_VIDEO.toByteArray())
} }
it.write(content) it.write(item.data.data)
} }
} }
} }
@ -200,21 +252,15 @@ class ZimFileReader constructor(
.subscribe({ }, Throwable::printStackTrace) .subscribe({ }, Throwable::printStackTrace)
} }
private fun getContentAndMimeType(uri: String) = with(JNIKiwixString()) { private fun getItem(url: String): Item? =
getContent(url = JNIKiwixString(uri.filePath), mime = this) to value if (jniKiwixReader.hasEntryByPath(getActualUrl(url))) {
} jniKiwixReader.getEntryByPath(getActualUrl(url)).getItem(true).also {
Log.e(TAG, "getItem: $url")
private fun getContent( }
url: JNIKiwixString = JNIKiwixString(), } else {
jniKiwixString: JNIKiwixString = JNIKiwixString(), Log.e(TAG, "getItem else: $url")
mime: JNIKiwixString = JNIKiwixString(), null
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
@Suppress("ExplicitThis") // this@ZimFileReader.name is required @Suppress("ExplicitThis") // this@ZimFileReader.name is required
fun toBook() = Book().apply { fun toBook() = Book().apply {

View File

@ -50,7 +50,7 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F
fun load(url: String, requestHeaders: Map<String, String>): WebResourceResponse { fun load(url: String, requestHeaders: Map<String, String>): WebResourceResponse {
val data = zimFileReader?.load(url) val data = zimFileReader?.load(url)
return WebResourceResponse( return WebResourceResponse(
zimFileReader?.readContentAndMimeType(url), zimFileReader?.getMimeTypeFromUrl(url),
Charsets.UTF_8.name(), Charsets.UTF_8.name(),
data data
) )
@ -80,7 +80,7 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F
val zimFileTitle get() = zimFileReader?.title val zimFileTitle get() = zimFileReader?.title
val mainPage get() = zimFileReader?.mainPage val mainPage get() = zimFileReader?.mainPage
val id get() = zimFileReader?.id val id get() = zimFileReader?.id
val fileSize get() = zimFileReader?.fileSize ?: 0 val fileSize get() = zimFileReader?.fileSize ?: 0L
val creator get() = zimFileReader?.creator val creator get() = zimFileReader?.creator
val publisher get() = zimFileReader?.publisher val publisher get() = zimFileReader?.publisher
val name get() = zimFileReader?.name val name get() = zimFileReader?.name

View File

@ -24,6 +24,7 @@ import kotlinx.coroutines.yield
import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.ZimSearchResultListItem import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.ZimSearchResultListItem
import org.kiwix.libzim.SuggestionSearch
import javax.inject.Inject import javax.inject.Inject
interface SearchResultGenerator { interface SearchResultGenerator {
@ -46,13 +47,16 @@ class ZimSearchResultGenerator @Inject constructor() : SearchResultGenerator {
reader: ZimFileReader? reader: ZimFileReader?
) = ) =
reader.also { yield() } reader.also { yield() }
?.searchSuggestions(searchTerm, 200) ?.searchSuggestions(searchTerm)
.also { yield() } .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() yield()
reader?.getNextSuggestion() reader?.getNextSuggestion(suggestionSearch)
?.let { ZimSearchResultListItem(it.title) } ?.let { ZimSearchResultListItem(it.title) }
} }
.distinct() .distinct()

View File

@ -48,14 +48,14 @@ internal class ZimSearchResultGeneratorTest {
val validTitle = "title" val validTitle = "title"
val searchTerm = " " val searchTerm = " "
val item = mockk<SearchSuggestion>() val item = mockk<SearchSuggestion>()
every { zimFileReader.searchSuggestions(" ", 200) } returns true every { zimFileReader.searchSuggestions(" ") } returns true
every { zimFileReader.getNextSuggestion() } returnsMany listOf(item, item, null) every { zimFileReader.getNextSuggestion(suggestionSearch) } returnsMany listOf(item, item, null)
every { item.title } returns validTitle every { item.title } returns validTitle
runBlocking { runBlocking {
assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader)) assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader))
.isEqualTo(listOf(ZimSearchResultListItem(validTitle))) .isEqualTo(listOf(ZimSearchResultListItem(validTitle)))
verify { verify {
zimFileReader.searchSuggestions(searchTerm, 200) zimFileReader.searchSuggestions(searchTerm)
} }
} }
} }