Fix pages loading issues
This commit is contained in:
@@ -136,7 +136,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'io.coil-kt:coil-base:2.7.0'
|
implementation 'io.coil-kt:coil-base:2.7.0'
|
||||||
implementation 'io.coil-kt:coil-svg: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 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
implementation 'io.noties.markwon:core:4.6.2'
|
implementation 'io.noties.markwon:core:4.6.2'
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ class ProgressResponseBody(
|
|||||||
override fun contentType(): MediaType? = delegate.contentType()
|
override fun contentType(): MediaType? = delegate.contentType()
|
||||||
|
|
||||||
override fun source(): BufferedSource {
|
override fun source(): BufferedSource {
|
||||||
return bufferedSource ?: ProgressSource(delegate.source(), contentLength(), progressState).buffer().also {
|
return bufferedSource ?: synchronized(this) {
|
||||||
|
bufferedSource ?: ProgressSource(delegate.source(), contentLength(), progressState).buffer().also {
|
||||||
bufferedSource = it
|
bufferedSource = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ProgressSource(
|
private class ProgressSource(
|
||||||
delegate: Source,
|
delegate: Source,
|
||||||
|
|||||||
@@ -104,10 +104,9 @@ class MangaPageFetcher(
|
|||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
throw HttpException(response)
|
throw HttpException(response)
|
||||||
}
|
}
|
||||||
val body = response.requireBody()
|
|
||||||
val mimeType = response.mimeType
|
val mimeType = response.mimeType
|
||||||
val file = body.use {
|
val file = response.requireBody().use {
|
||||||
pagesCache.put(pageUrl, it.source())
|
pagesCache.put(pageUrl, it.source(), mimeType)
|
||||||
}
|
}
|
||||||
SourceResult(
|
SourceResult(
|
||||||
source = ImageSource(
|
source = ImageSource(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.local.data
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
import com.tomclaw.cache.DiskLruCache
|
import com.tomclaw.cache.DiskLruCache
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -15,7 +16,7 @@ import okio.use
|
|||||||
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
|
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
|
||||||
import org.koitharu.kotatsu.core.util.FileSize
|
import org.koitharu.kotatsu.core.util.FileSize
|
||||||
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
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.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.subdir
|
import org.koitharu.kotatsu.core.util.ext.subdir
|
||||||
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
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.SuspendLazy
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -50,15 +52,15 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
}.getOrThrow()
|
}.getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun get(url: String): File? {
|
suspend fun get(url: String): File? = withContext(Dispatchers.IO) {
|
||||||
val cache = lruCache.get()
|
val cache = lruCache.get()
|
||||||
return runInterruptible(Dispatchers.IO) {
|
runInterruptible {
|
||||||
cache.get(url)?.takeIfReadable()
|
cache.get(url)?.takeIfReadable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun put(url: String, source: Source): File = withContext(Dispatchers.IO) {
|
suspend fun put(url: String, source: Source, mimeType: String?): File = withContext(Dispatchers.IO) {
|
||||||
val file = File(cacheDir.get().parentFile, url.longHashCode().toString())
|
val file = createBufferFile(url, mimeType)
|
||||||
try {
|
try {
|
||||||
val bytes = file.sink(append = false).buffer().use {
|
val bytes = file.sink(append = false).buffer().use {
|
||||||
it.writeAllCancellable(source)
|
it.writeAllCancellable(source)
|
||||||
@@ -66,17 +68,23 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
if (bytes == 0L) {
|
if (bytes == 0L) {
|
||||||
throw NoDataReceivedException(url)
|
throw NoDataReceivedException(url)
|
||||||
}
|
}
|
||||||
lruCache.get().put(url, file)
|
val cache = lruCache.get()
|
||||||
|
runInterruptible {
|
||||||
|
cache.put(url, file)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun put(url: String, bitmap: Bitmap): File = withContext(Dispatchers.IO) {
|
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 {
|
try {
|
||||||
bitmap.compressToPNG(file)
|
bitmap.compressToPNG(file)
|
||||||
lruCache.get().put(url, file)
|
val cache = lruCache.get()
|
||||||
|
runInterruptible {
|
||||||
|
cache.put(url, file)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
@@ -90,12 +98,24 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getAvailableSize(): Long = runCatchingCancellable {
|
private suspend fun getAvailableSize(): Long = runCatchingCancellable {
|
||||||
val statFs = StatFs(cacheDir.get().absolutePath)
|
val dir = cacheDir.get()
|
||||||
|
runInterruptible(Dispatchers.IO) {
|
||||||
|
val statFs = StatFs(dir.absolutePath)
|
||||||
statFs.availableBytes
|
statFs.availableBytes
|
||||||
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
it.printStackTraceDebug()
|
it.printStackTraceDebug()
|
||||||
}.getOrDefault(SIZE_DEFAULT)
|
}.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 {
|
private companion object {
|
||||||
|
|
||||||
val SIZE_MIN
|
val SIZE_MIN
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package org.koitharu.kotatsu.reader.domain
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.ImageDecoder
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import androidx.annotation.AnyThread
|
import androidx.annotation.AnyThread
|
||||||
import androidx.collection.LongSparseArray
|
import androidx.collection.LongSparseArray
|
||||||
import androidx.collection.set
|
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.local.data.isZipUri
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
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.requireBody
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||||
@@ -149,9 +152,13 @@ class PageLoader @Inject constructor(
|
|||||||
cache.put(uri.toString(), bitmap).toUri()
|
cache.put(uri.toString(), bitmap).toUri()
|
||||||
} else {
|
} else {
|
||||||
val file = uri.toFile()
|
val file = uri.toFile()
|
||||||
context.ensureRamAtLeast(file.length() * 2)
|
|
||||||
runInterruptible(Dispatchers.IO) {
|
runInterruptible(Dispatchers.IO) {
|
||||||
|
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))
|
checkBitmapNotNull(BitmapFactory.decodeFile(file.absolutePath))
|
||||||
|
}
|
||||||
}.use { image ->
|
}.use { image ->
|
||||||
image.compressToPNG(file)
|
image.compressToPNG(file)
|
||||||
}
|
}
|
||||||
@@ -235,7 +242,7 @@ class PageLoader @Inject constructor(
|
|||||||
val request = createPageRequest(pageUrl, page.source)
|
val request = createPageRequest(pageUrl, page.source)
|
||||||
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
|
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
|
||||||
response.requireBody().withProgress(progress).use {
|
response.requireBody().withProgress(progress).use {
|
||||||
cache.put(pageUrl, it.source())
|
cache.put(pageUrl, it.source(), response.mimeType)
|
||||||
}
|
}
|
||||||
}.toUri()
|
}.toUri()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ class PageHolderDelegate(
|
|||||||
} catch (ce: CancellationException) {
|
} catch (ce: CancellationException) {
|
||||||
throw ce
|
throw ce
|
||||||
} catch (e2: Throwable) {
|
} catch (e2: Throwable) {
|
||||||
|
e2.printStackTrace()
|
||||||
e.addSuppressed(e2)
|
e.addSuppressed(e2)
|
||||||
state = State.ERROR
|
state = State.ERROR
|
||||||
callback.onError(e)
|
callback.onError(e)
|
||||||
|
|||||||
Reference in New Issue
Block a user