Avoid hard crash if cached image file was already deleted

Closes #9720
This commit is contained in:
arkon 2024-01-06 18:15:17 -05:00
parent 36f307e3bb
commit 3ea026e311
2 changed files with 68 additions and 50 deletions

View file

@ -17,10 +17,12 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import logcat.LogPriority
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.ImageUtil
import tachiyomi.core.util.system.logcat
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
@ -136,40 +138,47 @@ class PagerPageHolder(
val streamFn = page.stream ?: return val streamFn = page.stream ?: return
val (bais, isAnimated, background) = withIOContext { try {
streamFn().buffered(16).use { stream -> val (bais, isAnimated, background) = withIOContext {
process(item, stream).use { itemStream -> streamFn().buffered(16).use { stream ->
val bais = ByteArrayInputStream(itemStream.readBytes()) process(item, stream).use { itemStream ->
val isAnimated = ImageUtil.isAnimatedAndSupported(bais) val bais = ByteArrayInputStream(itemStream.readBytes())
bais.reset() val isAnimated = ImageUtil.isAnimatedAndSupported(bais)
val background = if (!isAnimated && viewer.config.automaticBackground) { bais.reset()
ImageUtil.chooseBackground(context, bais) val background = if (!isAnimated && viewer.config.automaticBackground) {
} else { ImageUtil.chooseBackground(context, bais)
null } else {
null
}
bais.reset()
Triple(bais, isAnimated, background)
} }
bais.reset()
Triple(bais, isAnimated, background)
} }
} }
} withUIContext {
withUIContext { bais.use {
bais.use { setImage(
setImage( it,
it, isAnimated,
isAnimated, Config(
Config( zoomDuration = viewer.config.doubleTapAnimDuration,
zoomDuration = viewer.config.doubleTapAnimDuration, minimumScaleType = viewer.config.imageScaleType,
minimumScaleType = viewer.config.imageScaleType, cropBorders = viewer.config.imageCropBorders,
cropBorders = viewer.config.imageCropBorders, zoomStartPosition = viewer.config.imageZoomType,
zoomStartPosition = viewer.config.imageZoomType, landscapeZoom = viewer.config.landscapeZoom,
landscapeZoom = viewer.config.landscapeZoom, ),
), )
) if (!isAnimated) {
if (!isAnimated) { pageBackground = background
pageBackground = background }
} }
removeErrorLayout()
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
withUIContext {
setError()
} }
removeErrorLayout()
} }
} }

View file

@ -23,10 +23,12 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import logcat.LogPriority
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.ImageUtil
import tachiyomi.core.util.system.logcat
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.InputStream import java.io.InputStream
@ -184,28 +186,35 @@ class WebtoonPageHolder(
val streamFn = page?.stream ?: return val streamFn = page?.stream ?: return
val (openStream, isAnimated) = withIOContext { try {
val stream = streamFn().buffered(16) val (openStream, isAnimated) = withIOContext {
val openStream = process(stream) val stream = streamFn().buffered(16)
val openStream = process(stream)
val isAnimated = ImageUtil.isAnimatedAndSupported(stream) val isAnimated = ImageUtil.isAnimatedAndSupported(stream)
Pair(openStream, isAnimated) Pair(openStream, isAnimated)
} }
withUIContext { withUIContext {
frame.setImage( frame.setImage(
openStream, openStream,
isAnimated, isAnimated,
ReaderPageImageView.Config( ReaderPageImageView.Config(
zoomDuration = viewer.config.doubleTapAnimDuration, zoomDuration = viewer.config.doubleTapAnimDuration,
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH, minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH,
cropBorders = viewer.config.imageCropBorders, cropBorders = viewer.config.imageCropBorders,
), ),
) )
removeErrorLayout() removeErrorLayout()
} }
// Suspend the coroutine to close the input stream only when the WebtoonPageHolder is recycled // Suspend the coroutine to close the input stream only when the WebtoonPageHolder is recycled
suspendCancellableCoroutine<Nothing> { continuation -> suspendCancellableCoroutine<Nothing> { continuation ->
continuation.invokeOnCancellation { openStream.close() } continuation.invokeOnCancellation { openStream.close() }
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
withUIContext {
setError()
}
} }
} }