Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed, Reduce mass-storage consumption with Android custom apps with embedded ZIM #3516

Merged
merged 8 commits into from
Nov 12, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class MimeTypeTest : BaseActivityTest() {
}
val zimFileReader = ZimFileReader(
zimFile,
null,
Archive(zimFile.canonicalPath),
NightModeConfig(SharedPreferenceUtil(context), context)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,13 @@ class AddNoteDialog : DialogFragment() {
.inject(this)

// Returns name of the form ".../Kiwix/granbluefantasy_en_all_all_nopic_2018-10.zim"
zimFileName = zimReaderContainer.zimCanonicalPath
zimFileName = zimReaderContainer.zimCanonicalPath ?: zimReaderContainer.name
if (zimFileName != null) { // No zim file currently opened
zimFileTitle = zimReaderContainer.zimFileTitle
zimId = zimReaderContainer.id.orEmpty()

if (arguments != null) {
articleTitle = arguments?.getString(NOTES_TITLE)
articleTitle = arguments?.getString(NOTES_TITLE)?.substringAfter(": ")
zimFileUrl = arguments?.getString(ARTICLE_URL).orEmpty()
} else {
val webView = (activity as WebViewProvider?)?.getCurrentWebView()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Rect
Expand Down Expand Up @@ -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,
assetFileDescriptor: AssetFileDescriptor? = null
) {
if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || isCustomApp) {
if (file.isFileExist()) {
openAndSetInContainer(file)
if (file?.isFileExist() == true) {
openAndSetInContainer(file = file)
updateTitle()
} else if (assetFileDescriptor != null) {
openAndSetInContainer(assetFileDescriptor = assetFileDescriptor)
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 {
Expand All @@ -1419,16 +1427,24 @@ abstract class CoreReaderFragment :
)
}

private fun openAndSetInContainer(file: File) {
private fun openAndSetInContainer(
file: File? = null,
assetFileDescriptor: AssetFileDescriptor? = null
) {
try {
if (isNotPreviouslyOpenZim(file.canonicalPath)) {
if (isNotPreviouslyOpenZim(file?.canonicalPath)) {
webViewList.clear()
}
} catch (e: IOException) {
e.printStackTrace()
}
zimReaderContainer?.let { zimReaderContainer ->
zimReaderContainer.setZimFile(file)
if (assetFileDescriptor != null) {
zimReaderContainer.setZimFileDescriptor(assetFileDescriptor)
} 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.kiwix.kiwixmobile.core.di.ActivityScope
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import javax.inject.Inject

private const val TAG = "MainPresenter"
Expand All @@ -33,6 +34,7 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour
private var saveHistoryDisposable: Disposable? = null
private var saveBookmarkDisposable: Disposable? = null
private var saveNoteDisposable: Disposable? = null
private var saveBookDisposable: Disposable? = null
private var deleteNoteDisposable: Disposable? = null

fun saveHistory(history: HistoryItem) {
Expand Down Expand Up @@ -61,10 +63,16 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour
.subscribe({}, { e -> Log.e(TAG, "Unable to delete note", e) })
}

fun saveBook(book: BookOnDisk) {
saveBookDisposable = dataSource.saveBook(book)
.subscribe({}, { e -> Log.e(TAG, "Unable to save book", e) })
}

fun dispose() {
saveHistoryDisposable?.dispose()
saveBookmarkDisposable?.dispose()
saveNoteDisposable?.dispose()
deleteNoteDisposable?.dispose()
saveBookDisposable?.dispose()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ data class ShowOpenNoteDialog(
{ effects.offer(OpenPage(page, zimReaderContainer)) },
{
val item = page as NoteListItem
val file = File(item.zimFilePath.orEmpty())
zimReaderContainer.setZimFile(file)
// Check if zimFilePath is not null, and then set it in zimReaderContainer.
// For custom apps, we are currently using fileDescriptor, and they only have a single file in them,
// which is already set in zimReaderContainer, so there's no need to set it again.
item.zimFilePath?.let {
val file = File(it)
zimReaderContainer.setZimFile(file)
}
effects.offer(OpenNote(item.noteFilePath, item.zimUrl, item.title))
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ import javax.inject.Inject
private const val TAG = "ZimFileReader"

class ZimFileReader constructor(
val zimFile: File,
val zimFile: File?,
val assetFileDescriptor: AssetFileDescriptor? = null,
private val jniKiwixReader: Archive,
private val nightModeConfig: NightModeConfig,
private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader)
) {
interface Factory {
fun create(file: File): ZimFileReader?
fun create(assetFileDescriptor: AssetFileDescriptor): ZimFileReader?

class Impl @Inject constructor(private val nightModeConfig: NightModeConfig) :
Factory {
Expand All @@ -76,6 +78,26 @@ class ZimFileReader constructor(
} catch (ignore: Exception) { // for handing the error, if any zim file is corrupted
null
}

override fun create(assetFileDescriptor: AssetFileDescriptor): ZimFileReader? =
try {
ZimFileReader(
null,
assetFileDescriptor,
nightModeConfig = nightModeConfig,
jniKiwixReader = Archive(
assetFileDescriptor.parcelFileDescriptor.dup().fileDescriptor,
assetFileDescriptor.startOffset,
assetFileDescriptor.length
)
).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
}
}
}

Expand Down Expand Up @@ -289,6 +311,7 @@ class ZimFileReader constructor(
fun dispose() {
jniKiwixReader.dispose()
searcher.dispose()
assetFileDescriptor?.parcelFileDescriptor?.detachFd()
}

@Suppress("TooGenericExceptionCaught")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.kiwix.kiwixmobile.core.reader

import android.content.res.AssetFileDescriptor
import android.webkit.WebResourceResponse
import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory
Expand All @@ -42,6 +43,13 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F
else null
}

fun setZimFileDescriptor(assetFileDescriptor: AssetFileDescriptor) {
zimFileReader =
if (assetFileDescriptor.parcelFileDescriptor.dup().fileDescriptor.valid())
zimFileReaderFactory.create(assetFileDescriptor)
else null
}

fun getPageUrlFromTitle(title: String) = zimFileReader?.getPageUrlFrom(title)

fun getRandomArticleUrl() = zimFileReader?.getRandomArticleUrl()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import android.provider.DocumentsContract
import android.util.Log
import android.webkit.URLUtil
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -402,4 +403,8 @@ object FileUtils {
null
}
}

@JvmStatic
fun getDemoFilePathForCustomApp(context: Context) =
"${ContextCompat.getExternalFilesDirs(context, null)[0]}/demo.zim"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

package org.kiwix.kiwixmobile.core.webserver

import android.content.Context
import android.util.Log
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getDemoFilePathForCustomApp
import org.kiwix.libkiwix.Book
import org.kiwix.libkiwix.JNIKiwixException
import org.kiwix.libkiwix.Library
Expand All @@ -36,13 +39,32 @@ class KiwixServer @Inject constructor(
private val jniKiwixServer: Server
) {

class Factory @Inject constructor() {
class Factory @Inject constructor(
private val context: Context,
private val zimReaderContainer: ZimReaderContainer
) {
@Suppress("NestedBlockDepth")
fun createKiwixServer(selectedBooksPath: ArrayList<String>): KiwixServer {
val kiwixLibrary = Library()
selectedBooksPath.forEach { path ->
try {
val book = Book().apply {
update(Archive(path))
// Determine whether to create an Archive from an asset or a file path
val archive = if (path == getDemoFilePathForCustomApp(context)) {
// For custom apps using a demo file, create an Archive with FileDescriptor
val assetFileDescriptor = zimReaderContainer.zimFileReader?.assetFileDescriptor
val startOffset = assetFileDescriptor?.startOffset ?: 0L
val size = assetFileDescriptor?.length ?: 0L
Archive(
assetFileDescriptor?.parcelFileDescriptor?.dup()?.fileDescriptor,
startOffset,
size
)
} else {
// For regular files, create an Archive from the file path
Archive(path)
}
update(archive)
}
kiwixLibrary.addBook(book)
} catch (e: JNIKiwixException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,28 @@ import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.databinding.ActivityZimHostBinding
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.cachedComponent
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.navigateToAppSettings
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.utils.ConnectivityReporter
import org.kiwix.kiwixmobile.core.utils.REQUEST_POST_NOTIFICATION_PERMISSION
import org.kiwix.kiwixmobile.core.utils.ServerUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.StartServer
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_CHECK_IP_ADDRESS
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_START_SERVER
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_STOP_SERVER
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BookOnDiskDelegate
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_CHECK_IP_ADDRESS
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_START_SERVER
import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_STOP_SERVER
import javax.inject.Inject

class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
Expand All @@ -87,6 +89,9 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
@Inject
lateinit var zimReaderFactory: ZimFileReader.Factory

@Inject
lateinit var zimReaderContainer: ZimReaderContainer

private lateinit var booksAdapter: BooksOnDiskAdapter
private lateinit var bookDelegate: BookOnDiskDelegate.BookDelegate
private var hotspotService: HotspotService? = null
Expand Down Expand Up @@ -479,21 +484,19 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
@Suppress("NestedBlockDepth")
override fun addBooks(books: List<BooksOnDiskListItem>) {
// Check if this is the app module, as custom apps may have multiple package names
if (requireActivity().packageName == "org.kiwix.kiwixmobile") {
if (!requireActivity().isCustomApp()) {
booksAdapter.items = books
} else {
val updatedBooksList: MutableList<BooksOnDiskListItem> = arrayListOf()
books.forEach {
if (it is BookOnDisk) {
zimReaderFactory.create(it.file)?.let { zimFileReader ->
zimReaderContainer.zimFileReader?.let { zimFileReader ->
val booksOnDiskListItem =
(BookOnDisk(zimFileReader.zimFile, zimFileReader) as BooksOnDiskListItem)
(BookOnDisk(it.file, zimFileReader) as BooksOnDiskListItem)
.apply {
isSelected = true
}
updatedBooksList.add(booksOnDiskListItem).also {
zimFileReader.dispose()
}
updatedBooksList.add(booksOnDiskListItem)
}
} else {
updatedBooksList.add(it)
Expand Down
4 changes: 4 additions & 0 deletions custom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ android {
}
}
assetPacks += ":install_time_asset"
androidResources {
// to not compress zim file in asset folder
noCompress.add("zim")
}
}

fun ProductFlavor.createDownloadTask(file: File): Task {
Expand Down
Loading
Loading