diff --git a/.idea/inspectionProfiles/kiwixAndroidInspections.xml b/.idea/inspectionProfiles/kiwixAndroidInspections.xml index 2f014f407..49dd2a8e3 100644 --- a/.idea/inspectionProfiles/kiwixAndroidInspections.xml +++ b/.idea/inspectionProfiles/kiwixAndroidInspections.xml @@ -364,7 +364,9 @@ - + + + diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.java b/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.java index dc874df72..7beefa3a1 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.java +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.java @@ -39,7 +39,6 @@ import org.junit.runner.RunWith; import org.kiwix.kiwixmobile.core.CoreApp; import org.kiwix.kiwixmobile.core.di.components.DaggerTestComponent; import org.kiwix.kiwixmobile.core.di.components.TestComponent; -import org.kiwix.kiwixmobile.core.reader.ZimContentProvider; import org.kiwix.kiwixmobile.main.KiwixMainActivity; import org.kiwix.kiwixmobile.testutils.TestUtils; import org.kiwix.kiwixmobile.utils.KiwixIdlingResource; @@ -92,8 +91,6 @@ public class NetworkTest { CoreApp.setCoreComponent(component); - ZimContentProvider zimContentProvider = new ZimContentProvider(); - CoreApp.getCoreComponent().inject(zimContentProvider); component.inject(this); InputStream library = NetworkTest.class.getClassLoader().getResourceAsStream("library.xml"); InputStream metalinks = diff --git a/build.gradle.kts b/build.gradle.kts index 29d4115f0..56327158c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,9 +6,6 @@ buildscript { dependencies { classpath(Libs.com_android_tools_build_gradle) classpath(Libs.kotlin_gradle_plugin) - classpath(Libs.ktlint_gradle) - classpath(Libs.jacoco_android) - classpath(Libs.detekt_gradle_plugin) // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index dab490388..f2a28b042 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -89,7 +89,7 @@ object Versions { const val core_ktx: String = "1.2.0" - const val kiwixlib: String = "9.1.0" + const val kiwixlib: String = "9.1.2" const val material: String = "1.1.0-beta02" // available: "1.1.0" diff --git a/core/detekt_baseline.xml b/core/detekt_baseline.xml index ee0f581dd..40be6d414 100644 --- a/core/detekt_baseline.xml +++ b/core/detekt_baseline.xml @@ -4,6 +4,7 @@ EmptyFunctionBlock:BooksOnDiskViewHolder.kt$BookOnDiskViewHolder.BookViewHolder${ } EmptyFunctionBlock:FetchDownloadMonitor.kt$FetchDownloadMonitor.<no name provided>${} + ForbiddenComment:JNIInitialiser.kt$JNIInitialiser$// TODO: Consider surfacing to user LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList<KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean ) MagicNumber:ArticleCount.kt$ArticleCount$1000.0 MagicNumber:ArticleCount.kt$ArticleCount$3 @@ -12,6 +13,7 @@ MagicNumber:DownloaderModule.kt$DownloaderModule$5 MagicNumber:FetchDownloadRequester.kt$10 MagicNumber:FileUtils.kt$FileUtils$3 + MagicNumber:JNIInitialiser.kt$JNIInitialiser$1024 MagicNumber:KiloByte.kt$KiloByte$1024.0 MagicNumber:MainMenu.kt$MainMenu$99 MagicNumber:SearchResultGenerator.kt$ZimSearchResultGenerator$200 @@ -20,6 +22,7 @@ MagicNumber:Seconds.kt$Seconds$60.0 NestedBlockDepth:FileUtils.kt$FileUtils$deleteZimFile NestedBlockDepth:ImageUtils.kt$ImageUtils$getBitmapFromView + NestedBlockDepth:JNIInitialiser.kt$JNIInitialiser$loadICUData NestedBlockDepth:StorageDeviceUtils.kt$StorageDeviceUtils$canWrite PackageNaming:ArticleCount.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view PackageNaming:BookOnDiskDelegate.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter @@ -36,6 +39,7 @@ ReturnCount:FileUtils.kt$FileUtils$@JvmStatic fun hasPart(file: File): Boolean ReturnCount:FileUtils.kt$FileUtils$@Synchronized private fun deleteZimFileParts(path: String): Boolean ReturnCount:ImageUtils.kt$ImageUtils$private fun getBitmapFromView(width: Int, height: Int, viewToDrawFrom: View): Bitmap? + TooGenericExceptionCaught:JNIInitialiser.kt$JNIInitialiser$e: Exception TooGenericExceptionThrown:AbstractContentProvider.kt$AbstractContentProvider$throw RuntimeException("Operation not supported") TooGenericExceptionThrown:AdapterDelegateManager.kt$AdapterDelegateManager$throw RuntimeException("No delegate registered for $item") TooGenericExceptionThrown:Bytes.kt$Bytes$throw RuntimeException("impossible value $size") @@ -51,7 +55,6 @@ TooManyFunctions:NewBookDao.kt$NewBookDao$NewBookDao TooManyFunctions:Repository.kt$Repository$Repository TooManyFunctions:ZimFileReader.kt$ZimFileReader$ZimFileReader - TooManyFunctions:ZimReaderContainer.kt$ZimReaderContainer$ZimReaderContainer TopLevelPropertyNaming:Bytes.kt$const val Eb = Pb * 1024 TopLevelPropertyNaming:Bytes.kt$const val Gb = Mb * 1024 TopLevelPropertyNaming:Bytes.kt$const val Kb = 1 * 1024L diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index 389603e6d..84aaeb472 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -29,11 +29,6 @@ - - diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.java b/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.java index 020f8c352..15c7473d5 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.java +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.java @@ -50,6 +50,14 @@ public abstract class CoreApp extends Application { @Inject KiwixDatabase kiwixDatabase; + /** + * The init of this class does the work of initializing, + * simply injecting it is all that there is to be done + */ + @SuppressWarnings("unused") + @Inject + JNIInitialiser jniInitialiser; + public static CoreApp getInstance() { return app; } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/JNIInitialiser.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/JNIInitialiser.kt new file mode 100644 index 000000000..c8e37c98f --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/JNIInitialiser.kt @@ -0,0 +1,57 @@ +/* + * Kiwix Android + * Copyright (c) 2020 Kiwix + * 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 + * (at your option) 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, see . + * + */ +package org.kiwix.kiwixmobile.core + +import android.content.Context +import android.util.Log +import org.kiwix.kiwixlib.JNIKiwix +import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX +import java.io.File +import java.io.FileOutputStream +import javax.inject.Inject + +internal class JNIInitialiser @Inject constructor(context: Context, jniKiwix: JNIKiwix) { + init { + loadICUData(context)?.let(jniKiwix::setDataDirectory) + } + + private fun loadICUData(context: Context): String? { + return try { + val icuDir = File(context.filesDir, "icu") + if (!icuDir.exists()) { + icuDir.mkdirs() + } + val icuFileNames = context.assets.list("icu") ?: emptyArray() + for (icuFileName in icuFileNames) { + val icuDataFile = File(icuDir, icuFileName) + if (!icuDataFile.exists()) { + FileOutputStream(icuDataFile).use { outputStream -> + context.assets.open("icu/$icuFileName").use { inputStream -> + inputStream.copyTo(outputStream, 1024) + } + } + } + } + icuDir.absolutePath + } catch (e: Exception) { + Log.w(TAG_KIWIX, "Error copying icu data file", e) + // TODO: Consider surfacing to user + null + } + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/AbstractContentProvider.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/AbstractContentProvider.kt deleted file mode 100644 index c7ea63549..000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/data/AbstractContentProvider.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * 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 - * (at your option) 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, see . - * - */ - -package org.kiwix.kiwixmobile.core.data - -import android.content.ContentProvider -import android.content.ContentValues -import android.database.Cursor -import android.net.Uri - -internal abstract class AbstractContentProvider : ContentProvider() { - override fun query( - url: Uri, - projection: Array?, - selection: String?, - selectionArgs: Array?, - sort: String? - ): Cursor? { - throw RuntimeException("Operation not supported") - } - - override fun insert(uri: Uri, initialValues: ContentValues?): Uri? { - throw RuntimeException("Operation not supported") - } - - override fun update( - uri: Uri, - values: ContentValues?, - where: String?, - whereArgs: Array? - ): Int { - throw RuntimeException("Operation not supported") - } - - override fun delete(uri: Uri, where: String?, whereArgs: Array?): Int { - throw RuntimeException("Operation not supported") - } -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index a804fc8b3..5b67faab1 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -47,7 +47,6 @@ import org.kiwix.kiwixmobile.core.error.ErrorActivity import org.kiwix.kiwixmobile.core.help.HelpActivity import org.kiwix.kiwixmobile.core.history.HistoryModule import org.kiwix.kiwixmobile.core.main.KiwixWebView -import org.kiwix.kiwixmobile.core.reader.ZimContentProvider import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.search.SearchActivity @@ -101,7 +100,6 @@ interface CoreComponent { fun notificationManager(): NotificationManager fun inject(application: CoreApp) - fun inject(zimContentProvider: ZimContentProvider) fun inject(kiwixWebView: KiwixWebView) fun inject(storageSelectDialog: StorageSelectDialog) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java index 95a8fcf27..fa8388e89 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java @@ -1391,7 +1391,7 @@ public abstract class CoreMainActivity extends BaseActivity @NotNull private String contentUrl(String articleUrl) { - return Uri.parse(ZimFileReader.CONTENT_URI + articleUrl).toString(); + return Uri.parse(ZimFileReader.CONTENT_PREFIX + articleUrl).toString(); } @NotNull @@ -1680,7 +1680,7 @@ public abstract class CoreMainActivity extends BaseActivity @Override public void webViewLongClick(final String url) { boolean handleEvent = false; - if (url.startsWith(ZimFileReader.CONTENT_URI.toString())) { + if (url.startsWith(ZimFileReader.CONTENT_PREFIX)) { // This is my web site, so do not override; let my WebView load the page handleEvent = true; } else if (url.startsWith("file://")) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreWebViewClient.java b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreWebViewClient.java index f00effd23..936cd0ca4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreWebViewClient.java +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreWebViewClient.java @@ -24,16 +24,19 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.webkit.MimeTypeMap; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; +import androidx.annotation.Nullable; import java.util.HashMap; import org.kiwix.kiwixmobile.core.CoreApp; import org.kiwix.kiwixmobile.core.R; -import org.kiwix.kiwixmobile.core.reader.ZimFileReader; import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer; import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil; import static org.kiwix.kiwixmobile.core.main.CoreMainActivity.HOME_URL; +import static org.kiwix.kiwixmobile.core.reader.ZimFileReader.CONTENT_PREFIX; +import static org.kiwix.kiwixmobile.core.reader.ZimFileReader.UI_URI; import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.EXTRA_EXTERNAL_LINK; import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_KIWIX; @@ -65,7 +68,7 @@ public abstract class CoreWebViewClient extends WebViewClient { view.loadUrl(zimReaderContainer.getRedirect(url)); return true; } - if (url.startsWith(ZimFileReader.CONTENT_URI.toString())) { + if (url.startsWith(CONTENT_PREFIX)) { return handleEpubAndPdf(url); } if (url.startsWith("file://")) { @@ -76,7 +79,7 @@ public abstract class CoreWebViewClient extends WebViewClient { // Allow javascript for HTML functions and code execution (EX: night mode) return true; } - if (url.startsWith(ZimFileReader.UI_URI.toString())) { + if (url.startsWith(UI_URI.toString())) { Log.e("KiwixWebViewClient", "UI Url " + url + " not supported."); //TODO: Document this code - what's a UI_URL? return true; @@ -137,4 +140,14 @@ public abstract class CoreWebViewClient extends WebViewClient { view.removeAllViews(); view.addView(home); } + + @Nullable + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + if (url.startsWith(CONTENT_PREFIX)) { + return zimReaderContainer.load(url); + } else { + return super.shouldInterceptRequest(view, url); + } + } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimContentProvider.java b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimContentProvider.java deleted file mode 100644 index 7dd4cfc9c..000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimContentProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * 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 - * (at your option) 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, see . - * - */ - -package org.kiwix.kiwixmobile.core.reader; - -import android.content.Context; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Log; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import javax.inject.Inject; -import org.kiwix.kiwixlib.JNIKiwix; -import org.kiwix.kiwixmobile.core.CoreApp; -import org.kiwix.kiwixmobile.core.data.AbstractContentProvider; - -import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_KIWIX; - -public class ZimContentProvider extends AbstractContentProvider { - - @Inject - public JNIKiwix jniKiwix; - @Inject - ZimReaderContainer zimReaderContainer; - - @Override - public boolean onCreate() { - CoreApp.getCoreComponent().inject(this); - setIcuDataDirectory(); - return true; - } - - @Override - public String getType(Uri uri) { - return zimReaderContainer.readMimeType(uri); - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) { - return zimReaderContainer.load(uri); - } - - private void setIcuDataDirectory() { - String icuDirPath = loadICUData(getContext()); - if (icuDirPath != null) { - Log.d(TAG_KIWIX, "Setting the ICU directory path to " + icuDirPath); - jniKiwix.setDataDirectory(icuDirPath); - } - } - - private String loadICUData(Context context) { - try { - File icuDir = new File(context.getFilesDir(), "icu"); - if (!icuDir.exists()) { - icuDir.mkdirs(); - } - String[] icuFileNames = context.getAssets().list("icu"); - for (int i = 0; i < icuFileNames.length; i++) { - String icuFileName = icuFileNames[i]; - File icuDataFile = new File(icuDir, icuFileName); - if (!icuDataFile.exists()) { - InputStream in = context.getAssets().open("icu/" + icuFileName); - OutputStream out = new FileOutputStream(icuDataFile); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - in.close(); - out.flush(); - out.close(); - } - } - return icuDir.getAbsolutePath(); - } catch (Exception e) { - Log.w(TAG_KIWIX, "Error copying icu data file", e); - //TODO: Consider surfacing to user - return null; - } - } - - private static String getFulltextIndexPath(String file) { - String[] names = { file, file }; - - /* File might be a ZIM chunk like foobar.zimaa */ - if (!names[0].substring(names[0].length() - 3).equals("zim")) { - names[0] = names[0].substring(0, names[0].length() - 2); - } - - /* Try to find a *.idx fulltext file/directory beside the ZIM - * file. Returns .zim.idx or .zimaa.idx. */ - for (String name : names) { - File f = new File(name + ".idx"); - if (f.exists() && f.isDirectory()) { - return f.getPath(); - } - } - - /* If no separate fulltext index file found then returns the ZIM - * file path itself (embedded fulltext index) */ - return file; - } -} 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 acb01b2ed..74ec0f29f 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 @@ -17,14 +17,13 @@ */ package org.kiwix.kiwixmobile.core.reader +import android.content.res.AssetFileDescriptor import android.net.Uri import android.os.ParcelFileDescriptor -import android.os.ParcelFileDescriptor.AutoCloseOutputStream -import android.os.ParcelFileDescriptor.dup import android.util.Log import android.webkit.MimeTypeMap import androidx.core.net.toUri -import io.reactivex.Single +import io.reactivex.Completable import io.reactivex.schedulers.Schedulers import org.kiwix.kiwixlib.JNIKiwixException import org.kiwix.kiwixlib.JNIKiwixInt @@ -34,14 +33,16 @@ import org.kiwix.kiwixlib.Pair import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.NightModeConfig import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book -import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Companion.CONTENT_URI +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 java.io.File -import java.io.FileDescriptor -import java.io.FileOutputStream +import java.io.FileInputStream import java.io.IOException -import java.io.RandomAccessFile +import java.io.InputStream +import java.io.OutputStream +import java.io.PipedInputStream +import java.io.PipedOutputStream import javax.inject.Inject private const val TAG = "ZimFileReader" @@ -80,7 +81,7 @@ class ZimFileReader constructor( val description: String get() = jniKiwixReader.description val favicon: String? get() = jniKiwixReader.favicon val language: String get() = jniKiwixReader.language - val tags: String get() = "${getContent(Uri.parse("M/Tags"))}" + val tags: String get() = "${getContent("M/Tags")}" private val mediaCount: Int? get() = try { jniKiwixReader.mediaCount @@ -112,8 +113,9 @@ class ZimFileReader constructor( fun getRandomArticleUrl(): String? = valueOfJniStringAfter(jniKiwixReader::getRandomPage) - fun load(uri: Uri): ParcelFileDescriptor { - if ("$uri".matches(VIDEO_REGEX)) { + fun load(uri: String): InputStream? { + val extension = uri.substringAfterLast(".") + if (videoExtensions.any { it == extension }) { try { return loadVideo(uri) } catch (ioException: IOException) { @@ -123,7 +125,7 @@ class ZimFileReader constructor( return loadContent(uri) } - fun readMimeType(uri: Uri) = "$uri".removeArguments().let { + fun readMimeType(uri: String) = uri.removeArguments().let { it.mimeType?.takeIf(String::isNotEmpty) ?: mimeTypeFromReader(it) }.also { Log.d(TAG, "getting mimetype for $uri = $it") } @@ -134,78 +136,73 @@ class ZimFileReader constructor( fun getRedirect(url: String) = "${toRedirect(url)}" fun isRedirect(url: String) = - url.startsWith("$CONTENT_URI") && url != getRedirect(url) + url.startsWith(CONTENT_PREFIX) && url != getRedirect(url) private fun toRedirect(url: String) = - "$CONTENT_URI${jniKiwixReader.checkUrl(url.toUri().filePath)}".toUri() + "$CONTENT_PREFIX${jniKiwixReader.checkUrl(url.toUri().filePath)}".toUri() - private fun loadContent(uri: Uri) = + private fun loadContent(uri: String) = try { - ParcelFileDescriptor.createPipe().also { - streamZimContentToPipe(uri, AutoCloseOutputStream(it[1])) - }[0] + val outputStream = PipedOutputStream() + PipedInputStream(outputStream).also { streamZimContentToPipe(uri, outputStream) } } catch (ioException: IOException) { throw IOException("Could not open pipe for $uri", ioException) } - private fun loadVideo(uri: Uri): ParcelFileDescriptor { + private fun loadVideo(uri: String): InputStream? { val infoPair = jniKiwixReader.getDirectAccessInformation(uri.filePath) if (infoPair == null || !File(infoPair.filename).exists()) { return loadVideoFromCache(uri) } - return dup(infoPair.fileDescriptor) + return AssetFileDescriptor( + infoPair.parcelFileDescriptor, + infoPair.offset, + jniKiwixReader.getArticleSize(uri.filePath) + ).createInputStream() } @Throws(IOException::class) - private fun loadVideoFromCache(uri: Uri): ParcelFileDescriptor { - val outputFile = File( + private fun loadVideoFromCache(uri: String): FileInputStream { + return File( FileUtils.getFileCacheDir(CoreApp.getInstance()), - "$uri".substringAfterLast("/") - ) - FileOutputStream(outputFile).use { it.write(getContent(uri)) } - return ParcelFileDescriptor.open(outputFile, ParcelFileDescriptor.MODE_READ_ONLY) + uri.substringAfterLast("/") + ).apply { writeBytes(getContent(uri)) } + .inputStream() } - private fun streamZimContentToPipe( - uri: Uri, - outputStream: AutoCloseOutputStream - ) { - Single.just(Unit) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .subscribe( - { - try { - outputStream.use { - val mime = JNIKiwixString() - val size = JNIKiwixInt() - val url = JNIKiwixString(uri.filePath.removeArguments()) - val content = getContent(url = url, mime = mime, size = size) - if ("text/css" == mime.value && nightModeConfig.isNightModeActive()) { - it.write(INVERT_IMAGES_VIDEO.toByteArray(Charsets.UTF_8)) - } - it.write(content) - Log.d( - TAG, - "reading ${url.value}(mime: ${mime.value}, size: ${size.value}) finished." - ) + private fun getContent(url: String) = getContentAndMimeType(url).let { (content, _) -> content } + + private fun streamZimContentToPipe(uri: String, outputStream: OutputStream) { + Completable.fromAction { + try { + outputStream.use { + getContentAndMimeType(uri).let { (content: ByteArray, mimeType: String) -> + if ("text/css" == mimeType && nightModeConfig.isNightModeActive()) { + it.write(INVERT_IMAGES_VIDEO.toByteArray(Charsets.UTF_8)) } - } catch (ioException: IOException) { - Log.e(TAG, "error writing pipe for $uri", ioException) + it.write(content) } - }, - Throwable::printStackTrace - ) + } + } catch (ioException: IOException) { + Log.e(TAG, "error writing pipe for $uri", ioException) + } + } + .subscribeOn(Schedulers.io()) + .subscribe({ }, Throwable::printStackTrace) } - private fun getContent(uri: Uri) = getContent(JNIKiwixString(uri.filePath.removeArguments())) + private fun getContentAndMimeType(uri: String) = with(JNIKiwixString()) { + getContent(url = JNIKiwixString(uri.filePath.removeArguments()), 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) + ) = 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 @@ -233,9 +230,11 @@ class ZimFileReader constructor( */ @JvmField val UI_URI: Uri? = Uri.parse("content://org.kiwix.ui/") + @JvmField - val CONTENT_URI: Uri? = - Uri.parse("content://${CoreApp.getInstance().packageName}.zim.base/") + val CONTENT_PREFIX = + Uri.parse("content://${CoreApp.getInstance().packageName}.zim.base/").toString() + private val INVERT_IMAGES_VIDEO = """ img, video, div[poster], div#header { @@ -251,18 +250,18 @@ class ZimFileReader constructor( filter: invert(0); } """.trimIndent() - private val VIDEO_REGEX = Regex("([^\\s]+(\\.(?i)(3gp|mp4|m4a|webm|mkv|ogg|ogv))\$)") + private val videoExtensions = listOf("3gp", "mp4", "m4a", "webm", "mkv", "ogg", "ogv") } } private fun String.removeArguments() = substringBefore("?") -private val Pair.fileDescriptor: FileDescriptor? - get() = RandomAccessFile(filename, "r").apply { seek(offset.toLong()) }.fd private val Uri.filePath: String get() = toString().filePath private val String.filePath: String - get() = substringAfter("$CONTENT_URI").substringBefore("#") + get() = substringAfter(CONTENT_PREFIX).substringBefore("#") private val String.mimeType: String? get() = MimeTypeMap.getSingleton().getMimeTypeFromExtension( MimeTypeMap.getFileExtensionFromUrl(this) ) +private val Pair.parcelFileDescriptor: ParcelFileDescriptor? + get() = ParcelFileDescriptor.open(File(filename), ParcelFileDescriptor.MODE_READ_ONLY) 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 242e08ab1..e26df34cd 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 @@ -17,7 +17,7 @@ */ package org.kiwix.kiwixmobile.core.reader -import android.net.Uri +import android.webkit.WebResourceResponse import org.kiwix.kiwixlib.JNIKiwixSearcher import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory import java.io.File @@ -48,10 +48,6 @@ class ZimReaderContainer @Inject constructor( else null } - fun readMimeType(uri: Uri) = zimFileReader?.readMimeType(uri) - - fun load(uri: Uri) = zimFileReader?.load(uri) - fun searchSuggestions(prefix: String, count: Int) = zimFileReader?.searchSuggestions(prefix, count) ?: false @@ -67,8 +63,15 @@ class ZimReaderContainer @Inject constructor( fun getNextResult() = jniKiwixSearcher?.nextResult?.let { SearchResult(it.title) } fun isRedirect(url: String): Boolean = zimFileReader?.isRedirect(url) == true fun getRedirect(url: String): String = zimFileReader?.getRedirect(url) ?: "" + fun load(url: String) = + WebResourceResponse( + zimFileReader?.readMimeType(url), + Charsets.UTF_8.name(), + zimFileReader?.load(url) + ) val zimFile get() = zimFileReader?.zimFile + val zimCanonicalPath get() = zimFileReader?.zimFile?.canonicalPath val zimFileTitle get() = zimFileReader?.title val mainPage get() = zimFileReader?.mainPage diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt index 8e1a95e04..4c5f4a949 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt @@ -18,6 +18,7 @@ package org.kiwix.kiwixmobile.core +import android.net.Uri import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk @@ -58,6 +59,8 @@ class StorageObserverTest { setScheduler(Schedulers.trampoline()) mockkStatic(CoreApp::class) every { CoreApp.getInstance().packageName } returns "pkg" + mockkStatic(Uri::class) + every { Uri.parse(any()).toString() } returns "pkg" zimFileReader = mockk() }