From 2684a7384e3305ccb25fa84a18d58e13c09960c9 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 26 Aug 2023 16:44:09 +0300 Subject: [PATCH] Restore covers using interceptor --- .../browser/cloudflare/CaptchaNotifier.kt | 10 +- .../org/koitharu/kotatsu/core/AppModule.kt | 9 +- .../koitharu/kotatsu/core/util/ext/Coil.kt | 2 - ...Restorer.kt => CoverRestoreInterceptor.kt} | 96 +++++++++---------- 4 files changed, 57 insertions(+), 60 deletions(-) rename app/src/main/kotlin/org/koitharu/kotatsu/main/domain/{CoverRestorer.kt => CoverRestoreInterceptor.kt} (59%) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt index 04a76345b..65a4966ea 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt @@ -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) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt index f44b8c440..e19266023 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt @@ -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, + 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() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt index 69735b012..67e07d63d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt @@ -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) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestorer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt similarity index 59% rename from app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestorer.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt index fdcafb2d5..9aef8c91d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestorer.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt @@ -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, -) : EventListener { +) : Interceptor { - private val blacklist = ArraySet() + private val blacklist = Collections.synchronizedSet(ArraySet()) - override fun onError(request: ImageRequest, result: ErrorResult) { - super.onError(request, result) - if (!result.throwable.shouldRestore()) { - return - } - request.tags.tag()?.let { - restoreManga(it, request) - } - request.tags.tag()?.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()?.let { + if (restoreManga(it)) { + return chain.proceed(request.newBuilder().build()) + } else { + return result + } + } + request.tags.tag()?.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 {