Merge pull request #1980 from kiwix/macgills/feature/1659-autoload-next-video

#1659 Autoload next video - replace content provider with WebResource…
This commit is contained in:
Seán Mac Gillicuddy 2020-04-22 16:58:28 +01:00 committed by GitHub
commit d766ec7e90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 162 additions and 260 deletions

View File

@ -364,7 +364,9 @@
<inspection_tool class="ReplaceSizeCheckWithIsNotEmpty" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ReplaceSizeZeroCheckWithIsEmpty" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ReplaceToStringWithStringTemplate" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ReplaceToStringWithStringTemplate" enabled="true" level="ERROR" enabled_by_default="true">
<scope name="Tests" level="INFORMATION" enabled="true" />
</inspection_tool>
<inspection_tool class="ReplaceToWithInfixForm" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ReplaceWithEnumMap" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ReplaceWithOperatorAssignment" enabled="true" level="ERROR" enabled_by_default="true" />

View File

@ -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 =

View File

@ -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
}

View File

@ -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"

View File

@ -4,6 +4,7 @@
<Whitelist>
<ID>EmptyFunctionBlock:BooksOnDiskViewHolder.kt$BookOnDiskViewHolder.BookViewHolder${ }</ID>
<ID>EmptyFunctionBlock:FetchDownloadMonitor.kt$FetchDownloadMonitor.&lt;no name provided&gt;${}</ID>
<ID>ForbiddenComment:JNIInitialiser.kt$JNIInitialiser$// TODO: Consider surfacing to user</ID>
<ID>LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList&lt;KiwixWebView&gt;, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )</ID>
<ID>MagicNumber:ArticleCount.kt$ArticleCount$1000.0</ID>
<ID>MagicNumber:ArticleCount.kt$ArticleCount$3</ID>
@ -12,6 +13,7 @@
<ID>MagicNumber:DownloaderModule.kt$DownloaderModule$5</ID>
<ID>MagicNumber:FetchDownloadRequester.kt$10</ID>
<ID>MagicNumber:FileUtils.kt$FileUtils$3</ID>
<ID>MagicNumber:JNIInitialiser.kt$JNIInitialiser$1024</ID>
<ID>MagicNumber:KiloByte.kt$KiloByte$1024.0</ID>
<ID>MagicNumber:MainMenu.kt$MainMenu$99</ID>
<ID>MagicNumber:SearchResultGenerator.kt$ZimSearchResultGenerator$200</ID>
@ -20,6 +22,7 @@
<ID>MagicNumber:Seconds.kt$Seconds$60.0</ID>
<ID>NestedBlockDepth:FileUtils.kt$FileUtils$deleteZimFile</ID>
<ID>NestedBlockDepth:ImageUtils.kt$ImageUtils$getBitmapFromView</ID>
<ID>NestedBlockDepth:JNIInitialiser.kt$JNIInitialiser$loadICUData</ID>
<ID>NestedBlockDepth:StorageDeviceUtils.kt$StorageDeviceUtils$canWrite</ID>
<ID>PackageNaming:ArticleCount.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view</ID>
<ID>PackageNaming:BookOnDiskDelegate.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter</ID>
@ -36,6 +39,7 @@
<ID>ReturnCount:FileUtils.kt$FileUtils$@JvmStatic fun hasPart(file: File): Boolean</ID>
<ID>ReturnCount:FileUtils.kt$FileUtils$@Synchronized private fun deleteZimFileParts(path: String): Boolean</ID>
<ID>ReturnCount:ImageUtils.kt$ImageUtils$private fun getBitmapFromView(width: Int, height: Int, viewToDrawFrom: View): Bitmap?</ID>
<ID>TooGenericExceptionCaught:JNIInitialiser.kt$JNIInitialiser$e: Exception</ID>
<ID>TooGenericExceptionThrown:AbstractContentProvider.kt$AbstractContentProvider$throw RuntimeException("Operation not supported")</ID>
<ID>TooGenericExceptionThrown:AdapterDelegateManager.kt$AdapterDelegateManager$throw RuntimeException("No delegate registered for $item")</ID>
<ID>TooGenericExceptionThrown:Bytes.kt$Bytes$throw RuntimeException("impossible value $size")</ID>
@ -51,7 +55,6 @@
<ID>TooManyFunctions:NewBookDao.kt$NewBookDao$NewBookDao</ID>
<ID>TooManyFunctions:Repository.kt$Repository$Repository</ID>
<ID>TooManyFunctions:ZimFileReader.kt$ZimFileReader$ZimFileReader</ID>
<ID>TooManyFunctions:ZimReaderContainer.kt$ZimReaderContainer$ZimReaderContainer</ID>
<ID>TopLevelPropertyNaming:Bytes.kt$const val Eb = Pb * 1024</ID>
<ID>TopLevelPropertyNaming:Bytes.kt$const val Gb = Mb * 1024</ID>
<ID>TopLevelPropertyNaming:Bytes.kt$const val Kb = 1 * 1024L</ID>

View File

@ -29,11 +29,6 @@
<activity android:name=".bookmark.BookmarksActivity" />
<provider
android:name=".reader.ZimContentProvider"
android:authorities="${applicationId}.zim.base"
android:exported="true" />
<activity
android:name=".error.ErrorActivity"
android:process=":error_activity" />

View File

@ -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;
}

View File

@ -0,0 +1,57 @@
/*
* Kiwix Android
* Copyright (c) 2020 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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
}
}
}

View File

@ -1,53 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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<String>?,
selection: String?,
selectionArgs: Array<String>?,
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<String>?
): Int {
throw RuntimeException("Operation not supported")
}
override fun delete(uri: Uri, where: String?, whereArgs: Array<String>?): Int {
throw RuntimeException("Operation not supported")
}
}

View File

@ -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)

View File

@ -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://")) {

View File

@ -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);
}
}
}

View File

@ -1,120 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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 <zimfile>.zim.idx or <zimfile>.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;
}
}

View File

@ -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)

View File

@ -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

View File

@ -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()
}