diff --git a/app/build.gradle b/app/build.gradle index 01b9251c6..5613f92c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { implementation 'io.coil-kt:coil-base:2.7.0' implementation 'io.coil-kt:coil-svg:2.7.0' - implementation 'com.github.KotatsuApp:subsampling-scale-image-view:e04098de68' + implementation 'com.github.KotatsuApp:subsampling-scale-image-view:ac7360c5e3' implementation 'com.github.solkin:disk-lru-cache:1.4' implementation 'io.noties.markwon:core:4.6.2' diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/ProgressResponseBody.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/ProgressResponseBody.kt index b66e5cd2a..06a3fea9b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/ProgressResponseBody.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/ProgressResponseBody.kt @@ -26,8 +26,10 @@ class ProgressResponseBody( override fun contentType(): MediaType? = delegate.contentType() override fun source(): BufferedSource { - return bufferedSource ?: ProgressSource(delegate.source(), contentLength(), progressState).buffer().also { - bufferedSource = it + return bufferedSource ?: synchronized(this) { + bufferedSource ?: ProgressSource(delegate.source(), contentLength(), progressState).buffer().also { + bufferedSource = it + } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt index a5d8b7082..fd065ee2e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt @@ -104,10 +104,9 @@ class MangaPageFetcher( if (!response.isSuccessful) { throw HttpException(response) } - val body = response.requireBody() val mimeType = response.mimeType - val file = body.use { - pagesCache.put(pageUrl, it.source()) + val file = response.requireBody().use { + pagesCache.put(pageUrl, it.source(), mimeType) } SourceResult( source = ImageSource( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/PagesCache.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/PagesCache.kt index a66b60956..dfc86c51d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/PagesCache.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/PagesCache.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.local.data import android.content.Context import android.graphics.Bitmap import android.os.StatFs +import android.webkit.MimeTypeMap import com.tomclaw.cache.DiskLruCache import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -15,7 +16,7 @@ import okio.use import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException import org.koitharu.kotatsu.core.util.FileSize import org.koitharu.kotatsu.core.util.ext.compressToPNG -import org.koitharu.kotatsu.core.util.ext.longHashCode +import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.subdir import org.koitharu.kotatsu.core.util.ext.takeIfReadable @@ -24,6 +25,7 @@ import org.koitharu.kotatsu.core.util.ext.writeAllCancellable import org.koitharu.kotatsu.parsers.util.SuspendLazy import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import java.io.File +import java.util.UUID import javax.inject.Inject import javax.inject.Singleton @@ -50,15 +52,15 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) { }.getOrThrow() } - suspend fun get(url: String): File? { + suspend fun get(url: String): File? = withContext(Dispatchers.IO) { val cache = lruCache.get() - return runInterruptible(Dispatchers.IO) { + runInterruptible { cache.get(url)?.takeIfReadable() } } - suspend fun put(url: String, source: Source): File = withContext(Dispatchers.IO) { - val file = File(cacheDir.get().parentFile, url.longHashCode().toString()) + suspend fun put(url: String, source: Source, mimeType: String?): File = withContext(Dispatchers.IO) { + val file = createBufferFile(url, mimeType) try { val bytes = file.sink(append = false).buffer().use { it.writeAllCancellable(source) @@ -66,17 +68,23 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) { if (bytes == 0L) { throw NoDataReceivedException(url) } - lruCache.get().put(url, file) + val cache = lruCache.get() + runInterruptible { + cache.put(url, file) + } } finally { file.delete() } } suspend fun put(url: String, bitmap: Bitmap): File = withContext(Dispatchers.IO) { - val file = File(cacheDir.get().parentFile, url.longHashCode().toString()) + val file = createBufferFile(url, "image/png") try { bitmap.compressToPNG(file) - lruCache.get().put(url, file) + val cache = lruCache.get() + runInterruptible { + cache.put(url, file) + } } finally { file.delete() } @@ -90,12 +98,24 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) { } private suspend fun getAvailableSize(): Long = runCatchingCancellable { - val statFs = StatFs(cacheDir.get().absolutePath) - statFs.availableBytes + val dir = cacheDir.get() + runInterruptible(Dispatchers.IO) { + val statFs = StatFs(dir.absolutePath) + statFs.availableBytes + } }.onFailure { it.printStackTraceDebug() }.getOrDefault(SIZE_DEFAULT) + private suspend fun createBufferFile(url: String, mimeType: String?): File { + val ext = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } + ?: MimeTypeMap.getFileExtensionFromUrl(url).ifNullOrEmpty { "dat" } + val cacheDir = cacheDir.get() + val rootDir = checkNotNull(cacheDir.parentFile) { "Cannot get parent for ${cacheDir.absolutePath}" } + val name = UUID.randomUUID().toString() + "." + ext + return File(rootDir, name) + } + private companion object { val SIZE_MIN diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt index 1b3464c3c..00cbb7ad4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt @@ -3,8 +3,10 @@ package org.koitharu.kotatsu.reader.domain import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.ImageDecoder import android.graphics.Rect import android.net.Uri +import android.os.Build import androidx.annotation.AnyThread import androidx.collection.LongSparseArray import androidx.collection.set @@ -56,6 +58,7 @@ import org.koitharu.kotatsu.local.data.isFileUri import org.koitharu.kotatsu.local.data.isZipUri import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.mimeType import org.koitharu.kotatsu.parsers.util.requireBody import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.reader.ui.pager.ReaderPage @@ -149,9 +152,13 @@ class PageLoader @Inject constructor( cache.put(uri.toString(), bitmap).toUri() } else { val file = uri.toFile() - context.ensureRamAtLeast(file.length() * 2) runInterruptible(Dispatchers.IO) { - checkBitmapNotNull(BitmapFactory.decodeFile(file.absolutePath)) + context.ensureRamAtLeast(file.length() * 2) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(file)) + } else { + checkBitmapNotNull(BitmapFactory.decodeFile(file.absolutePath)) + } }.use { image -> image.compressToPNG(file) } @@ -235,7 +242,7 @@ class PageLoader @Inject constructor( val request = createPageRequest(pageUrl, page.source) imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response -> response.requireBody().withProgress(progress).use { - cache.put(pageUrl, it.source()) + cache.put(pageUrl, it.source(), response.mimeType) } }.toUri() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt index 3cfcea351..7c7ec895f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/PageHolderDelegate.kt @@ -152,6 +152,7 @@ class PageHolderDelegate( } catch (ce: CancellationException) { throw ce } catch (e2: Throwable) { + e2.printStackTrace() e.addSuppressed(e2) state = State.ERROR callback.onError(e)