Restore broken covers and bookmark previews

This commit is contained in:
Koitharu
2023-08-18 16:52:14 +03:00
parent df04dcc8a3
commit ead8a3d6df
10 changed files with 141 additions and 3 deletions

View File

@@ -52,6 +52,13 @@ class BookmarksRepository @Inject constructor(
}
}
suspend fun updateBookmark(bookmark: Bookmark, imageUrl: String) {
val entity = bookmark.toEntity().copy(
imageUrl = imageUrl,
)
db.bookmarksDao.upsert(listOf(entity))
}
suspend fun removeBookmark(mangaId: Long, chapterId: Long, page: Int) {
check(db.bookmarksDao.delete(mangaId, chapterId, page) != 0) {
"Bookmark not found"

View File

@@ -34,6 +34,7 @@ fun bookmarkListAD(
fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder)
allowRgb565(true)
tag(item)
decodeRegion(item.scroll)
source(item.manga.source)
enqueueWith(coil)

View File

@@ -35,6 +35,7 @@ fun bookmarkLargeAD(
fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder)
allowRgb565(true)
tag(item)
decodeRegion(item.scroll)
source(item.manga.source)
enqueueWith(coil)

View File

@@ -13,6 +13,7 @@ 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
@@ -46,6 +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.ui.protect.AppProtectHelper
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.reader.ui.thumbnails.MangaPageFetcher
@@ -89,6 +91,7 @@ interface AppModule {
mangaRepositoryFactory: MangaRepository.Factory,
imageProxyInterceptor: ImageProxyInterceptor,
pageFetcherFactory: MangaPageFetcher.Factory,
coverRestorerProvider: Lazy<CoverRestorer>,
): ImageLoader {
val diskCacheFactory = {
val rootDir = context.externalCacheDir ?: context.cacheDir
@@ -105,6 +108,7 @@ interface AppModule {
.diskCache(diskCacheFactory)
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
.allowRgb565(context.isLowRamDevice())
.eventListenerFactory { coverRestorerProvider.get() }
.components(
ComponentRegistry.Builder()
.add(SvgDecoder.Factory())

View File

@@ -111,6 +111,11 @@ class RemoteMangaRepository(
return details.await()
}
suspend fun find(manga: Manga): Manga? {
val list = getList(0, manga.title)
return list.find { x -> x.id == manga.id }
}
fun getAuthProvider(): MangaParserAuthProvider? = parser as? MangaParserAuthProvider
fun getConfigKeys(): List<ConfigKey<*>> = ArrayList<ConfigKey<*>>().also {

View File

@@ -29,7 +29,7 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
.data(data)
.lifecycle(lifecycleOwner)
.crossfade(context)
.listener(CaptchaNotifier(context.applicationContext))
.addListener(CaptchaNotifier(context.applicationContext))
.target(this)
}
@@ -65,11 +65,11 @@ fun ImageResult.toBitmapOrNull() = when (this) {
}
fun ImageRequest.Builder.indicator(indicator: BaseProgressIndicator<*>): ImageRequest.Builder {
return listener(ImageRequestIndicatorListener(listOf(indicator)))
return addListener(ImageRequestIndicatorListener(listOf(indicator)))
}
fun ImageRequest.Builder.indicator(indicators: List<BaseProgressIndicator<*>>): ImageRequest.Builder {
return listener(ImageRequestIndicatorListener(indicators))
return addListener(ImageRequestIndicatorListener(indicators))
}
fun ImageRequest.Builder.decodeRegion(
@@ -86,3 +86,30 @@ fun ImageRequest.Builder.crossfade(context: Context): ImageRequest.Builder {
fun ImageRequest.Builder.source(source: MangaSource?): ImageRequest.Builder {
return tag(MangaSource::class.java, source)
}
fun ImageRequest.Builder.addListener(listener: ImageRequest.Listener): ImageRequest.Builder {
val existing = build().listener
return listener(
when (existing) {
null -> listener
is CompositeImageRequestListener -> existing + listener
else -> CompositeImageRequestListener(arrayOf(existing, listener))
},
)
}
private class CompositeImageRequestListener(
private val delegates: Array<ImageRequest.Listener>,
) : ImageRequest.Listener {
override fun onCancel(request: ImageRequest) = delegates.forEach { it.onCancel(request) }
override fun onError(request: ImageRequest, result: ErrorResult) = delegates.forEach { it.onError(request, result) }
override fun onStart(request: ImageRequest) = delegates.forEach { it.onStart(request) }
override fun onSuccess(request: ImageRequest, result: SuccessResult) =
delegates.forEach { it.onSuccess(request, result) }
operator fun plus(other: ImageRequest.Listener) = CompositeImageRequestListener(delegates + other)
}

View File

@@ -48,6 +48,7 @@ fun mangaGridItemAD(
error(R.drawable.ic_error_placeholder)
transformations(TrimTransformation())
allowRgb565(true)
tag(item.manga)
source(item.source)
enqueueWith(coil)
}

View File

@@ -61,6 +61,7 @@ fun mangaListDetailedItemAD(
error(R.drawable.ic_error_placeholder)
transformations(TrimTransformation())
allowRgb565(true)
tag(item.manga)
source(item.source)
enqueueWith(coil)
}

View File

@@ -42,6 +42,7 @@ fun mangaListItemAD(
error(R.drawable.ic_error_placeholder)
allowRgb565(true)
transformations(TrimTransformation())
tag(item.manga)
source(item.source)
enqueueWith(coil)
}

View File

@@ -0,0 +1,90 @@
package org.koitharu.kotatsu.main.domain
import androidx.lifecycle.coroutineScope
import coil.EventListener
import coil.ImageLoader
import coil.network.HttpException
import coil.request.ErrorResult
import coil.request.ImageRequest
import kotlinx.coroutines.launch
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.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import javax.inject.Inject
import javax.inject.Provider
class CoverRestorer @Inject constructor(
private val dataRepository: MangaDataRepository,
private val bookmarksRepository: BookmarksRepository,
private val repositoryFactory: MangaRepository.Factory,
private val coilProvider: Provider<ImageLoader>,
) : EventListener {
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
if (result.throwable !is HttpException) {
return
}
request.tags.tag<Manga>()?.let {
restoreManga(it, request)
}
request.tags.tag<Bookmark>()?.let {
restoreBookmark(it, request)
}
}
private fun restoreManga(manga: Manga, request: ImageRequest) {
request.lifecycle.coroutineScope.launch {
val restored = runCatchingCancellable {
restoreMangaImpl(manga)
}.getOrDefault(false)
if (restored) {
request.newBuilder().enqueueWith(coilProvider.get())
}
}
}
private suspend fun restoreMangaImpl(manga: Manga): Boolean {
if (dataRepository.findMangaById(manga.id) == null) {
return false
}
val repo = repositoryFactory.create(manga.source) as? RemoteMangaRepository ?: return false
val fixed = repo.find(manga) ?: return false
return if (fixed != manga) {
dataRepository.storeManga(fixed)
fixed.coverUrl != manga.coverUrl
} else {
false
}
}
private fun restoreBookmark(bookmark: Bookmark, request: ImageRequest) {
request.lifecycle.coroutineScope.launch {
val restored = runCatchingCancellable {
restoreBookmarkImpl(bookmark)
}.getOrDefault(false)
if (restored) {
request.newBuilder().enqueueWith(coilProvider.get())
}
}
}
private suspend fun restoreBookmarkImpl(bookmark: Bookmark): Boolean {
val repo = repositoryFactory.create(bookmark.manga.source) as? RemoteMangaRepository ?: return false
val chapter = repo.getDetails(bookmark.manga).chapters?.find { it.id == bookmark.chapterId } ?: return false
val page = repo.getPages(chapter)[bookmark.page]
val imageUrl = page.preview.ifNullOrEmpty { page.url }
return if (imageUrl != bookmark.imageUrl) {
bookmarksRepository.updateBookmark(bookmark, imageUrl)
true
} else {
false
}
}
}