mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-12 12:08:56 +01:00
Fix download splitter potentially throwing OOM on huge images (#7099)
* Fix download splitter potentially throwing OOM on huge images Also move the splitting to ImageUtil * Change variable name and logcat output
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.webkit.MimeTypeMap
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
@@ -29,8 +27,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil.isAnimatedAndSupported
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil.isTallImage
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.async
|
||||
import logcat.LogPriority
|
||||
@@ -42,12 +38,9 @@ import rx.subscriptions.CompositeSubscription
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.zip.CRC32
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* This class is the one in charge of downloading chapters.
|
||||
@@ -353,9 +346,7 @@ class Downloader(
|
||||
.onBackpressureLatest()
|
||||
// Do when page is downloaded.
|
||||
.doOnNext { page ->
|
||||
if (preferences.splitTallImages().get()) {
|
||||
splitTallImage(page, tmpDir)
|
||||
}
|
||||
splitTallImageIfNeeded(page, tmpDir)
|
||||
notifier.onProgressChange(download)
|
||||
}
|
||||
.toList()
|
||||
@@ -364,6 +355,7 @@ class Downloader(
|
||||
.doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) }
|
||||
// If the page list threw, it will resume here
|
||||
.onErrorReturn { error ->
|
||||
logcat(LogPriority.ERROR, error)
|
||||
download.status = Download.State.ERROR
|
||||
notifier.onError(error.message, download.chapter.name, download.manga.title)
|
||||
download
|
||||
@@ -487,6 +479,18 @@ class Downloader(
|
||||
return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg"
|
||||
}
|
||||
|
||||
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {
|
||||
if (!preferences.splitTallImages().get()) return
|
||||
|
||||
val filename = String.format("%03d", page.number)
|
||||
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith("$filename.") }
|
||||
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
|
||||
val imageFilePath = imageFile.filePath
|
||||
?: throw Error(context.getString(R.string.download_notifier_split_page_path_not_found, page.number))
|
||||
|
||||
ImageUtil.splitTallImage(imageFile, imageFilePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the download was successful.
|
||||
*
|
||||
@@ -557,48 +561,6 @@ class Downloader(
|
||||
tmpDir.delete()
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits tall images to improve performance of reader
|
||||
*/
|
||||
private fun splitTallImage(page: Page, tmpDir: UniFile) {
|
||||
val filename = String.format("%03d", page.number)
|
||||
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith("$filename.") }
|
||||
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
|
||||
|
||||
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
|
||||
return
|
||||
}
|
||||
|
||||
val bitmap = BitmapFactory.decodeFile(imageFile.filePath)
|
||||
val splitsCount = bitmap.height / context.resources.displayMetrics.heightPixels + 1
|
||||
val heightPerSplit = ceil(bitmap.height / splitsCount.toDouble()).toInt()
|
||||
logcat { "Splitting height ${bitmap.height} by $splitsCount * $heightPerSplit" }
|
||||
|
||||
try {
|
||||
(0 until splitsCount).forEach { split ->
|
||||
logcat { "Split #$split at y=${split * heightPerSplit}" }
|
||||
val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(split + 1)}.jpg"
|
||||
val splitHeight = split * heightPerSplit
|
||||
FileOutputStream(splitPath).use { stream ->
|
||||
Bitmap.createBitmap(
|
||||
bitmap,
|
||||
0,
|
||||
splitHeight,
|
||||
bitmap.width,
|
||||
min(heightPerSplit, bitmap.height - splitHeight),
|
||||
).compress(Bitmap.CompressFormat.JPEG, 100, stream)
|
||||
}
|
||||
}
|
||||
imageFile.delete()
|
||||
} catch (e: Exception) {
|
||||
// Image splits were not successfully saved so delete them and keep the original image
|
||||
(0 until splitsCount)
|
||||
.map { imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(it + 1)}.jpg" }
|
||||
.forEach { File(it).delete() }
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes a download. This method is called in the main thread.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user