diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt index f2e2712cd7..54d7d1643b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -4,8 +4,8 @@ import android.content.Context import android.text.format.Formatter import co.touchlab.kermit.Logger import coil3.imageLoader -import coil3.memory.MemoryCache import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.updateCoverLastModified import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.e @@ -71,7 +71,10 @@ class CoverCache(val context: Context) { val db = Injekt.get() var deletedSize = 0L val urls = db.getFavoriteMangas().executeOnIO().mapNotNull { - it.thumbnail_url?.let { url -> return@mapNotNull DiskUtil.hashKeyForDisk(url) } + it.thumbnail_url?.let { url -> + it.updateCoverLastModified() + return@mapNotNull DiskUtil.hashKeyForDisk(url) + } null } val files = cacheDir.listFiles()?.iterator() ?: return @@ -170,7 +173,6 @@ class CoverCache(val context: Context) { fun setCustomCoverToCache(manga: Manga, inputStream: InputStream) { getCustomCoverFile(manga).outputStream().use { inputStream.copyTo(it) - removeFromMemory(manga, true) } } @@ -184,14 +186,13 @@ class CoverCache(val context: Context) { val result = getCustomCoverFile(manga).let { it.exists() && it.delete() } - removeFromMemory(manga, true) return result } /** * Returns the cover from cache. * - * @param thumbnailUrl the thumbnail url. + * @param mangaThumbnailUrl the thumbnail url. * @return cover image. */ fun getCoverFile(mangaThumbnailUrl: String?, isOnline: Boolean = false): File? { @@ -200,26 +201,6 @@ class CoverCache(val context: Context) { } } - fun removeFromMemory(manga: Manga, custom: Boolean = false) { - if (custom) { - context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key())) - return - } - - manga.thumbnail_url?.let { - if (it.isEmpty()) return - context.imageLoader.memoryCache - ?.remove(MemoryCache.Key(if (!manga.favorite) it else DiskUtil.hashKeyForDisk(it))) - } - } - - fun deleteFromCache(name: String?) { - if (name.isNullOrEmpty()) return - val file = getCoverFile(name, true) ?: return - context.imageLoader.memoryCache?.remove(MemoryCache.Key(file.name)) - if (file.exists()) file.delete() - } - /** * Delete the cover file from the disk cache and optional from memory cache * @@ -235,8 +216,7 @@ class CoverCache(val context: Context) { // Remove file getCoverFile(manga.thumbnail_url, !manga.favorite)?.let { - removeFromMemory(manga) - it.delete() + if (it.exists()) it.delete() } if (deleteCustom) deleteCustomCover(manga) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/LibraryMangaImageTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/LibraryMangaImageTarget.kt index f6fa9772b4..bae6a8c78f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/LibraryMangaImageTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/LibraryMangaImageTarget.kt @@ -10,6 +10,7 @@ import coil3.request.Disposable import coil3.request.ImageRequest import coil3.target.ImageViewTarget import eu.kanade.tachiyomi.data.cache.CoverCache +import eu.kanade.tachiyomi.data.database.models.updateCoverLastModified import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.util.system.launchIO import uy.kohesive.injekt.injectLazy @@ -32,7 +33,7 @@ class LibraryMangaImageTarget( options.inJustDecodeBounds = true BitmapFactory.decodeFile(file.path, options) if (options.outWidth == -1 || options.outHeight == -1) { - coverCache.removeFromMemory(manga) + manga.updateCoverLastModified() file.delete() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt index e304173474..3c79c5a814 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt @@ -9,13 +9,15 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil class MangaCoverKeyer : Keyer { override fun key(data: Manga, options: Options): String? { val hasCustomCover by lazy { data.hasCustomCover() } + val suffix by lazy { ";${data.cover_last_modified}" } + if (data.thumbnail_url.isNullOrBlank() && !hasCustomCover) return null - if (hasCustomCover) return data.key() + if (hasCustomCover) return "${data.id}$suffix" return if (!data.favorite) { data.thumbnail_url!! } else { DiskUtil.hashKeyForDisk(data.thumbnail_url!!) - } + } + suffix } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt index caaba0e101..2b82cf6c94 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.data.database +import android.database.Cursor import com.pushtorefresh.storio.sqlite.StorIOSQLite inline fun StorIOSQLite.inTransaction(block: () -> Unit) { @@ -22,3 +23,5 @@ inline fun StorIOSQLite.inTransactionReturn(block: () -> T): T { lowLevel().endTransaction() } } + +fun Cursor.getBoolean(index: Int) = getLong(index) > 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt index 7e62c5cd16..81775f4e86 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.data.database.mappers +import android.annotation.SuppressLint import android.content.ContentValues import android.database.Cursor import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping @@ -9,10 +10,12 @@ import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.InsertQuery import com.pushtorefresh.storio.sqlite.queries.UpdateQuery -import eu.kanade.tachiyomi.data.database.models.MangaImpl +import eu.kanade.tachiyomi.data.database.getBoolean +import eu.kanade.tachiyomi.data.database.models.mapper import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS +import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFIED import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE @@ -59,8 +62,8 @@ class MangaPutResolver : DefaultPutResolver() { put(COL_AUTHOR, obj.originalAuthor) put(COL_DESCRIPTION, obj.originalDescription) put(COL_GENRE, obj.originalGenre) - put(COL_TITLE, obj.originalTitle) - put(COL_STATUS, obj.originalStatus) + put(COL_TITLE, obj.ogTitle) + put(COL_STATUS, obj.ogStatus) put(COL_THUMBNAIL_URL, obj.thumbnail_url) put(COL_FAVORITE, obj.favorite) put(COL_LAST_UPDATE, obj.last_update) @@ -70,40 +73,41 @@ class MangaPutResolver : DefaultPutResolver() { put(COL_CHAPTER_FLAGS, obj.chapter_flags) put(COL_DATE_ADDED, obj.date_added) put(COL_FILTERED_SCANLATORS, obj.filtered_scanlators) - put(COL_UPDATE_STRATEGY, obj.update_strategy.let(updateStrategyAdapter::encode).toInt()) + put(COL_UPDATE_STRATEGY, obj.update_strategy.let(updateStrategyAdapter::encode)) + put(COL_COVER_LAST_MODIFIED, obj.cover_last_modified) } } interface BaseMangaGetResolver { - fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply { - id = cursor.getLong(cursor.getColumnIndex(COL_ID)) - source = cursor.getLong(cursor.getColumnIndex(COL_SOURCE)) - url = cursor.getString(cursor.getColumnIndex(COL_URL)) - artist = cursor.getString(cursor.getColumnIndex(COL_ARTIST)) - author = cursor.getString(cursor.getColumnIndex(COL_AUTHOR)) - description = cursor.getString(cursor.getColumnIndex(COL_DESCRIPTION)) - genre = cursor.getString(cursor.getColumnIndex(COL_GENRE)) - title = cursor.getString(cursor.getColumnIndex(COL_TITLE)) - status = cursor.getInt(cursor.getColumnIndex(COL_STATUS)) - thumbnail_url = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL)) - favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1 - last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE)) - initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1 - viewer_flags = cursor.getInt(cursor.getColumnIndex(COL_VIEWER)) - chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS)) - hide_title = cursor.getInt(cursor.getColumnIndex(COL_HIDE_TITLE)) == 1 - date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED)) - filtered_scanlators = cursor.getString(cursor.getColumnIndex(COL_FILTERED_SCANLATORS)) - update_strategy = cursor.getInt(cursor.getColumnIndex(COL_UPDATE_STRATEGY)).let { - updateStrategyAdapter.decode(it.toLong()) - } - } + @SuppressLint("Range") + fun mapBaseFromCursor(cursor: Cursor) = Manga.mapper( + id = cursor.getLong(cursor.getColumnIndex(COL_ID)), + source = cursor.getLong(cursor.getColumnIndex(COL_SOURCE)), + url = cursor.getString(cursor.getColumnIndex(COL_URL)), + artist = cursor.getString(cursor.getColumnIndex(COL_ARTIST)), + author = cursor.getString(cursor.getColumnIndex(COL_AUTHOR)), + description = cursor.getString(cursor.getColumnIndex(COL_DESCRIPTION)), + genre = cursor.getString(cursor.getColumnIndex(COL_GENRE)), + title = cursor.getString(cursor.getColumnIndex(COL_TITLE)), + status = cursor.getLong(cursor.getColumnIndex(COL_STATUS)), + thumbnailUrl = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL)), + favorite = cursor.getBoolean(cursor.getColumnIndex(COL_FAVORITE)), + lastUpdate = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE)), + initialized = cursor.getBoolean(cursor.getColumnIndex(COL_INITIALIZED)), + viewerFlags = cursor.getLong(cursor.getColumnIndex(COL_VIEWER)), + chapterFlags = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_FLAGS)), + hideTitle = cursor.getBoolean(cursor.getColumnIndex(COL_HIDE_TITLE)), + dateAdded = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED)), + filteredScanlators = cursor.getString(cursor.getColumnIndex(COL_FILTERED_SCANLATORS)), + updateStrategy = cursor.getLong(cursor.getColumnIndex(COL_UPDATE_STRATEGY)), + coverLastModified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED)), + ) } open class MangaGetResolver : DefaultGetResolver(), BaseMangaGetResolver { override fun mapFromCursor(cursor: Cursor): Manga { - return mapBaseFromCursor(MangaImpl(), cursor) + return mapBaseFromCursor(cursor) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt index f6044fc927..e017568209 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.data.database.models import eu.kanade.tachiyomi.ui.library.LibraryItem -import yokai.data.updateStrategyAdapter import kotlin.math.roundToInt +import yokai.data.updateStrategyAdapter data class LibraryManga( var unread: Int = 0, @@ -49,6 +49,7 @@ data class LibraryManga( } fun mapper( + // manga id: Long, source: Long, url: String, @@ -59,15 +60,17 @@ data class LibraryManga( title: String, status: Long, thumbnailUrl: String?, - favorite: Long, + favorite: Boolean, lastUpdate: Long?, initialized: Boolean, viewerFlags: Long, - hideTitle: Long, + hideTitle: Boolean, chapterFlags: Long, dateAdded: Long?, filteredScanlators: String?, updateStrategy: Long, + coverLastModified: Long, + // libraryManga total: Long, readCount: Double, bookmarkCount: Double, @@ -86,15 +89,16 @@ data class LibraryManga( this.title = title this.status = status.toInt() this.thumbnail_url = thumbnailUrl - this.favorite = favorite > 0 + this.favorite = favorite this.last_update = lastUpdate ?: 0L this.initialized = initialized this.viewer_flags = viewerFlags.toInt() - this.hide_title = hideTitle > 0 + this.hide_title = hideTitle this.chapter_flags = chapterFlags.toInt() this.date_added = dateAdded ?: 0L this.filtered_scanlators = filteredScanlators this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode) + this.cover_last_modified = coverLastModified this.read = readCount.roundToInt() this.unread = maxOf((total - readCount).roundToInt(), 0) this.totalChapters = total.toInt() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt similarity index 88% rename from app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index 375a756794..56b68adbc1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.ui.reader.settings.OrientationType import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType +import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata import eu.kanade.tachiyomi.util.system.withIOContext import java.util.Locale @@ -21,6 +22,8 @@ import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import yokai.data.updateStrategyAdapter import yokai.domain.chapter.interactor.GetChapter +import yokai.domain.manga.interactor.UpdateManga +import yokai.domain.manga.models.MangaUpdate import yokai.i18n.MR import yokai.util.lang.getString @@ -193,15 +196,16 @@ fun Manga.Companion.mapper( title: String, status: Long, thumbnailUrl: String?, - favorite: Long, + favorite: Boolean, lastUpdate: Long?, initialized: Boolean, viewerFlags: Long, - hideTitle: Long, + hideTitle: Boolean, chapterFlags: Long, dateAdded: Long?, filteredScanlators: String?, - updateStrategy: Long + updateStrategy: Long, + coverLastModified: Long, ) = create(source).apply { this.id = id this.url = url @@ -212,17 +216,41 @@ fun Manga.Companion.mapper( this.title = title this.status = status.toInt() this.thumbnail_url = thumbnailUrl - this.favorite = favorite > 0 + this.favorite = favorite this.last_update = lastUpdate ?: 0L this.initialized = initialized this.viewer_flags = viewerFlags.toInt() this.chapter_flags = chapterFlags.toInt() - this.hide_title = hideTitle > 0 + this.hide_title = hideTitle this.date_added = dateAdded ?: 0L this.filtered_scanlators = filteredScanlators this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode) + this.cover_last_modified = coverLastModified } fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean { return coverCache.getCustomCoverFile(this).exists() } + +/** + * Call before updating [Manga.thumbnail_url] to ensure old cover can be cleared from cache + */ +fun Manga.prepareCoverUpdate(coverCache: CoverCache = Injekt.get()) { + cover_last_modified = System.currentTimeMillis() + + if (!isLocal()) { + coverCache.deleteFromCache(this, true) + } +} + +fun Manga.removeCover(coverCache: CoverCache = Injekt.get(), deleteCustom: Boolean = true) { + if (isLocal()) return + + cover_last_modified = System.currentTimeMillis() + coverCache.deleteFromCache(this, deleteCustom) +} + +suspend fun Manga.updateCoverLastModified(updateManga: UpdateManga = Injekt.get()) { + cover_last_modified = System.currentTimeMillis() + updateManga.await(MangaUpdate(id = id!!, coverLastModified = cover_last_modified)) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt index f4136aecdf..adef88b7a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga class MangaChapter(val manga: Manga, val chapter: Chapter) { companion object { fun mapper( + // manga mangaId: Long, source: Long, mangaUrl: String, @@ -15,15 +16,17 @@ class MangaChapter(val manga: Manga, val chapter: Chapter) { title: String, status: Long, thumbnailUrl: String?, - favorite: Long, + favorite: Boolean, lastUpdate: Long?, initialized: Boolean, viewer: Long, - hideTitle: Long, + hideTitle: Boolean, chapterFlags: Long, dateAdded: Long?, filteredScanlators: String?, updateStrategy: Long, + coverLastModified: Long, + // chapter chapterId: Long, _mangaId: Long, chapterUrl: String, @@ -58,6 +61,7 @@ class MangaChapter(val manga: Manga, val chapter: Chapter) { dateAdded = dateAdded, filteredScanlators = filteredScanlators, updateStrategy = updateStrategy, + coverLastModified = coverLastModified, ), Chapter.mapper( id = chapterId, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt index c2aba9b0f7..63cccb151e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt @@ -81,6 +81,8 @@ open class MangaImpl : Manga { override var ogGenre: String? = null override var ogStatus: Int = 0 + override var cover_last_modified: Long = 0L + override fun copyFrom(other: SManga) { if (other is MangaImpl && other::ogTitle.isInitialized && other.title.isNotBlank() && other.ogTitle != ogTitle diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt index 3c045def9f..351d99f736 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt @@ -42,4 +42,5 @@ object MangaTable { const val COL_UPDATE_STRATEGY = "update_strategy" + const val COL_COVER_LAST_MODIFIED = "cover_last_modified" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index 997bcd1087..ecb9a6a026 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.LibraryManga +import eu.kanade.tachiyomi.data.database.models.prepareCoverUpdate import eu.kanade.tachiyomi.data.download.DownloadJob import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.notification.Notifications @@ -55,6 +56,12 @@ import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.localeContext import eu.kanade.tachiyomi.util.system.tryToSetForeground import eu.kanade.tachiyomi.util.system.withIOContext +import java.io.File +import java.lang.ref.WeakReference +import java.util.Date +import java.util.concurrent.CancellationException +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers @@ -79,11 +86,6 @@ import yokai.domain.manga.interactor.GetLibraryManga import yokai.domain.manga.interactor.UpdateManga import yokai.i18n.MR import yokai.util.lang.getString -import java.io.File -import java.lang.ref.WeakReference -import java.util.* -import java.util.concurrent.* -import java.util.concurrent.atomic.* class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { @@ -267,11 +269,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } if (networkManga != null) { val thumbnailUrl = manga.thumbnail_url + if (thumbnailUrl != networkManga.thumbnail_url) { + manga.prepareCoverUpdate() + } manga.copyFrom(networkManga) manga.initialized = true val request: ImageRequest = if (thumbnailUrl != manga.thumbnail_url) { - coverCache.deleteFromCache(thumbnailUrl) // load new covers in background ImageRequest.Builder(context).data(manga) .memoryCachePolicy(CachePolicy.DISABLED).build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index fd0c118f98..fe64335040 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter.Companion.copy import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.Track +import eu.kanade.tachiyomi.data.database.models.removeCover import eu.kanade.tachiyomi.data.database.models.seriesType import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.model.Download @@ -1260,7 +1261,7 @@ class LibraryPresenter( val mangaToDelete = mangas.distinctBy { it.id } mangaToDelete.forEach { manga -> if (coverCacheToo) { - coverCache.deleteFromCache(manga) + manga.removeCover(coverCache) } val source = sourceManager.get(manga.source) as? HttpSource if (source != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index 9b5ccd9f3d..e3761e5847 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -20,9 +20,11 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.bookmarkedFilter import eu.kanade.tachiyomi.data.database.models.chapterOrder import eu.kanade.tachiyomi.data.database.models.downloadedFilter -import eu.kanade.tachiyomi.data.database.models.hasCustomCover +import eu.kanade.tachiyomi.data.database.models.prepareCoverUpdate import eu.kanade.tachiyomi.data.database.models.readFilter +import eu.kanade.tachiyomi.data.database.models.removeCover import eu.kanade.tachiyomi.data.database.models.sortDescending +import eu.kanade.tachiyomi.data.database.models.updateCoverLastModified import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.DownloadQueue @@ -59,6 +61,7 @@ import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.launchIO +import eu.kanade.tachiyomi.util.system.launchNonCancellableIO import eu.kanade.tachiyomi.util.system.launchNow import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.withIOContext @@ -375,7 +378,6 @@ class MangaDetailsPresenter( emptyList() } } - val thumbnailUrl = manga.thumbnail_url val nManga = async(Dispatchers.IO) { try { source.getMangaDetails(manga.copy()) @@ -387,12 +389,13 @@ class MangaDetailsPresenter( val networkManga = nManga.await() if (networkManga != null) { + if (manga.thumbnail_url != networkManga.thumbnail_url) { + manga.prepareCoverUpdate() + } + manga.copyFrom(networkManga) manga.initialized = true - if (thumbnailUrl != networkManga.thumbnail_url) { - coverCache.deleteFromCache(thumbnailUrl) - } db.insertManga(manga).executeAsBlocking() launchIO { @@ -403,7 +406,6 @@ class MangaDetailsPresenter( .build() if (preferences.context.imageLoader.execute(request) is SuccessResult) { - coverCache.removeFromMemory(manga, manga.hasCustomCover(coverCache)) withContext(Dispatchers.Main) { view?.setPaletteColor() } @@ -735,7 +737,7 @@ class MangaDetailsPresenter( fun confirmDeletion() { launchIO { - coverCache.deleteFromCache(manga) + manga.removeCover(coverCache) customMangaManager.saveMangaInfo(CustomMangaInfo( mangaId = manga.id!!, title = null, @@ -858,6 +860,7 @@ class MangaDetailsPresenter( editCoverWithStream(uri) } else if (resetCover) { coverCache.deleteCustomCover(manga) + presenterScope.launchIO { manga.updateCoverLastModified() } view?.setPaletteColor() } view?.updateHeader() @@ -881,12 +884,14 @@ class MangaDetailsPresenter( downloadManager.context.contentResolver.openInputStream(uri) ?: return false if (manga.isLocal()) { LocalSource.updateCover(manga, inputStream) + presenterScope.launchNonCancellableIO { manga.updateCoverLastModified() } view?.setPaletteColor() return true } if (manga.favorite) { coverCache.setCustomCoverToCache(manga, inputStream) + presenterScope.launchNonCancellableIO { manga.updateCoverLastModified() } view?.setPaletteColor() return true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt index fe21eab268..1cac676223 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt @@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.MangaCategory +import eu.kanade.tachiyomi.data.database.models.updateCoverLastModified import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.EnhancedTrackService @@ -229,6 +230,7 @@ class MigrationProcessAdapter( if (MigrationFlags.hasCustomMangaInfo(flags)) { if (coverCache.getCustomCoverFile(prevManga).exists()) { coverCache.setCustomCoverToCache(manga, coverCache.getCustomCoverFile(prevManga).inputStream()) + launchNow { manga.updateCoverLastModified() } } customMangaManager.getManga(prevManga)?.let { customManga -> launchNow { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 3f5f9f6721..b45ceab36e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.defaultReaderType import eu.kanade.tachiyomi.data.database.models.orientationType import eu.kanade.tachiyomi.data.database.models.readingModeType +import eu.kanade.tachiyomi.data.database.models.updateCoverLastModified import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.download.model.Download @@ -938,14 +939,15 @@ class ReaderViewModel( viewModelScope.launchNonCancellableIO { val result = try { if (manga.isLocal()) { - val context = Injekt.get() coverCache.deleteFromCache(manga) LocalSource.updateCover(manga, stream()) + manga.updateCoverLastModified() MR.strings.cover_updated SetAsCoverResult.Success } else { if (manga.favorite) { coverCache.setCustomCoverToCache(manga, stream()) + manga.updateCoverLastModified() SetAsCoverResult.Success } else { SetAsCoverResult.AddToLibraryFirst diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt index d666fad8f7..7f75937849 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt @@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.create +import eu.kanade.tachiyomi.data.database.models.removeCover import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.domain.manga.models.Manga @@ -283,7 +284,7 @@ open class BrowseSourcePresenter( fun confirmDeletion(manga: Manga) { launchIO { - coverCache.deleteFromCache(manga) + manga.removeCover(coverCache) val downloadManager: DownloadManager = Injekt.get() downloadManager.deleteManga(manga, source) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt index 115e42699c..982d84bcde 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.source.globalsearch import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.create +import eu.kanade.tachiyomi.data.database.models.removeCover import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.domain.manga.models.Manga @@ -16,6 +17,8 @@ import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.withUIContext +import java.util.Date +import java.util.Locale import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.launchIn @@ -26,7 +29,6 @@ import kotlinx.coroutines.sync.withPermit import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.util.* /** * Presenter of [GlobalSearchController] @@ -138,7 +140,7 @@ open class GlobalSearchPresenter( } fun confirmDeletion(manga: Manga) { - coverCache.deleteFromCache(manga) + manga.removeCover(coverCache) val downloadManager: DownloadManager = Injekt.get() sourceManager.get(manga.source)?.let { source -> downloadManager.deleteManga(manga, source) diff --git a/app/src/main/java/yokai/data/manga/MangaRepositoryImpl.kt b/app/src/main/java/yokai/data/manga/MangaRepositoryImpl.kt index acaed478ff..c4adc8091a 100644 --- a/app/src/main/java/yokai/data/manga/MangaRepositoryImpl.kt +++ b/app/src/main/java/yokai/data/manga/MangaRepositoryImpl.kt @@ -4,7 +4,6 @@ import co.touchlab.kermit.Logger import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.mapper import eu.kanade.tachiyomi.domain.manga.models.Manga -import eu.kanade.tachiyomi.util.system.toInt import kotlinx.coroutines.flow.Flow import yokai.data.DatabaseHandler import yokai.data.updateStrategyAdapter @@ -66,15 +65,16 @@ class MangaRepositoryImpl(private val handler: DatabaseHandler) : MangaRepositor title = update.title, status = update.status?.toLong(), thumbnailUrl = update.thumbnailUrl, - favorite = update.favorite?.toInt()?.toLong(), + favorite = update.favorite, lastUpdate = update.lastUpdate, initialized = update.initialized, viewer = update.viewerFlags?.toLong(), - hideTitle = update.hideTitle?.toInt()?.toLong(), + hideTitle = update.hideTitle, chapterFlags = update.chapterFlags?.toLong(), dateAdded = update.dateAdded, filteredScanlators = update.filteredScanlators, updateStrategy = update.updateStrategy?.let(updateStrategyAdapter::encode), + coverLastModified = update.coverLastModified, mangaId = update.id, ) } @@ -93,15 +93,16 @@ class MangaRepositoryImpl(private val handler: DatabaseHandler) : MangaRepositor title = manga.title, status = manga.status.toLong(), thumbnailUrl = manga.thumbnail_url, - favorite = manga.favorite.toInt().toLong(), + favorite = manga.favorite, lastUpdate = manga.last_update, initialized = manga.initialized, viewer = manga.viewer_flags.toLong(), - hideTitle = manga.hide_title.toInt().toLong(), + hideTitle = manga.hide_title, chapterFlags = manga.chapter_flags.toLong(), dateAdded = manga.date_added, filteredScanlators = manga.filtered_scanlators, updateStrategy = manga.update_strategy.let(updateStrategyAdapter::encode), + coverLastModified = manga.cover_last_modified, ) mangasQueries.selectLastInsertedRowId() } diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt index 4bb5215d2f..d4989d31e9 100644 --- a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt +++ b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch diff --git a/data/src/commonMain/sqldelight/tachiyomi/data/mangas.sq b/data/src/commonMain/sqldelight/tachiyomi/data/mangas.sq index 0f3d00a9b4..a09e361328 100644 --- a/data/src/commonMain/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/commonMain/sqldelight/tachiyomi/data/mangas.sq @@ -1,5 +1,4 @@ import kotlin.Boolean; -import kotlin.Long; CREATE TABLE mangas( _id INTEGER NOT NULL PRIMARY KEY, @@ -12,15 +11,16 @@ CREATE TABLE mangas( title TEXT NOT NULL, status INTEGER NOT NULL, thumbnail_url TEXT, - favorite INTEGER NOT NULL, - last_update INTEGER AS Long, + favorite INTEGER AS Boolean NOT NULL, + last_update INTEGER, initialized INTEGER AS Boolean NOT NULL, viewer INTEGER NOT NULL, - hide_title INTEGER NOT NULL, + hide_title INTEGER AS Boolean NOT NULL, chapter_flags INTEGER NOT NULL, - date_added INTEGER AS Long, + date_added INTEGER, filtered_scanlators TEXT, - update_strategy INTEGER NOT NULL DEFAULT 0 + update_strategy INTEGER NOT NULL DEFAULT 0, + cover_last_modified INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX mangas_url_index ON mangas(url); @@ -51,8 +51,8 @@ FROM mangas WHERE favorite = 1; insert: -INSERT INTO mangas (source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, initialized, viewer, hide_title, chapter_flags, date_added, filtered_scanlators, update_strategy) -VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :initialized, :viewer, :hideTitle, :chapterFlags, :dateAdded, :filteredScanlators, :updateStrategy); +INSERT INTO mangas (source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, initialized, viewer, hide_title, chapter_flags, date_added, filtered_scanlators, update_strategy, cover_last_modified) +VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :initialized, :viewer, :hideTitle, :chapterFlags, :dateAdded, :filteredScanlators, :updateStrategy, :coverLastModified); update: UPDATE mangas SET @@ -73,7 +73,8 @@ UPDATE mangas SET chapter_flags = coalesce(:chapterFlags, chapter_flags), date_added = coalesce(:dateAdded, date_added), filtered_scanlators = coalesce(:filteredScanlators, filtered_scanlators), - update_strategy = coalesce(:updateStrategy, update_strategy) + update_strategy = coalesce(:updateStrategy, update_strategy), + cover_last_modified = coalesce(:coverLastModified, cover_last_modified) WHERE _id = :mangaId; selectLastInsertedRowId: diff --git a/data/src/commonMain/sqldelight/tachiyomi/migrations/26.sqm b/data/src/commonMain/sqldelight/tachiyomi/migrations/26.sqm new file mode 100644 index 0000000000..146f25c664 --- /dev/null +++ b/data/src/commonMain/sqldelight/tachiyomi/migrations/26.sqm @@ -0,0 +1,2 @@ +ALTER TABLE mangas +ADD COLUMN cover_last_modified INTEGER NOT NULL DEFAULT 0; diff --git a/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt b/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt index 78ce08b469..7f4c3eb9e3 100644 --- a/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt +++ b/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt @@ -1,9 +1,9 @@ package eu.kanade.tachiyomi.domain.manga.models import eu.kanade.tachiyomi.source.model.SManga -import yokai.domain.manga.models.MangaUpdate -import java.util.* +import java.util.Locale import kotlin.collections.set +import yokai.domain.manga.models.MangaUpdate // TODO: Transform into data class interface Manga : SManga { @@ -33,6 +33,8 @@ interface Manga : SManga { var ogGenre: String? var ogStatus: Int + var cover_last_modified: Long + @Deprecated("Use ogTitle directly instead", ReplaceWith("ogTitle")) val originalTitle: String get() = ogTitle diff --git a/domain/src/commonMain/kotlin/yokai/domain/manga/models/MangaUpdate.kt b/domain/src/commonMain/kotlin/yokai/domain/manga/models/MangaUpdate.kt index b8852c5080..eea83fe940 100644 --- a/domain/src/commonMain/kotlin/yokai/domain/manga/models/MangaUpdate.kt +++ b/domain/src/commonMain/kotlin/yokai/domain/manga/models/MangaUpdate.kt @@ -22,4 +22,5 @@ data class MangaUpdate( var chapterFlags: Int? = null, var hideTitle: Boolean? = null, var filteredScanlators: String? = null, + var coverLastModified: Long? = null, )