|
|
|
@ -10,15 +10,20 @@ import coil3.ColorImage
|
|
|
|
import coil3.ImageLoader
|
|
|
|
import coil3.ImageLoader
|
|
|
|
import coil3.asImage
|
|
|
|
import coil3.asImage
|
|
|
|
import coil3.decode.DataSource
|
|
|
|
import coil3.decode.DataSource
|
|
|
|
|
|
|
|
import coil3.decode.ImageSource
|
|
|
|
import coil3.fetch.FetchResult
|
|
|
|
import coil3.fetch.FetchResult
|
|
|
|
import coil3.fetch.Fetcher
|
|
|
|
import coil3.fetch.Fetcher
|
|
|
|
import coil3.fetch.ImageFetchResult
|
|
|
|
import coil3.fetch.ImageFetchResult
|
|
|
|
|
|
|
|
import coil3.fetch.SourceFetchResult
|
|
|
|
import coil3.request.Options
|
|
|
|
import coil3.request.Options
|
|
|
|
import coil3.size.pxOrElse
|
|
|
|
import coil3.size.pxOrElse
|
|
|
|
import coil3.toAndroidUri
|
|
|
|
import coil3.toAndroidUri
|
|
|
|
|
|
|
|
import coil3.toBitmap
|
|
|
|
import kotlinx.coroutines.ensureActive
|
|
|
|
import kotlinx.coroutines.ensureActive
|
|
|
|
import kotlinx.coroutines.runInterruptible
|
|
|
|
import kotlinx.coroutines.runInterruptible
|
|
|
|
|
|
|
|
import okio.FileSystem
|
|
|
|
import okio.IOException
|
|
|
|
import okio.IOException
|
|
|
|
|
|
|
|
import okio.Path.Companion.toOkioPath
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
|
|
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
|
|
|
import org.koitharu.kotatsu.core.model.MangaSource
|
|
|
|
import org.koitharu.kotatsu.core.model.MangaSource
|
|
|
|
@ -26,8 +31,16 @@ import org.koitharu.kotatsu.core.parser.EmptyMangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.external.ExternalMangaRepository
|
|
|
|
import org.koitharu.kotatsu.core.parser.external.ExternalMangaRepository
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.core.util.MimeTypes
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.fetch
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.fetch
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.local.data.FaviconCache
|
|
|
|
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
|
|
|
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.local.data.LocalStorageCache
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|
|
|
|
|
|
|
import java.io.File
|
|
|
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
import kotlin.coroutines.coroutineContext
|
|
|
|
import kotlin.coroutines.coroutineContext
|
|
|
|
import coil3.Uri as CoilUri
|
|
|
|
import coil3.Uri as CoilUri
|
|
|
|
|
|
|
|
|
|
|
|
@ -36,6 +49,7 @@ class FaviconFetcher(
|
|
|
|
private val options: Options,
|
|
|
|
private val options: Options,
|
|
|
|
private val imageLoader: ImageLoader,
|
|
|
|
private val imageLoader: ImageLoader,
|
|
|
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
|
|
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
|
|
|
|
|
|
|
private val localStorageCache: LocalStorageCache,
|
|
|
|
) : Fetcher {
|
|
|
|
) : Fetcher {
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun fetch(): FetchResult? {
|
|
|
|
override suspend fun fetch(): FetchResult? {
|
|
|
|
@ -61,6 +75,16 @@ class FaviconFetcher(
|
|
|
|
options.size.width.pxOrElse { FALLBACK_SIZE },
|
|
|
|
options.size.width.pxOrElse { FALLBACK_SIZE },
|
|
|
|
options.size.height.pxOrElse { FALLBACK_SIZE },
|
|
|
|
options.size.height.pxOrElse { FALLBACK_SIZE },
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
val cacheKey = options.diskCacheKey ?: "${repository.source.name}_$sizePx"
|
|
|
|
|
|
|
|
if (options.diskCachePolicy.readEnabled) {
|
|
|
|
|
|
|
|
localStorageCache[cacheKey]?.let { file ->
|
|
|
|
|
|
|
|
return SourceFetchResult(
|
|
|
|
|
|
|
|
source = ImageSource(file.toOkioPath(), FileSystem.SYSTEM),
|
|
|
|
|
|
|
|
mimeType = MimeTypes.probeMimeType(file)?.toString(),
|
|
|
|
|
|
|
|
dataSource = DataSource.DISK,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
var favicons = repository.getFavicons()
|
|
|
|
var favicons = repository.getFavicons()
|
|
|
|
var lastError: Exception? = null
|
|
|
|
var lastError: Exception? = null
|
|
|
|
while (favicons.isNotEmpty()) {
|
|
|
|
while (favicons.isNotEmpty()) {
|
|
|
|
@ -69,7 +93,11 @@ class FaviconFetcher(
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
val result = imageLoader.fetch(icon.url, options)
|
|
|
|
val result = imageLoader.fetch(icon.url, options)
|
|
|
|
if (result != null) {
|
|
|
|
if (result != null) {
|
|
|
|
return result
|
|
|
|
return if (options.diskCachePolicy.writeEnabled) {
|
|
|
|
|
|
|
|
writeToCache(cacheKey, result)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
favicons -= icon
|
|
|
|
favicons -= icon
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -97,8 +125,39 @@ class FaviconFetcher(
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Factory(
|
|
|
|
private suspend fun writeToCache(key: String, result: FetchResult): FetchResult = runCatchingCancellable {
|
|
|
|
|
|
|
|
when (result) {
|
|
|
|
|
|
|
|
is ImageFetchResult -> {
|
|
|
|
|
|
|
|
if (result.dataSource == DataSource.NETWORK) {
|
|
|
|
|
|
|
|
localStorageCache.set(key, result.image.toBitmap()).asFetchResult()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is SourceFetchResult -> {
|
|
|
|
|
|
|
|
if (result.dataSource == DataSource.NETWORK) {
|
|
|
|
|
|
|
|
result.source.source().use {
|
|
|
|
|
|
|
|
localStorageCache.set(key, it, result.mimeType?.toMimeTypeOrNull()).asFetchResult()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}.onFailure {
|
|
|
|
|
|
|
|
it.printStackTraceDebug()
|
|
|
|
|
|
|
|
}.getOrDefault(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun File.asFetchResult() = SourceFetchResult(
|
|
|
|
|
|
|
|
source = ImageSource(toOkioPath(), FileSystem.SYSTEM),
|
|
|
|
|
|
|
|
mimeType = MimeTypes.probeMimeType(this)?.toString(),
|
|
|
|
|
|
|
|
dataSource = DataSource.DISK,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Factory @Inject constructor(
|
|
|
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
|
|
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
|
|
|
|
|
|
|
@FaviconCache private val faviconCache: LocalStorageCache,
|
|
|
|
) : Fetcher.Factory<CoilUri> {
|
|
|
|
) : Fetcher.Factory<CoilUri> {
|
|
|
|
|
|
|
|
|
|
|
|
override fun create(
|
|
|
|
override fun create(
|
|
|
|
@ -106,7 +165,7 @@ class FaviconFetcher(
|
|
|
|
options: Options,
|
|
|
|
options: Options,
|
|
|
|
imageLoader: ImageLoader
|
|
|
|
imageLoader: ImageLoader
|
|
|
|
): Fetcher? = if (data.scheme == URI_SCHEME_FAVICON) {
|
|
|
|
): Fetcher? = if (data.scheme == URI_SCHEME_FAVICON) {
|
|
|
|
FaviconFetcher(data.toAndroidUri(), options, imageLoader, mangaRepositoryFactory)
|
|
|
|
FaviconFetcher(data.toAndroidUri(), options, imageLoader, mangaRepositoryFactory, faviconCache)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|