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: Not able to upload 512MB+ custom apps anymore. #3801

Merged
merged 6 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class MimeTypeTest : BaseActivityTest() {
val archive = Archive(zimFile.canonicalPath)
val zimFileReader = ZimFileReader(
zimFile,
null,
emptyList(),
null,
archive,
NightModeConfig(SharedPreferenceUtil(context), context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class EncodedUrlTest : BaseActivityTest() {
val archive = Archive(zimFile.canonicalPath)
val zimFileReader = ZimFileReader(
zimFile,
null,
emptyList(),
null,
archive,
NightModeConfig(SharedPreferenceUtil(context), context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class ZimFileReaderWithSplittedZimFileTest : BaseActivityTest() {
val archive = Archive(zimFile.canonicalPath)
val zimFileReader = ZimFileReader(
zimFile,
null,
emptyList(),
null,
archive,
NightModeConfig(SharedPreferenceUtil(context), context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
override fun onResume() {
super.onResume()
if (zimReaderContainer?.zimFile == null &&
zimReaderContainer?.zimFileReader?.assetFileDescriptor == null
zimReaderContainer?.zimFileReader?.assetFileDescriptorList?.isEmpty() == true
) {
exitBook()
}
Expand Down
2 changes: 0 additions & 2 deletions buildSrc/src/main/kotlin/custom/CustomApps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ fun ProductFlavors.create(customApps: List<CustomApp>) {
buildConfigField("String", "ENFORCED_LANG", "\"${customApp.enforcedLanguage}\"")
buildConfigField("String", "ABOUT_APP_URL", "\"${customApp.aboutAppUrl}\"")
buildConfigField("String", "SUPPORT_URL", "\"${customApp.supportUrl}\"")
// Add asset file name in buildConfig file, we will use later to receive the zim file.
buildConfigField("String", "PLAY_ASSET_FILE", "\"${customApp.name}.zim\"")
buildConfigField("Boolean", "DISABLE_SIDEBAR", "${customApp.disableSideBar}")
buildConfigField("Boolean", "DISABLE_TABS", "${customApp.disableTabs}")
buildConfigField("Boolean", "DISABLE_READ_ALOUD", "${customApp.disableReadAloud}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@
import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX
import org.kiwix.kiwixmobile.core.utils.UpdateUtils.reformatProviderUrl
import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower
import org.kiwix.kiwixmobile.core.utils.dialog.UnsupportedMimeTypeHandler
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
import org.kiwix.kiwixmobile.core.utils.dialog.UnsupportedMimeTypeHandler
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.Log
Expand Down Expand Up @@ -1554,7 +1554,7 @@
protected fun openZimFile(
file: File?,
isCustomApp: Boolean = false,
assetFileDescriptor: AssetFileDescriptor? = null,
assetFileDescriptorList: List<AssetFileDescriptor> = emptyList(),
filePath: String? = null
) {
if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || isCustomApp) {
Expand All @@ -1564,10 +1564,10 @@
reopenBook()
openAndSetInContainer(file = file)
updateTitle()
} else if (assetFileDescriptor != null) {
} else if (assetFileDescriptorList.isNotEmpty()) {
reopenBook()
openAndSetInContainer(
assetFileDescriptor = assetFileDescriptor,
assetFileDescriptorList = assetFileDescriptorList,

Check warning on line 1570 in core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt#L1570

Added line #L1570 was not covered by tests
filePath = filePath
)
updateTitle()
Expand Down Expand Up @@ -1602,7 +1602,7 @@

private fun openAndSetInContainer(
file: File? = null,
assetFileDescriptor: AssetFileDescriptor? = null,
assetFileDescriptorList: List<AssetFileDescriptor> = emptyList(),
filePath: String? = null
) {
try {
Expand All @@ -1613,9 +1613,9 @@
e.printStackTrace()
}
zimReaderContainer?.let { zimReaderContainer ->
if (assetFileDescriptor != null) {
if (assetFileDescriptorList.isNotEmpty()) {
zimReaderContainer.setZimFileDescriptor(
assetFileDescriptor,
assetFileDescriptorList,

Check warning on line 1618 in core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt#L1618

Added line #L1618 was not covered by tests
filePath = filePath
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.kiwix.libkiwix.JNIKiwixException
import org.kiwix.libzim.Archive
import org.kiwix.libzim.DirectAccessInfo
import org.kiwix.libzim.FdInput
import org.kiwix.libzim.Item
import org.kiwix.libzim.SuggestionSearch
import org.kiwix.libzim.SuggestionSearcher
Expand All @@ -56,7 +57,7 @@
@Suppress("LongParameterList")
class ZimFileReader constructor(
val zimFile: File?,
val assetFileDescriptor: AssetFileDescriptor? = null,
val assetFileDescriptorList: List<AssetFileDescriptor> = emptyList(),
val assetDescriptorFilePath: String? = null,
val jniKiwixReader: Archive,
private val nightModeConfig: NightModeConfig,
Expand All @@ -65,7 +66,7 @@
interface Factory {
suspend fun create(file: File): ZimFileReader?
suspend fun create(
assetFileDescriptor: AssetFileDescriptor,
assetFileDescriptorList: List<AssetFileDescriptor>,
filePath: String? = null
): ZimFileReader?

Expand All @@ -91,18 +92,19 @@
}

override suspend fun create(
assetFileDescriptor: AssetFileDescriptor,
assetFileDescriptorList: List<AssetFileDescriptor>,
filePath: String?
): ZimFileReader? = withContext(Dispatchers.IO) { // Bug Fix #3805
try {
val archive = Archive(
assetFileDescriptor.parcelFileDescriptor.dup().fileDescriptor,
assetFileDescriptor.startOffset,
assetFileDescriptor.length
)
val fdInputArray = getFdInputArrayFromAssetFileDescriptorList(assetFileDescriptorList)

Check warning on line 99 in core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt#L99

Added line #L99 was not covered by tests
val archive = if (fdInputArray.size == 1) {
Archive(fdInputArray[0])

Check warning on line 101 in core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt#L101

Added line #L101 was not covered by tests
} else {
Archive(fdInputArray)

Check warning on line 103 in core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt#L103

Added line #L103 was not covered by tests
}
ZimFileReader(
null,
assetFileDescriptor,
assetFileDescriptorList,

Check warning on line 107 in core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt#L107

Added line #L107 was not covered by tests
assetDescriptorFilePath = filePath,
nightModeConfig = nightModeConfig,
jniKiwixReader = archive,
Expand All @@ -116,6 +118,17 @@
null
}
}

private fun getFdInputArrayFromAssetFileDescriptorList(
assetFileDescriptorList: List<AssetFileDescriptor>
): Array<FdInput> =
assetFileDescriptorList.map {
FdInput(
it.parcelFileDescriptor.dup().fileDescriptor,
it.startOffset,
it.length

Check warning on line 129 in core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt#L125-L129

Added lines #L125 - L129 were not covered by tests
)
}.toTypedArray()

Check warning on line 131 in core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt#L131

Added line #L131 was not covered by tests
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@
}

fun setZimFileDescriptor(
assetFileDescriptor: AssetFileDescriptor,
assetFileDescriptorList: List<AssetFileDescriptor>,
filePath: String? = null
) {
zimFileReader = runBlocking {
if (assetFileDescriptor.parcelFileDescriptor.dup().fileDescriptor.valid())
zimFileReaderFactory.create(assetFileDescriptor, filePath)
if (assetFileDescriptorList.isNotEmpty() &&
assetFileDescriptorList[0].parcelFileDescriptor.dup().fileDescriptor.valid()
)
zimFileReaderFactory.create(assetFileDescriptorList, filePath)

Check warning on line 56 in core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt#L56

Added line #L56 was not covered by tests
else null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
// 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 assetFileDescriptor =

Check warning on line 54 in core/src/main/java/org/kiwix/kiwixmobile/core/webserver/KiwixServer.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/webserver/KiwixServer.kt#L54

Added line #L54 was not covered by tests
zimReaderContainer.zimFileReader?.assetFileDescriptorList?.get(0)
val startOffset = assetFileDescriptor?.startOffset ?: 0L
val size = assetFileDescriptor?.length ?: 0L
Archive(
Expand Down
37 changes: 36 additions & 1 deletion custom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,41 @@ fun writeZimFileData(responseBody: ResponseBody, file: File) {
}
}

fun writeZimFileDataInChunk(
responseBody: ResponseBody,
file: File,
chunkSize: Long = 500 * 1024 * 1024 // create a chunk of 500MB
) {
var outputStream: FileOutputStream? = null
val buffer = ByteArray(4096)
var bytesRead: Int
var totalBytesWritten = 0L
var chunkNumber = 0

responseBody.byteStream().use { inputStream ->
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
if (outputStream == null || totalBytesWritten >= chunkSize) {
// Close the current chunk and open a new one
outputStream?.flush()
outputStream?.close()
chunkNumber++
val nextChunkFile = File(file.parent, "chunk$chunkNumber.zim")
nextChunkFile.createNewFile()
outputStream = FileOutputStream(nextChunkFile)
totalBytesWritten = 0 // Reset totalBytesWritten for the new chunk
}

// Write data to the output stream
outputStream?.write(buffer, 0, bytesRead)
totalBytesWritten += bytesRead
}
}

// Close the last chunk (if any)
outputStream?.flush()
outputStream?.close()
}

fun ProductFlavor.createDownloadTaskForPlayAssetDelivery(
file: File
): Task {
Expand All @@ -134,7 +169,7 @@ fun ProductFlavor.createDownloadTaskForPlayAssetDelivery(
OkHttpClient().newCall(fetchRequest()).execute().use { response ->
if (response.isSuccessful) {
response.body?.let { responseBody ->
writeZimFileData(responseBody, file)
writeZimFileDataInChunk(responseBody, file)
}
} else {
throw RuntimeException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ package org.kiwix.kiwixmobile.custom.main
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.content.res.AssetManager
import androidx.core.content.ContextCompat
import org.kiwix.kiwixmobile.core.utils.files.Log
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
Expand All @@ -37,16 +37,18 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
when (val installationState = detectInstallationState()) {
is HasBothFiles,
is HasFile -> onFilesFound(installationState)

HasNothing -> onNoFilesFound()
}

private fun detectInstallationState(
obbFiles: List<File> = obbFiles(),
zimFiles: List<File> = zimFiles(),
assetFileDescriptor: AssetFileDescriptor? = getAssetFileDescriptorFromPlayAssetDelivery()
assetFileDescriptorList: List<AssetFileDescriptor> =
getAssetFileDescriptorListFromPlayAssetDelivery()
): ValidationState {
return when {
assetFileDescriptor != null -> HasFile(null, assetFileDescriptor)
assetFileDescriptorList.isNotEmpty() -> HasFile(null, assetFileDescriptorList)
obbFiles.isNotEmpty() && zimFiles().isNotEmpty() -> HasBothFiles(obbFiles[0], zimFiles[0])
obbFiles.isNotEmpty() -> HasFile(obbFiles[0])
zimFiles.isNotEmpty() -> HasFile(zimFiles[0])
Expand All @@ -55,11 +57,15 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
}

@Suppress("MagicNumber")
private fun getAssetFileDescriptorFromPlayAssetDelivery(): AssetFileDescriptor? {
private fun getAssetFileDescriptorListFromPlayAssetDelivery(): List<AssetFileDescriptor> {
try {
val context = context.createPackageContext(context.packageName, 0)
val assetManager = context.assets
return assetManager.openFd(BuildConfig.PLAY_ASSET_FILE)
val assetManager = context.createPackageContext(context.packageName, 0).assets
val assetFileDescriptorList: ArrayList<AssetFileDescriptor> = arrayListOf()
getChunksList(assetManager).forEach {
assetFileDescriptorList.add(assetManager.openFd(it))
}

return assetFileDescriptorList
} catch (packageNameNotFoundException: PackageManager.NameNotFoundException) {
Log.w(
"ASSET_PACKAGE_DELIVERY",
Expand All @@ -68,7 +74,24 @@ 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 null
return emptyList()
}

private fun getChunksList(assetManager: AssetManager): List<String> {
val chunkFiles = mutableListOf<String>()

try {
// List of all files in the asset directory
val assets = assetManager.list("") ?: emptyArray()

// Filter and count chunk files.
assets.filterTo(chunkFiles) { it.startsWith("chunk") && it.endsWith(".zim") }
chunkFiles.sortBy { it.substringAfter("chunk").substringBefore(".zim").toInt() }
} catch (ioException: IOException) {
ioException.printStackTrace()
}

return chunkFiles
}

private fun obbFiles() = scanDirs(ContextCompat.getObbDirs(context), "obb")
Expand Down Expand Up @@ -104,7 +127,10 @@ 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?, val assetFileDescriptor: AssetFileDescriptor? = null) :
data class HasFile(
val file: File?,
val assetFileDescriptorList: List<AssetFileDescriptor> = emptyList()
) :
ValidationState()

object HasNothing : ValidationState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ class CustomReaderFragment : CoreReaderFragment() {
onFilesFound = {
when (it) {
is ValidationState.HasFile -> {
if (it.assetFileDescriptor != null) {
openZimFile(null, true, it.assetFileDescriptor)
if (it.assetFileDescriptorList.isNotEmpty()) {
openZimFile(null, true, it.assetFileDescriptorList)
} else {
openZimFile(it.file, true)
}
Expand Down
Loading