Restore covers using interceptor
This commit is contained in:
@@ -1,28 +1,28 @@
|
||||
package org.koitharu.kotatsu.browser.cloudflare
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import coil.EventListener
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
|
||||
class CaptchaNotifier(
|
||||
private val context: Context,
|
||||
) : ImageRequest.Listener {
|
||||
) : EventListener {
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun notify(exception: CloudFlareProtectedException) {
|
||||
val manager = NotificationManagerCompat.from(context)
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
if (!context.checkNotificationPermission()) {
|
||||
return
|
||||
}
|
||||
val manager = NotificationManagerCompat.from(context)
|
||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(context.getString(R.string.captcha_required))
|
||||
.setShowBadge(true)
|
||||
|
||||
@@ -13,7 +13,6 @@ import coil.decode.SvgDecoder
|
||||
import coil.disk.DiskCache
|
||||
import coil.util.DebugLogger
|
||||
import dagger.Binds
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -26,6 +25,7 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.cache.ContentCache
|
||||
import org.koitharu.kotatsu.core.cache.MemoryContentCache
|
||||
import org.koitharu.kotatsu.core.cache.StubContentCache
|
||||
@@ -47,7 +47,7 @@ import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.main.domain.CoverRestorer
|
||||
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
|
||||
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.MangaPageFetcher
|
||||
@@ -91,7 +91,7 @@ interface AppModule {
|
||||
mangaRepositoryFactory: MangaRepository.Factory,
|
||||
imageProxyInterceptor: ImageProxyInterceptor,
|
||||
pageFetcherFactory: MangaPageFetcher.Factory,
|
||||
coverRestorerProvider: Lazy<CoverRestorer>,
|
||||
coverRestoreInterceptor: CoverRestoreInterceptor,
|
||||
): ImageLoader {
|
||||
val diskCacheFactory = {
|
||||
val rootDir = context.externalCacheDir ?: context.cacheDir
|
||||
@@ -108,7 +108,7 @@ interface AppModule {
|
||||
.diskCache(diskCacheFactory)
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
||||
.allowRgb565(context.isLowRamDevice())
|
||||
.eventListenerFactory { coverRestorerProvider.get() }
|
||||
.eventListener(CaptchaNotifier(context))
|
||||
.components(
|
||||
ComponentRegistry.Builder()
|
||||
.add(SvgDecoder.Factory())
|
||||
@@ -116,6 +116,7 @@ interface AppModule {
|
||||
.add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory))
|
||||
.add(pageFetcherFactory)
|
||||
.add(imageProxyInterceptor)
|
||||
.add(coverRestoreInterceptor)
|
||||
.build(),
|
||||
).build()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import coil.request.SuccessResult
|
||||
import coil.util.CoilUtils
|
||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.ui.image.RegionBitmapDecoder
|
||||
import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
@@ -29,7 +28,6 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
|
||||
.data(data)
|
||||
.lifecycle(lifecycleOwner)
|
||||
.crossfade(context)
|
||||
.addListener(CaptchaNotifier(context.applicationContext))
|
||||
.target(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +1,65 @@
|
||||
package org.koitharu.kotatsu.main.domain
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import coil.EventListener
|
||||
import coil.ImageLoader
|
||||
import coil.intercept.Interceptor
|
||||
import coil.network.HttpException
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.launch
|
||||
import coil.request.ImageResult
|
||||
import org.jsoup.HttpStatusException
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.util.Collections
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class CoverRestorer @Inject constructor(
|
||||
class CoverRestoreInterceptor @Inject constructor(
|
||||
private val dataRepository: MangaDataRepository,
|
||||
private val bookmarksRepository: BookmarksRepository,
|
||||
private val repositoryFactory: MangaRepository.Factory,
|
||||
private val coilProvider: Provider<ImageLoader>,
|
||||
) : EventListener {
|
||||
) : Interceptor {
|
||||
|
||||
private val blacklist = ArraySet<String>()
|
||||
private val blacklist = Collections.synchronizedSet(ArraySet<String>())
|
||||
|
||||
override fun onError(request: ImageRequest, result: ErrorResult) {
|
||||
super.onError(request, result)
|
||||
if (!result.throwable.shouldRestore()) {
|
||||
return
|
||||
}
|
||||
request.tags.tag<Manga>()?.let {
|
||||
restoreManga(it, request)
|
||||
}
|
||||
request.tags.tag<Bookmark>()?.let {
|
||||
restoreBookmark(it, request)
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreManga(manga: Manga, request: ImageRequest) {
|
||||
val key = manga.publicUrl
|
||||
if (key in blacklist) {
|
||||
return
|
||||
}
|
||||
request.lifecycle.coroutineScope.launch {
|
||||
val restored = runCatchingCancellable {
|
||||
restoreMangaImpl(manga)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
request.newBuilder().enqueueWith(coilProvider.get())
|
||||
} else {
|
||||
blacklist.add(key)
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val request = chain.request
|
||||
val result = chain.proceed(request)
|
||||
if (result is ErrorResult && result.throwable.shouldRestore()) {
|
||||
request.tags.tag<Manga>()?.let {
|
||||
if (restoreManga(it)) {
|
||||
return chain.proceed(request.newBuilder().build())
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
request.tags.tag<Bookmark>()?.let {
|
||||
if (restoreBookmark(it)) {
|
||||
return chain.proceed(request.newBuilder().build())
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun restoreManga(manga: Manga): Boolean {
|
||||
val key = manga.publicUrl
|
||||
if (!blacklist.add(key)) {
|
||||
return false
|
||||
}
|
||||
val restored = runCatchingCancellable {
|
||||
restoreMangaImpl(manga)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
blacklist.remove(key)
|
||||
}
|
||||
return restored
|
||||
}
|
||||
|
||||
private suspend fun restoreMangaImpl(manga: Manga): Boolean {
|
||||
@@ -75,21 +76,18 @@ class CoverRestorer @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreBookmark(bookmark: Bookmark, request: ImageRequest) {
|
||||
private suspend fun restoreBookmark(bookmark: Bookmark): Boolean {
|
||||
val key = bookmark.imageUrl
|
||||
if (key in blacklist) {
|
||||
return
|
||||
if (!blacklist.add(key)) {
|
||||
return false
|
||||
}
|
||||
request.lifecycle.coroutineScope.launch {
|
||||
val restored = runCatchingCancellable {
|
||||
restoreBookmarkImpl(bookmark)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
request.newBuilder().enqueueWith(coilProvider.get())
|
||||
} else {
|
||||
blacklist.add(key)
|
||||
}
|
||||
val restored = runCatchingCancellable {
|
||||
restoreBookmarkImpl(bookmark)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
blacklist.remove(key)
|
||||
}
|
||||
return restored
|
||||
}
|
||||
|
||||
private suspend fun restoreBookmarkImpl(bookmark: Bookmark): Boolean {
|
||||
Reference in New Issue
Block a user