From 99614ed88fb2e13544d8ea99e40e32ece242ffe4 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Thu, 26 Oct 2023 18:33:15 +0530 Subject: [PATCH] Introducing reading zim file content through `fileDescriptor` instead of creating a file to avoid using storage twice for same zim file. --- .../core/main/CoreReaderFragment.kt | 27 ++++++++++---- .../page/bookmark/adapter/BookmarkItem.kt | 2 +- .../page/history/adapter/HistoryListItem.kt | 2 +- .../core/page/notes/adapter/NoteListItem.kt | 2 +- .../kiwixmobile/core/reader/ZimFileReader.kt | 19 +++++++++- .../core/reader/ZimReaderContainer.kt | 7 ++++ .../core/webserver/ZimHostFragment.kt | 15 +++++--- .../custom/main/CustomFileValidator.kt | 37 +++++-------------- .../custom/main/CustomReaderFragment.kt | 8 +++- 9 files changed, 74 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt index a579544d0..363063a36 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt @@ -154,6 +154,7 @@ import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog import org.kiwix.kiwixmobile.core.utils.files.FileUtils.deleteCachedFiles import org.kiwix.kiwixmobile.core.utils.files.FileUtils.readFile import java.io.File +import java.io.FileDescriptor import java.io.IOException import java.text.SimpleDateFormat import java.util.Date @@ -1386,13 +1387,20 @@ abstract class CoreReaderFragment : externalLinkOpener?.openExternalUrl(intent) } - protected fun openZimFile(file: File, isCustomApp: Boolean = false) { + protected fun openZimFile( + file: File?, + isCustomApp: Boolean = false, + fileDescriptor: FileDescriptor? = null + ) { if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || isCustomApp) { - if (file.isFileExist()) { - openAndSetInContainer(file) + if (file?.isFileExist() == true) { + openAndSetInContainer(file = file) + updateTitle() + } else if (fileDescriptor != null) { + openAndSetInContainer(fileDescriptor = fileDescriptor) updateTitle() } else { - Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + file.absolutePath) + Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + file?.absolutePath) requireActivity().toast(R.string.error_file_not_found, Toast.LENGTH_LONG) } } else { @@ -1419,16 +1427,21 @@ abstract class CoreReaderFragment : ) } - private fun openAndSetInContainer(file: File) { + private fun openAndSetInContainer(file: File? = null, fileDescriptor: FileDescriptor? = null) { try { - if (isNotPreviouslyOpenZim(file.canonicalPath)) { + if (isNotPreviouslyOpenZim(file?.canonicalPath)) { webViewList.clear() } } catch (e: IOException) { e.printStackTrace() } zimReaderContainer?.let { zimReaderContainer -> - zimReaderContainer.setZimFile(file) + if (fileDescriptor != null) { + zimReaderContainer.setZimFileDescriptor(fileDescriptor) + } else { + zimReaderContainer.setZimFile(file) + } + val zimFileReader = zimReaderContainer.zimFileReader zimFileReader?.let { zimFileReader -> // uninitialized the service worker to fix https://github.com/kiwix/kiwix-android/issues/2561 diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt index 0f5401103..552028020 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt @@ -51,7 +51,7 @@ data class BookmarkItem( ) : this( zimId = zimFileReader.id, zimName = zimFileReader.name, - zimFilePath = zimFileReader.zimFile.canonicalPath, + zimFilePath = zimFileReader.zimFile?.canonicalPath, bookmarkUrl = url, title = title, favicon = zimFileReader.favicon diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt index eee4fca52..8556e4086 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt @@ -48,7 +48,7 @@ sealed class HistoryListItem : PageRelated { ) : this( zimId = zimFileReader.id, zimName = zimFileReader.name, - zimFilePath = zimFileReader.zimFile.canonicalPath, + zimFilePath = zimFileReader.zimFile?.canonicalPath ?: "", favicon = zimFileReader.favicon, historyUrl = url, title = title, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt index 4f40d24cd..1741fab2f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt @@ -35,7 +35,7 @@ data class NoteListItem( ) : this( zimId = zimFileReader.id, title = title, - zimFilePath = zimFileReader.zimFile.canonicalPath, + zimFilePath = zimFileReader.zimFile?.canonicalPath, zimUrl = url, favicon = zimFileReader.favicon, noteFilePath = noteFilePath 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 b8553d56e..f536a7aac 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 @@ -40,6 +40,7 @@ import org.kiwix.libzim.Item import org.kiwix.libzim.SuggestionSearch import org.kiwix.libzim.SuggestionSearcher import java.io.File +import java.io.FileDescriptor import java.io.FileInputStream import java.io.IOException import java.io.InputStream @@ -52,13 +53,14 @@ import javax.inject.Inject private const val TAG = "ZimFileReader" class ZimFileReader constructor( - val zimFile: File, + val zimFile: File?, private val jniKiwixReader: Archive, private val nightModeConfig: NightModeConfig, private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader) ) { interface Factory { fun create(file: File): ZimFileReader? + fun create(fileDescriptor: FileDescriptor): ZimFileReader? class Impl @Inject constructor(private val nightModeConfig: NightModeConfig) : Factory { @@ -76,6 +78,21 @@ class ZimFileReader constructor( } catch (ignore: Exception) { // for handing the error, if any zim file is corrupted null } + + override fun create(fileDescriptor: FileDescriptor): ZimFileReader? = + try { + ZimFileReader( + null, + nightModeConfig = nightModeConfig, + jniKiwixReader = Archive(fileDescriptor) + ).also { + Log.e(TAG, "create: with fileDescriptor") + } + } catch (ignore: JNIKiwixException) { + null + } catch (ignore: Exception) { // for handing the error, if any zim file is corrupted + null + } } } 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 5c0218228..c354df487 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 @@ -21,6 +21,7 @@ import android.webkit.WebResourceResponse import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory import java.io.File +import java.io.FileDescriptor import java.net.HttpURLConnection import javax.inject.Inject import javax.inject.Singleton @@ -42,6 +43,12 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F else null } + fun setZimFileDescriptor(fileDescriptor: FileDescriptor) { + zimFileReader = + if (fileDescriptor.valid()) zimFileReaderFactory.create(fileDescriptor) + else null + } + fun getPageUrlFromTitle(title: String) = zimFileReader?.getPageUrlFrom(title) fun getRandomArticleUrl() = zimFileReader?.getRandomArticleUrl() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt index d9f0052cc..c3bdaf0f3 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt @@ -487,13 +487,16 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { if (it is BookOnDisk) { zimReaderFactory.create(it.file)?.let { zimFileReader -> val booksOnDiskListItem = - (BookOnDisk(zimFileReader.zimFile, zimFileReader) as BooksOnDiskListItem) - .apply { - isSelected = true - } - updatedBooksList.add(booksOnDiskListItem).also { - zimFileReader.dispose() + zimFileReader.zimFile?.let { file -> + (BookOnDisk(file, zimFileReader) as BooksOnDiskListItem) + .apply { + isSelected = true + } + } + if (booksOnDiskListItem != null) { + updatedBooksList.add(booksOnDiskListItem) } + zimFileReader.dispose() } } else { updatedBooksList.add(it) diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt index 73cad95ca..0cf15ed0e 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt @@ -22,13 +22,12 @@ import android.content.Context import android.content.pm.PackageManager import android.util.Log import androidx.core.content.ContextCompat -import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.custom.BuildConfig import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile import org.kiwix.kiwixmobile.custom.main.ValidationState.HasNothing import java.io.File -import java.io.FileOutputStream +import java.io.FileDescriptor import java.io.IOException import javax.inject.Inject @@ -44,10 +43,10 @@ class CustomFileValidator @Inject constructor(private val context: Context) { private fun detectInstallationState( obbFiles: List = obbFiles(), zimFiles: List = zimFiles(), - assetFile: File? = getFileFromPlayAssetDelivery() + assetFileDescriptor: FileDescriptor? = getFileFromPlayAssetDelivery() ): ValidationState { return when { - assetFile != null -> HasFile(assetFile) + assetFileDescriptor != null -> HasFile(null, assetFileDescriptor) obbFiles.isNotEmpty() && zimFiles().isNotEmpty() -> HasBothFiles(obbFiles[0], zimFiles[0]) obbFiles.isNotEmpty() -> HasFile(obbFiles[0]) zimFiles.isNotEmpty() -> HasFile(zimFiles[0]) @@ -56,30 +55,12 @@ class CustomFileValidator @Inject constructor(private val context: Context) { } @Suppress("NestedBlockDepth", "MagicNumber") - private fun getFileFromPlayAssetDelivery(): File? { - var zimFile: File? = null + private fun getFileFromPlayAssetDelivery(): FileDescriptor? { try { val context = context.createPackageContext(context.packageName, 0) val assetManager = context.assets - val inputStream = assetManager.open(BuildConfig.PLAY_ASSET_FILE) - val filePath = ContextCompat.getExternalFilesDirs(context, null)[0] - zimFile = File(filePath, BuildConfig.PLAY_ASSET_FILE) - // Write zim file data if file does not exist or corrupted - if (!zimFile.isFileExist() || zimFile.length() == 0L) { - // Delete previously corrupted file - if (zimFile.isFileExist()) zimFile.delete() - zimFile.createNewFile() - FileOutputStream(zimFile).use { outputSteam -> - inputStream.use { inputStream -> - val buffer = ByteArray(1024) - var length: Int - while (inputStream.read(buffer).also { length = it } > 0) { - outputSteam.write(buffer, 0, length) - } - outputSteam.flush() - } - } - } + val inputStream = assetManager.openFd(BuildConfig.PLAY_ASSET_FILE) + return inputStream.fileDescriptor } catch (packageNameNotFoundException: PackageManager.NameNotFoundException) { Log.w( "ASSET_PACKAGE_DELIVERY", @@ -88,7 +69,7 @@ class CustomFileValidator @Inject constructor(private val context: Context) { } catch (ioException: IOException) { Log.w("ASSET_PACKAGE_DELIVERY", "Unable to copy the content of asset $ioException") } - return zimFile + return null } private fun obbFiles() = scanDirs(ContextCompat.getObbDirs(context), "obb") @@ -124,6 +105,8 @@ class CustomFileValidator @Inject constructor(private val context: Context) { sealed class ValidationState { data class HasBothFiles(val obbFile: File, val zimFile: File) : ValidationState() - data class HasFile(val file: File) : ValidationState() + data class HasFile(val file: File?, val fileDescriptor: FileDescriptor? = null) : + ValidationState() + object HasNothing : ValidationState() } diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt index 090bac52d..f9ba5d208 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt @@ -143,7 +143,13 @@ class CustomReaderFragment : CoreReaderFragment() { customFileValidator.validate( onFilesFound = { when (it) { - is ValidationState.HasFile -> openZimFile(it.file, true) + is ValidationState.HasFile -> { + if (it.fileDescriptor != null) { + openZimFile(null, true, it.fileDescriptor) + } else { + openZimFile(it.file, true) + } + } is ValidationState.HasBothFiles -> { it.zimFile.delete() openZimFile(it.obbFile, true)