mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-09 23:40:26 -04:00
Introducing reading zim file content through fileDescriptor
instead of creating a file to avoid using storage twice for same zim file.
This commit is contained in:
parent
ae413559fe
commit
99614ed88f
@ -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.deleteCachedFiles
|
||||||
import org.kiwix.kiwixmobile.core.utils.files.FileUtils.readFile
|
import org.kiwix.kiwixmobile.core.utils.files.FileUtils.readFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileDescriptor
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@ -1386,13 +1387,20 @@ abstract class CoreReaderFragment :
|
|||||||
externalLinkOpener?.openExternalUrl(intent)
|
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 (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || isCustomApp) {
|
||||||
if (file.isFileExist()) {
|
if (file?.isFileExist() == true) {
|
||||||
openAndSetInContainer(file)
|
openAndSetInContainer(file = file)
|
||||||
|
updateTitle()
|
||||||
|
} else if (fileDescriptor != null) {
|
||||||
|
openAndSetInContainer(fileDescriptor = fileDescriptor)
|
||||||
updateTitle()
|
updateTitle()
|
||||||
} else {
|
} 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)
|
requireActivity().toast(R.string.error_file_not_found, Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1419,16 +1427,21 @@ abstract class CoreReaderFragment :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openAndSetInContainer(file: File) {
|
private fun openAndSetInContainer(file: File? = null, fileDescriptor: FileDescriptor? = null) {
|
||||||
try {
|
try {
|
||||||
if (isNotPreviouslyOpenZim(file.canonicalPath)) {
|
if (isNotPreviouslyOpenZim(file?.canonicalPath)) {
|
||||||
webViewList.clear()
|
webViewList.clear()
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
zimReaderContainer?.let { zimReaderContainer ->
|
zimReaderContainer?.let { zimReaderContainer ->
|
||||||
|
if (fileDescriptor != null) {
|
||||||
|
zimReaderContainer.setZimFileDescriptor(fileDescriptor)
|
||||||
|
} else {
|
||||||
zimReaderContainer.setZimFile(file)
|
zimReaderContainer.setZimFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
val zimFileReader = zimReaderContainer.zimFileReader
|
val zimFileReader = zimReaderContainer.zimFileReader
|
||||||
zimFileReader?.let { zimFileReader ->
|
zimFileReader?.let { zimFileReader ->
|
||||||
// uninitialized the service worker to fix https://github.com/kiwix/kiwix-android/issues/2561
|
// uninitialized the service worker to fix https://github.com/kiwix/kiwix-android/issues/2561
|
||||||
|
@ -51,7 +51,7 @@ data class BookmarkItem(
|
|||||||
) : this(
|
) : this(
|
||||||
zimId = zimFileReader.id,
|
zimId = zimFileReader.id,
|
||||||
zimName = zimFileReader.name,
|
zimName = zimFileReader.name,
|
||||||
zimFilePath = zimFileReader.zimFile.canonicalPath,
|
zimFilePath = zimFileReader.zimFile?.canonicalPath,
|
||||||
bookmarkUrl = url,
|
bookmarkUrl = url,
|
||||||
title = title,
|
title = title,
|
||||||
favicon = zimFileReader.favicon
|
favicon = zimFileReader.favicon
|
||||||
|
@ -48,7 +48,7 @@ sealed class HistoryListItem : PageRelated {
|
|||||||
) : this(
|
) : this(
|
||||||
zimId = zimFileReader.id,
|
zimId = zimFileReader.id,
|
||||||
zimName = zimFileReader.name,
|
zimName = zimFileReader.name,
|
||||||
zimFilePath = zimFileReader.zimFile.canonicalPath,
|
zimFilePath = zimFileReader.zimFile?.canonicalPath ?: "",
|
||||||
favicon = zimFileReader.favicon,
|
favicon = zimFileReader.favicon,
|
||||||
historyUrl = url,
|
historyUrl = url,
|
||||||
title = title,
|
title = title,
|
||||||
|
@ -35,7 +35,7 @@ data class NoteListItem(
|
|||||||
) : this(
|
) : this(
|
||||||
zimId = zimFileReader.id,
|
zimId = zimFileReader.id,
|
||||||
title = title,
|
title = title,
|
||||||
zimFilePath = zimFileReader.zimFile.canonicalPath,
|
zimFilePath = zimFileReader.zimFile?.canonicalPath,
|
||||||
zimUrl = url,
|
zimUrl = url,
|
||||||
favicon = zimFileReader.favicon,
|
favicon = zimFileReader.favicon,
|
||||||
noteFilePath = noteFilePath
|
noteFilePath = noteFilePath
|
||||||
|
@ -40,6 +40,7 @@ import org.kiwix.libzim.Item
|
|||||||
import org.kiwix.libzim.SuggestionSearch
|
import org.kiwix.libzim.SuggestionSearch
|
||||||
import org.kiwix.libzim.SuggestionSearcher
|
import org.kiwix.libzim.SuggestionSearcher
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileDescriptor
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -52,13 +53,14 @@ import javax.inject.Inject
|
|||||||
private const val TAG = "ZimFileReader"
|
private const val TAG = "ZimFileReader"
|
||||||
|
|
||||||
class ZimFileReader constructor(
|
class ZimFileReader constructor(
|
||||||
val zimFile: File,
|
val zimFile: File?,
|
||||||
private val jniKiwixReader: Archive,
|
private val jniKiwixReader: Archive,
|
||||||
private val nightModeConfig: NightModeConfig,
|
private val nightModeConfig: NightModeConfig,
|
||||||
private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader)
|
private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader)
|
||||||
) {
|
) {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(file: File): ZimFileReader?
|
fun create(file: File): ZimFileReader?
|
||||||
|
fun create(fileDescriptor: FileDescriptor): ZimFileReader?
|
||||||
|
|
||||||
class Impl @Inject constructor(private val nightModeConfig: NightModeConfig) :
|
class Impl @Inject constructor(private val nightModeConfig: NightModeConfig) :
|
||||||
Factory {
|
Factory {
|
||||||
@ -76,6 +78,21 @@ class ZimFileReader constructor(
|
|||||||
} catch (ignore: Exception) { // for handing the error, if any zim file is corrupted
|
} catch (ignore: Exception) { // for handing the error, if any zim file is corrupted
|
||||||
null
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import android.webkit.WebResourceResponse
|
|||||||
import org.kiwix.kiwixmobile.core.extensions.isFileExist
|
import org.kiwix.kiwixmobile.core.extensions.isFileExist
|
||||||
import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory
|
import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileDescriptor
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -42,6 +43,12 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F
|
|||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setZimFileDescriptor(fileDescriptor: FileDescriptor) {
|
||||||
|
zimFileReader =
|
||||||
|
if (fileDescriptor.valid()) zimFileReaderFactory.create(fileDescriptor)
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
|
||||||
fun getPageUrlFromTitle(title: String) = zimFileReader?.getPageUrlFrom(title)
|
fun getPageUrlFromTitle(title: String) = zimFileReader?.getPageUrlFrom(title)
|
||||||
|
|
||||||
fun getRandomArticleUrl() = zimFileReader?.getRandomArticleUrl()
|
fun getRandomArticleUrl() = zimFileReader?.getRandomArticleUrl()
|
||||||
|
@ -487,13 +487,16 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
|
|||||||
if (it is BookOnDisk) {
|
if (it is BookOnDisk) {
|
||||||
zimReaderFactory.create(it.file)?.let { zimFileReader ->
|
zimReaderFactory.create(it.file)?.let { zimFileReader ->
|
||||||
val booksOnDiskListItem =
|
val booksOnDiskListItem =
|
||||||
(BookOnDisk(zimFileReader.zimFile, zimFileReader) as BooksOnDiskListItem)
|
zimFileReader.zimFile?.let { file ->
|
||||||
|
(BookOnDisk(file, zimFileReader) as BooksOnDiskListItem)
|
||||||
.apply {
|
.apply {
|
||||||
isSelected = true
|
isSelected = true
|
||||||
}
|
}
|
||||||
updatedBooksList.add(booksOnDiskListItem).also {
|
|
||||||
zimFileReader.dispose()
|
|
||||||
}
|
}
|
||||||
|
if (booksOnDiskListItem != null) {
|
||||||
|
updatedBooksList.add(booksOnDiskListItem)
|
||||||
|
}
|
||||||
|
zimFileReader.dispose()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updatedBooksList.add(it)
|
updatedBooksList.add(it)
|
||||||
|
@ -22,13 +22,12 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import org.kiwix.kiwixmobile.core.extensions.isFileExist
|
|
||||||
import org.kiwix.kiwixmobile.custom.BuildConfig
|
import org.kiwix.kiwixmobile.custom.BuildConfig
|
||||||
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
|
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
|
||||||
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
|
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
|
||||||
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasNothing
|
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasNothing
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileDescriptor
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -44,10 +43,10 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
|
|||||||
private fun detectInstallationState(
|
private fun detectInstallationState(
|
||||||
obbFiles: List<File> = obbFiles(),
|
obbFiles: List<File> = obbFiles(),
|
||||||
zimFiles: List<File> = zimFiles(),
|
zimFiles: List<File> = zimFiles(),
|
||||||
assetFile: File? = getFileFromPlayAssetDelivery()
|
assetFileDescriptor: FileDescriptor? = getFileFromPlayAssetDelivery()
|
||||||
): ValidationState {
|
): ValidationState {
|
||||||
return when {
|
return when {
|
||||||
assetFile != null -> HasFile(assetFile)
|
assetFileDescriptor != null -> HasFile(null, assetFileDescriptor)
|
||||||
obbFiles.isNotEmpty() && zimFiles().isNotEmpty() -> HasBothFiles(obbFiles[0], zimFiles[0])
|
obbFiles.isNotEmpty() && zimFiles().isNotEmpty() -> HasBothFiles(obbFiles[0], zimFiles[0])
|
||||||
obbFiles.isNotEmpty() -> HasFile(obbFiles[0])
|
obbFiles.isNotEmpty() -> HasFile(obbFiles[0])
|
||||||
zimFiles.isNotEmpty() -> HasFile(zimFiles[0])
|
zimFiles.isNotEmpty() -> HasFile(zimFiles[0])
|
||||||
@ -56,30 +55,12 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NestedBlockDepth", "MagicNumber")
|
@Suppress("NestedBlockDepth", "MagicNumber")
|
||||||
private fun getFileFromPlayAssetDelivery(): File? {
|
private fun getFileFromPlayAssetDelivery(): FileDescriptor? {
|
||||||
var zimFile: File? = null
|
|
||||||
try {
|
try {
|
||||||
val context = context.createPackageContext(context.packageName, 0)
|
val context = context.createPackageContext(context.packageName, 0)
|
||||||
val assetManager = context.assets
|
val assetManager = context.assets
|
||||||
val inputStream = assetManager.open(BuildConfig.PLAY_ASSET_FILE)
|
val inputStream = assetManager.openFd(BuildConfig.PLAY_ASSET_FILE)
|
||||||
val filePath = ContextCompat.getExternalFilesDirs(context, null)[0]
|
return inputStream.fileDescriptor
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (packageNameNotFoundException: PackageManager.NameNotFoundException) {
|
} catch (packageNameNotFoundException: PackageManager.NameNotFoundException) {
|
||||||
Log.w(
|
Log.w(
|
||||||
"ASSET_PACKAGE_DELIVERY",
|
"ASSET_PACKAGE_DELIVERY",
|
||||||
@ -88,7 +69,7 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
|
|||||||
} catch (ioException: IOException) {
|
} catch (ioException: IOException) {
|
||||||
Log.w("ASSET_PACKAGE_DELIVERY", "Unable to copy the content of asset $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")
|
private fun obbFiles() = scanDirs(ContextCompat.getObbDirs(context), "obb")
|
||||||
@ -124,6 +105,8 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
|
|||||||
|
|
||||||
sealed class ValidationState {
|
sealed class ValidationState {
|
||||||
data class HasBothFiles(val obbFile: File, val zimFile: File) : 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()
|
object HasNothing : ValidationState()
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,13 @@ class CustomReaderFragment : CoreReaderFragment() {
|
|||||||
customFileValidator.validate(
|
customFileValidator.validate(
|
||||||
onFilesFound = {
|
onFilesFound = {
|
||||||
when (it) {
|
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 -> {
|
is ValidationState.HasBothFiles -> {
|
||||||
it.zimFile.delete()
|
it.zimFile.delete()
|
||||||
openZimFile(it.obbFile, true)
|
openZimFile(it.obbFile, true)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user