|
|
|
@ -8,9 +8,15 @@ import androidx.collection.LongSparseArray
|
|
|
|
import androidx.collection.set
|
|
|
|
import androidx.collection.set
|
|
|
|
import androidx.core.net.toFile
|
|
|
|
import androidx.core.net.toFile
|
|
|
|
import androidx.core.net.toUri
|
|
|
|
import androidx.core.net.toUri
|
|
|
|
|
|
|
|
import coil3.BitmapImage
|
|
|
|
|
|
|
|
import coil3.Image
|
|
|
|
|
|
|
|
import coil3.ImageLoader
|
|
|
|
|
|
|
|
import coil3.memory.MemoryCache
|
|
|
|
|
|
|
|
import coil3.request.ImageRequest
|
|
|
|
|
|
|
|
import coil3.request.transformations
|
|
|
|
|
|
|
|
import coil3.toBitmap
|
|
|
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
|
|
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
|
|
|
import dagger.hilt.android.ActivityRetainedLifecycle
|
|
|
|
import dagger.hilt.android.ActivityRetainedLifecycle
|
|
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
|
|
|
|
|
|
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
|
|
|
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
|
|
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
|
|
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
|
|
|
import kotlinx.coroutines.Deferred
|
|
|
|
import kotlinx.coroutines.Deferred
|
|
|
|
@ -24,6 +30,7 @@ import kotlinx.coroutines.sync.Mutex
|
|
|
|
import kotlinx.coroutines.sync.Semaphore
|
|
|
|
import kotlinx.coroutines.sync.Semaphore
|
|
|
|
import kotlinx.coroutines.sync.withLock
|
|
|
|
import kotlinx.coroutines.sync.withLock
|
|
|
|
import kotlinx.coroutines.sync.withPermit
|
|
|
|
import kotlinx.coroutines.sync.withPermit
|
|
|
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
import okhttp3.OkHttpClient
|
|
|
|
import okhttp3.OkHttpClient
|
|
|
|
import okhttp3.Request
|
|
|
|
import okhttp3.Request
|
|
|
|
import okio.use
|
|
|
|
import okio.use
|
|
|
|
@ -36,9 +43,9 @@ import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
|
|
|
import org.koitharu.kotatsu.core.parser.CachingMangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.CachingMangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
|
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
|
|
|
import org.koitharu.kotatsu.core.util.FileSize
|
|
|
|
import org.koitharu.kotatsu.core.util.FileSize
|
|
|
|
import org.koitharu.kotatsu.core.util.MimeTypes
|
|
|
|
import org.koitharu.kotatsu.core.util.MimeTypes
|
|
|
|
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.cancelChildrenAndJoin
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.cancelChildrenAndJoin
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
|
|
|
@ -49,6 +56,8 @@ import org.koitharu.kotatsu.core.util.ext.isFileUri
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.isNotEmpty
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.isNotEmpty
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.lifecycleScope
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.ramAvailable
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.ramAvailable
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.toMimeType
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.toMimeType
|
|
|
|
@ -76,13 +85,14 @@ class PageLoader @Inject constructor(
|
|
|
|
lifecycle: ActivityRetainedLifecycle,
|
|
|
|
lifecycle: ActivityRetainedLifecycle,
|
|
|
|
@MangaHttpClient private val okHttp: OkHttpClient,
|
|
|
|
@MangaHttpClient private val okHttp: OkHttpClient,
|
|
|
|
private val cache: PagesCache,
|
|
|
|
private val cache: PagesCache,
|
|
|
|
|
|
|
|
private val coil: ImageLoader,
|
|
|
|
private val settings: AppSettings,
|
|
|
|
private val settings: AppSettings,
|
|
|
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
|
|
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
|
|
|
private val imageProxyInterceptor: ImageProxyInterceptor,
|
|
|
|
private val imageProxyInterceptor: ImageProxyInterceptor,
|
|
|
|
private val downloadSlowdownDispatcher: DownloadSlowdownDispatcher,
|
|
|
|
private val downloadSlowdownDispatcher: DownloadSlowdownDispatcher,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
|
|
val loaderScope = RetainedLifecycleCoroutineScope(lifecycle) + InternalErrorHandler() + Dispatchers.Default
|
|
|
|
val loaderScope = lifecycle.lifecycleScope + InternalErrorHandler() + Dispatchers.Default
|
|
|
|
|
|
|
|
|
|
|
|
private val tasks = LongSparseArray<ProgressDeferred<Uri, Float>>()
|
|
|
|
private val tasks = LongSparseArray<ProgressDeferred<Uri, Float>>()
|
|
|
|
private val semaphore = Semaphore(3)
|
|
|
|
private val semaphore = Semaphore(3)
|
|
|
|
@ -121,6 +131,41 @@ class PageLoader @Inject constructor(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
suspend fun loadPreview(page: MangaPage): ImageSource? {
|
|
|
|
|
|
|
|
val preview = page.preview
|
|
|
|
|
|
|
|
if (preview.isNullOrEmpty()) {
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
val request = ImageRequest.Builder(context)
|
|
|
|
|
|
|
|
.data(preview)
|
|
|
|
|
|
|
|
.mangaSourceExtra(page.source)
|
|
|
|
|
|
|
|
.transformations(TrimTransformation())
|
|
|
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
return coil.execute(request).image?.toImageSource()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun peekPreviewSource(preview: String?): ImageSource? {
|
|
|
|
|
|
|
|
if (preview.isNullOrEmpty()) {
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
coil.memoryCache?.let { cache ->
|
|
|
|
|
|
|
|
val key = MemoryCache.Key(preview)
|
|
|
|
|
|
|
|
cache[key]?.image?.let {
|
|
|
|
|
|
|
|
return if (it is BitmapImage) {
|
|
|
|
|
|
|
|
ImageSource.cachedBitmap(it.toBitmap())
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
ImageSource.bitmap(it.toBitmap())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
coil.diskCache?.let { cache ->
|
|
|
|
|
|
|
|
cache.openSnapshot(preview)?.use { snapshot ->
|
|
|
|
|
|
|
|
return ImageSource.file(snapshot.data.toFile())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<Uri, Float> {
|
|
|
|
fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<Uri, Float> {
|
|
|
|
var task = tasks[page.id]?.takeIf { it.isValid() }
|
|
|
|
var task = tasks[page.id]?.takeIf { it.isValid() }
|
|
|
|
if (force) {
|
|
|
|
if (force) {
|
|
|
|
@ -237,7 +282,7 @@ class PageLoader @Inject constructor(
|
|
|
|
if (!skipCache) {
|
|
|
|
if (!skipCache) {
|
|
|
|
cache.get(pageUrl)?.let { return it.toUri() }
|
|
|
|
cache.get(pageUrl)?.let { return it.toUri() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val uri = Uri.parse(pageUrl)
|
|
|
|
val uri = pageUrl.toUri()
|
|
|
|
return when {
|
|
|
|
return when {
|
|
|
|
uri.isZipUri() -> if (uri.scheme == URI_SCHEME_ZIP) {
|
|
|
|
uri.isZipUri() -> if (uri.scheme == URI_SCHEME_ZIP) {
|
|
|
|
uri
|
|
|
|
uri
|
|
|
|
@ -264,6 +309,12 @@ class PageLoader @Inject constructor(
|
|
|
|
return context.ramAvailable <= FileSize.MEGABYTES.convert(PREFETCH_MIN_RAM_MB, FileSize.BYTES)
|
|
|
|
return context.ramAvailable <= FileSize.MEGABYTES.convert(PREFETCH_MIN_RAM_MB, FileSize.BYTES)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun Image.toImageSource(): ImageSource = if (this is BitmapImage) {
|
|
|
|
|
|
|
|
ImageSource.cachedBitmap(toBitmap())
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
ImageSource.bitmap(toBitmap())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun Deferred<Uri>.isValid(): Boolean {
|
|
|
|
private fun Deferred<Uri>.isValid(): Boolean {
|
|
|
|
return getCompletionResultOrNull()?.map { uri ->
|
|
|
|
return getCompletionResultOrNull()?.map { uri ->
|
|
|
|
uri.exists() && uri.isTargetNotEmpty()
|
|
|
|
uri.exists() && uri.isTargetNotEmpty()
|
|
|
|
|