From 22e2411c77fee8300bad02a090517b4312c72711 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 20 Dec 2024 11:21:49 +0200 Subject: [PATCH] Cache chapters list (close #812) --- .../alternatives/domain/AutoFixUseCase.kt | 5 +-- .../koitharu/kotatsu/core/db/MangaDatabase.kt | 16 ++++++--- .../org/koitharu/kotatsu/core/db/Tables.kt | 1 + .../kotatsu/core/db/dao/ChaptersDao.kt | 29 +++++++++++++++ .../koitharu/kotatsu/core/db/dao/MangaDao.kt | 3 ++ .../kotatsu/core/db/entity/ChapterEntity.kt | 32 +++++++++++++++++ .../kotatsu/core/db/entity/EntityMapping.kt | 36 +++++++++++++++++-- .../core/db/migrations/Migration23To24.kt | 11 ++++++ .../core/parser/MangaDataRepository.kt | 27 ++++++++++++-- .../details/domain/DetailsLoadUseCase.kt | 13 ++++++- .../kotatsu/details/ui/DetailsViewModel.kt | 15 ++++---- .../download/ui/list/DownloadsViewModel.kt | 2 +- .../download/ui/worker/DownloadWorker.kt | 2 +- .../kotatsu/favourites/data/EntityMapping.kt | 2 +- .../favourites/domain/FavouritesRepository.kt | 3 ++ .../domain/LocalFavoritesObserver.kt | 2 +- .../history/data/HistoryLocalObserver.kt | 2 +- .../kotatsu/history/data/HistoryRepository.kt | 26 ++++++++------ .../main/domain/CoverRestoreInterceptor.kt | 14 ++++---- .../kotatsu/stats/data/StatsRepository.kt | 2 +- .../domain/SuggestionRepository.kt | 13 +++---- .../kotatsu/tracker/data/EntityMapping.kt | 2 +- .../tracker/domain/TrackingRepository.kt | 4 +-- .../tracker/ui/debug/TrackerDebugViewModel.kt | 2 +- gradle/libs.versions.toml | 2 +- 25 files changed, 212 insertions(+), 54 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/ChaptersDao.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/ChapterEntity.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration23To24.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/AutoFixUseCase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/AutoFixUseCase.kt index ec45258c9..15ec1622c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/AutoFixUseCase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/AutoFixUseCase.kt @@ -29,8 +29,9 @@ class AutoFixUseCase @Inject constructor( ) { suspend operator fun invoke(mangaId: Long): Pair { - val seed = checkNotNull(mangaDataRepository.findMangaById(mangaId)) { "Manga $mangaId not found" } - .getDetailsSafe() + val seed = checkNotNull( + mangaDataRepository.findMangaById(mangaId, withChapters = true), + ) { "Manga $mangaId not found" }.getDetailsSafe() if (seed.isHealthy()) { return seed to null // no fix required } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt index 60f1abb02..daeea472f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -12,11 +12,13 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity import org.koitharu.kotatsu.bookmarks.data.BookmarksDao +import org.koitharu.kotatsu.core.db.dao.ChaptersDao import org.koitharu.kotatsu.core.db.dao.MangaDao import org.koitharu.kotatsu.core.db.dao.MangaSourcesDao import org.koitharu.kotatsu.core.db.dao.PreferencesDao import org.koitharu.kotatsu.core.db.dao.TagsDao import org.koitharu.kotatsu.core.db.dao.TrackLogsDao +import org.koitharu.kotatsu.core.db.entity.ChapterEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity @@ -36,6 +38,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration1To2 import org.koitharu.kotatsu.core.db.migrations.Migration20To21 import org.koitharu.kotatsu.core.db.migrations.Migration21To22 import org.koitharu.kotatsu.core.db.migrations.Migration22To23 +import org.koitharu.kotatsu.core.db.migrations.Migration23To24 import org.koitharu.kotatsu.core.db.migrations.Migration2To3 import org.koitharu.kotatsu.core.db.migrations.Migration3To4 import org.koitharu.kotatsu.core.db.migrations.Migration4To5 @@ -63,14 +66,14 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity import org.koitharu.kotatsu.tracker.data.TrackLogEntity import org.koitharu.kotatsu.tracker.data.TracksDao -const val DATABASE_VERSION = 23 +const val DATABASE_VERSION = 24 @Database( entities = [ - MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class, - FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class, - TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class, - ScrobblingEntity::class, MangaSourceEntity::class, StatsEntity::class, LocalMangaIndexEntity::class, + MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class, ChapterEntity::class, + FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class, TrackEntity::class, + TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class, ScrobblingEntity::class, + MangaSourceEntity::class, StatsEntity::class, LocalMangaIndexEntity::class, ], version = DATABASE_VERSION, ) @@ -103,6 +106,8 @@ abstract class MangaDatabase : RoomDatabase() { abstract fun getStatsDao(): StatsDao abstract fun getLocalMangaIndexDao(): LocalMangaIndexDao + + abstract fun getChaptersDao(): ChaptersDao } fun getDatabaseMigrations(context: Context): Array = arrayOf( @@ -128,6 +133,7 @@ fun getDatabaseMigrations(context: Context): Array = arrayOf( Migration20To21(), Migration21To22(), Migration22To23(), + Migration23To24(), ) fun MangaDatabase(context: Context): MangaDatabase = Room diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/Tables.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/Tables.kt index fc36085c7..a0d2dd63d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/Tables.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/Tables.kt @@ -7,3 +7,4 @@ const val TABLE_FAVOURITE_CATEGORIES = "favourite_categories" const val TABLE_HISTORY = "history" const val TABLE_MANGA_TAGS = "manga_tags" const val TABLE_SOURCES = "sources" +const val TABLE_CHAPTERS = "chapters" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/ChaptersDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/ChaptersDao.kt new file mode 100644 index 000000000..174e26c29 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/ChaptersDao.kt @@ -0,0 +1,29 @@ +package org.koitharu.kotatsu.core.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Transaction +import org.koitharu.kotatsu.core.db.entity.ChapterEntity + +@Dao +abstract class ChaptersDao { + + @Query("SELECT * FROM chapters WHERE manga_id = :mangaId ORDER BY `index` ASC") + abstract suspend fun findAll(mangaId: Long): List + + @Query("DELETE FROM chapters WHERE manga_id = :mangaId") + abstract suspend fun deleteAll(mangaId: Long) + + @Query("DELETE FROM chapters WHERE manga_id NOT IN (SELECT manga_id FROM history WHERE deleted_at = 0) AND manga_id NOT IN (SELECT manga_id FROM favourites WHERE deleted_at = 0)") + abstract suspend fun gc() + + @Transaction + open suspend fun replaceAll(mangaId: Long, entities: Collection) { + deleteAll(mangaId) + insert(entities) + } + + @Insert + protected abstract suspend fun insert(entities: Collection) +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt index 619c9befe..57fec66a1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt @@ -20,6 +20,9 @@ abstract class MangaDao { @Query("SELECT * FROM manga WHERE manga_id = :id") abstract suspend fun find(id: Long): MangaWithTags? + @Query("SELECT EXISTS(SELECT * FROM manga WHERE manga_id = :id)") + abstract suspend operator fun contains(id: Long): Boolean + @Transaction @Query("SELECT * FROM manga WHERE public_url = :publicUrl") abstract suspend fun findByPublicUrl(publicUrl: String): MangaWithTags? diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/ChapterEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/ChapterEntity.kt new file mode 100644 index 000000000..d03827c99 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/ChapterEntity.kt @@ -0,0 +1,32 @@ +package org.koitharu.kotatsu.core.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import org.koitharu.kotatsu.core.db.TABLE_CHAPTERS + +@Entity( + tableName = TABLE_CHAPTERS, + primaryKeys = ["manga_id", "chapter_id"], + foreignKeys = [ + ForeignKey( + entity = MangaEntity::class, + parentColumns = ["manga_id"], + childColumns = ["manga_id"], + onDelete = ForeignKey.CASCADE, + ), + ], +) +data class ChapterEntity( + @ColumnInfo(name = "chapter_id") val chapterId: Long, + @ColumnInfo(name = "manga_id") val mangaId: Long, + @ColumnInfo(name = "name") val name: String, + @ColumnInfo(name = "number") val number: Float, + @ColumnInfo(name = "volume") val volume: Int, + @ColumnInfo(name = "url") val url: String, + @ColumnInfo(name = "scanlator") val scanlator: String?, + @ColumnInfo(name = "upload_date") val uploadDate: Long, + @ColumnInfo(name = "branch") val branch: String?, + @ColumnInfo(name = "source") val source: String, + @ColumnInfo(name = "index") val index: Int, +) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt index 63a5ff2f4..a8a56f560 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.db.entity import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.util.ext.longHashCode import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.SortOrder @@ -21,7 +22,7 @@ fun Collection.toMangaTags() = mapToSet(TagEntity::toMangaTag) fun Collection.toMangaTagsList() = map(TagEntity::toMangaTag) -fun MangaEntity.toManga(tags: Set) = Manga( +fun MangaEntity.toManga(tags: Set, chapters: List?) = Manga( id = this.id, title = this.title, altTitle = this.altTitle, @@ -35,12 +36,27 @@ fun MangaEntity.toManga(tags: Set) = Manga( author = this.author, source = MangaSource(this.source), tags = tags, + chapters = chapters?.toMangaChapters(), ) -fun MangaWithTags.toManga() = manga.toManga(tags.toMangaTags()) +fun MangaWithTags.toManga(chapters: List? = null) = manga.toManga(tags.toMangaTags(), chapters) fun Collection.toMangaList() = map { it.toManga() } +fun ChapterEntity.toMangaChapter() = MangaChapter( + id = chapterId, + name = name, + number = number, + volume = volume, + url = url, + scanlator = scanlator, + uploadDate = uploadDate, + branch = branch, + source = MangaSource(source), +) + +fun Collection.toMangaChapters() = map { it.toMangaChapter() } + // Model to entity fun Manga.toEntity() = MangaEntity( @@ -67,6 +83,22 @@ fun MangaTag.toEntity() = TagEntity( fun Collection.toEntities() = map(MangaTag::toEntity) +fun Iterable>.toEntities(mangaId: Long) = map { (index, chapter) -> + ChapterEntity( + chapterId = chapter.id, + mangaId = mangaId, + name = chapter.name, + number = chapter.number, + volume = chapter.volume, + url = chapter.url, + scanlator = chapter.scanlator, + uploadDate = chapter.uploadDate, + branch = chapter.branch, + source = chapter.source.name, + index = index, + ) +} + // Other fun SortOrder(name: String, fallback: SortOrder): SortOrder = runCatching { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration23To24.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration23To24.kt new file mode 100644 index 000000000..008b1f158 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration23To24.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.core.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration23To24 : Migration(23, 24) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS `chapters` (`chapter_id` INTEGER NOT NULL, `manga_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `number` REAL NOT NULL, `volume` INTEGER NOT NULL, `url` TEXT NOT NULL, `scanlator` TEXT, `upload_date` INTEGER NOT NULL, `branch` TEXT, `source` TEXT NOT NULL, `index` INTEGER NOT NULL, PRIMARY KEY(`manga_id`, `chapter_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt index 35b7a63ac..adb2c544f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt @@ -71,8 +71,13 @@ class MangaDataRepository @Inject constructor( .distinctUntilChanged() } - suspend fun findMangaById(mangaId: Long): Manga? { - return db.getMangaDao().find(mangaId)?.toManga() + suspend fun findMangaById(mangaId: Long, withChapters: Boolean): Manga? { + val chapters = if (withChapters) { + db.getChaptersDao().findAll(mangaId).takeUnless { it.isEmpty() } + } else { + null + } + return db.getMangaDao().find(mangaId)?.toManga(chapters) } suspend fun findMangaByPublicUrl(publicUrl: String): Manga? { @@ -81,7 +86,7 @@ class MangaDataRepository @Inject constructor( suspend fun resolveIntent(intent: MangaIntent): Manga? = when { intent.manga != null -> intent.manga - intent.mangaId != 0L -> findMangaById(intent.mangaId) + intent.mangaId != 0L -> findMangaById(intent.mangaId, true) intent.uri != null -> resolverProvider.get().resolve(intent.uri) else -> null } @@ -98,10 +103,26 @@ class MangaDataRepository @Inject constructor( val tags = manga.tags.toEntities() db.getTagsDao().upsert(tags) db.getMangaDao().upsert(manga.toEntity(), tags) + if (!manga.isLocal) { + manga.chapters?.let { chapters -> + db.getChaptersDao().replaceAll(manga.id, chapters.withIndex().toEntities(manga.id)) + } + } } } } + suspend fun updateChapters(manga: Manga) { + val chapters = manga.chapters + if (!chapters.isNullOrEmpty() && manga.id in db.getMangaDao()) { + db.getChaptersDao().replaceAll(manga.id, chapters.withIndex().toEntities(manga.id)) + } + } + + suspend fun gcChapters() { + db.getChaptersDao().gc() + } + suspend fun findTags(source: MangaSource): Set { return db.getTagsDao().findTags(source.name).toMangaTags() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt index 4fc541e2d..7f7c96509 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt @@ -43,7 +43,14 @@ class DetailsLoadUseCase @Inject constructor( operator fun invoke(intent: MangaIntent): Flow = channelFlow { val manga = requireNotNull(mangaDataRepository.resolveIntent(intent)) { "Cannot resolve intent $intent" + }.let { m -> + if (m.chapters.isNullOrEmpty()) { + getCachedDetails(m.id) ?: m + } else { + m + } } + send(MangaDetails(manga, null, null, false)) val local = if (!manga.isLocal) { async { localMangaRepository.findSavedManga(manga) @@ -51,9 +58,9 @@ class DetailsLoadUseCase @Inject constructor( } else { null } - send(MangaDetails(manga, null, null, false)) try { val details = getDetails(manga) + launch { mangaDataRepository.updateChapters(details) } launch { updateTracker(details) } send( MangaDetails( @@ -122,4 +129,8 @@ class DetailsLoadUseCase @Inject constructor( }.onFailure { e -> e.printStackTraceDebug() } + + private suspend fun getCachedDetails(mangaId: Long): Manga? = runCatchingCancellable { + mangaDataRepository.findMangaById(mangaId, withChapters = true) + }.getOrNull() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 230df1f37..01acb1b19 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -226,14 +226,15 @@ class DetailsViewModel @Inject constructor( private fun doLoad() = launchLoadingJob(Dispatchers.Default) { detailsLoadUseCase.invoke(intent) .onEachWhile { - if (it.allChapters.isEmpty()) { - return@onEachWhile false + if (it.allChapters.isNotEmpty()) { + val manga = it.toManga() + // find default branch + val hist = historyRepository.getOne(manga) + selectedBranch.value = manga.getPreferredBranch(hist) + true + } else { + false } - val manga = it.toManga() - // find default branch - val hist = historyRepository.getOne(manga) - selectedBranch.value = manga.getPreferredBranch(hist) - true }.collect { mangaDetails.value = it } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt index 44795ee5b..6a74eb16b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt @@ -289,7 +289,7 @@ class DownloadsViewModel @Inject constructor( } return cacheMutex.withLock { mangaCache.getOrElse(mangaId) { - mangaDataRepository.findMangaById(mangaId)?.also { + mangaDataRepository.findMangaById(mangaId, withChapters = true)?.also { mangaCache[mangaId] = it } ?: return null } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt index 8aed8f1d4..1322a6b43 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt @@ -120,7 +120,7 @@ class DownloadWorker @AssistedInject constructor( override suspend fun doWork(): Result { setForeground(getForegroundInfo()) - val manga = mangaDataRepository.findMangaById(task.mangaId) ?: return Result.failure() + val manga = mangaDataRepository.findMangaById(task.mangaId, withChapters = true) ?: return Result.failure() publishState(DownloadState(manga = manga, isIndeterminate = true).also { lastPublishedState = it }) val downloadedIds = getDoneChapters(manga) return try { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/EntityMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/EntityMapping.kt index a99afe241..c32e5537d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/EntityMapping.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/EntityMapping.kt @@ -16,6 +16,6 @@ fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong()) isVisibleInLibrary = isVisibleInLibrary, ) -fun FavouriteManga.toManga() = manga.toManga(tags.toMangaTags()) +fun FavouriteManga.toManga() = manga.toManga(tags.toMangaTags(), null) fun Collection.toMangaList() = map { it.toManga() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index c65a2acce..7e7f1f049 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -199,6 +199,7 @@ class FavouritesRepository @Inject constructor( db.getFavouritesDao().deleteAll(id) db.getFavouriteCategoriesDao().delete(id) } + db.getChaptersDao().gc() } } @@ -238,6 +239,7 @@ class FavouritesRepository @Inject constructor( for (id in ids) { db.getFavouritesDao().delete(mangaId = id) } + db.getChaptersDao().gc() } return ReversibleHandle { recoverToFavourites(ids) } } @@ -247,6 +249,7 @@ class FavouritesRepository @Inject constructor( for (id in ids) { db.getFavouritesDao().delete(categoryId = categoryId, mangaId = id) } + db.getChaptersDao().gc() } return ReversibleHandle { recoverToCategory(categoryId, ids) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/LocalFavoritesObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/LocalFavoritesObserver.kt index 59162a210..59c201d79 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/LocalFavoritesObserver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/LocalFavoritesObserver.kt @@ -32,7 +32,7 @@ class LocalFavoritesObserver @Inject constructor( limit: Int ): Flow> = db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit).mapToLocal() - override fun toManga(e: FavouriteManga) = e.manga.toManga(e.tags.toMangaTags()) + override fun toManga(e: FavouriteManga) = e.manga.toManga(e.tags.toMangaTags(), null) override fun toResult(e: FavouriteManga, manga: Manga) = manga } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryLocalObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryLocalObserver.kt index 7deeea3c9..e1e807c86 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryLocalObserver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryLocalObserver.kt @@ -24,7 +24,7 @@ class HistoryLocalObserver @Inject constructor( limit: Int ) = db.getHistoryDao().observeAll(order, filterOptions, limit).mapToLocal() - override fun toManga(e: HistoryWithManga) = e.manga.toManga(e.tags.toMangaTags()) + override fun toManga(e: HistoryWithManga) = e.manga.toManga(e.tags.toMangaTags(), null) override fun toResult(e: HistoryWithManga, manga: Manga) = MangaWithHistory( manga = manga, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt index d93f2df01..38a7a4bf3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt @@ -49,7 +49,7 @@ class HistoryRepository @Inject constructor( suspend fun getList(offset: Int, limit: Int): List { val entities = db.getHistoryDao().findAll(offset, limit) - return entities.map { it.manga.toManga(it.tags.toMangaTags()) } + return entities.map { it.toManga() } } suspend fun search(query: String, limit: Int): List { @@ -63,25 +63,25 @@ class HistoryRepository @Inject constructor( suspend fun getLastOrNull(): Manga? { val entity = db.getHistoryDao().findAll(0, 1).firstOrNull() ?: return null - return entity.manga.toManga(entity.tags.toMangaTags()) + return entity.toManga() } fun observeLast(): Flow { return db.getHistoryDao().observeAll(1).map { val first = it.firstOrNull() - first?.manga?.toManga(first.tags.toMangaTags()) + first?.toManga() } } fun observeAll(): Flow> { return db.getHistoryDao().observeAll().mapItems { - it.manga.toManga(it.tags.toMangaTags()) + it.toManga() } } fun observeAll(limit: Int): Flow> { return db.getHistoryDao().observeAll(limit).mapItems { - it.manga.toManga(it.tags.toMangaTags()) + it.toManga() } } @@ -95,7 +95,7 @@ class HistoryRepository @Inject constructor( } return db.getHistoryDao().observeAll(order, filterOptions, limit).mapItems { MangaWithHistory( - it.manga.toManga(it.tags.toMangaTags()), + it.toManga(), it.history.toMangaHistory(), ) } @@ -156,16 +156,19 @@ class HistoryRepository @Inject constructor( db.getHistoryDao().clear() } - suspend fun delete(manga: Manga) { + suspend fun delete(manga: Manga) = db.withTransaction { db.getHistoryDao().delete(manga.id) + mangaRepository.gcChapters() } - suspend fun deleteAfter(minDate: Long) { + suspend fun deleteAfter(minDate: Long) = db.withTransaction { db.getHistoryDao().deleteAfter(minDate) + mangaRepository.gcChapters() } - suspend fun deleteNotFavorite() { + suspend fun deleteNotFavorite() = db.withTransaction { db.getHistoryDao().deleteNotFavorite() + mangaRepository.gcChapters() } suspend fun delete(ids: Collection): ReversibleHandle { @@ -173,6 +176,7 @@ class HistoryRepository @Inject constructor( for (id in ids) { db.getHistoryDao().delete(id) } + mangaRepository.gcChapters() } return ReversibleHandle { recover(ids) @@ -185,7 +189,7 @@ class HistoryRepository @Inject constructor( */ suspend fun deleteOrSwap(manga: Manga, alternative: Manga?) { if (alternative == null || db.getMangaDao().update(alternative.toEntity()) <= 0) { - db.getHistoryDao().delete(manga.id) + delete(manga) } } @@ -229,4 +233,6 @@ class HistoryRepository @Inject constructor( db.getHistoryDao().update(newEntity) return newEntity } + + private fun HistoryWithManga.toManga() = manga.toManga(tags.toMangaTags(), null) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt index 733105cea..edb46e8c6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt @@ -32,17 +32,17 @@ class CoverRestoreInterceptor @Inject constructor( val result = chain.proceed() if (result is ErrorResult && result.throwable.shouldRestore()) { request.extras[mangaKey]?.let { - if (restoreManga(it)) { - return chain.withRequest(request.newBuilder().build()).proceed() + return if (restoreManga(it)) { + chain.withRequest(request.newBuilder().build()).proceed() } else { - return result + result } } request.extras[bookmarkKey]?.let { - if (restoreBookmark(it)) { - return chain.withRequest(request.newBuilder().build()).proceed() + return if (restoreBookmark(it)) { + chain.withRequest(request.newBuilder().build()).proceed() } else { - return result + result } } } @@ -66,7 +66,7 @@ class CoverRestoreInterceptor @Inject constructor( } private suspend fun restoreMangaImpl(manga: Manga): Boolean { - if (dataRepository.findMangaById(manga.id) == null || manga.isLocal) { + if (dataRepository.findMangaById(manga.id, withChapters = false) == null || manga.isLocal) { return false } val repo = repositoryFactory.create(manga.source) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsRepository.kt index 9b621e1b0..54992260c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsRepository.kt @@ -33,7 +33,7 @@ class StatsRepository @Inject constructor( var other = StatsRecord(null, 0) val total = stats.values.sum() for ((mangaEntity, duration) in stats) { - val manga = mangaEntity.toManga(emptySet()) + val manga = mangaEntity.toManga(emptySet(), null) val percent = duration.toDouble() / total if (percent < 0.05) { other = other.copy(duration = other.duration + duration) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt index b7f8a4e6a..6a2e168cf 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt @@ -15,6 +15,7 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.suggestions.data.SuggestionEntity +import org.koitharu.kotatsu.suggestions.data.SuggestionWithManga import javax.inject.Inject class SuggestionRepository @Inject constructor( @@ -23,25 +24,23 @@ class SuggestionRepository @Inject constructor( fun observeAll(): Flow> { return db.getSuggestionDao().observeAll().mapItems { - it.manga.toManga(it.tags.toMangaTags()) + it.toManga() } } fun observeAll(limit: Int, filterOptions: Set): Flow> { return db.getSuggestionDao().observeAll(limit, filterOptions).mapItems { - it.manga.toManga(it.tags.toMangaTags()) + it.toManga() } } suspend fun getRandom(): Manga? { - return db.getSuggestionDao().getRandom()?.let { - it.manga.toManga(it.tags.toMangaTags()) - } + return db.getSuggestionDao().getRandom()?.toManga() } suspend fun getRandomList(limit: Int): List { return db.getSuggestionDao().getRandom(limit).map { - it.manga.toManga(it.tags.toMangaTags()) + it.toManga() } } @@ -80,4 +79,6 @@ class SuggestionRepository @Inject constructor( } } } + + private fun SuggestionWithManga.toManga() = manga.toManga(tags.toMangaTags(), null) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/EntityMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/EntityMapping.kt index 5af404b93..cf9e42e97 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/EntityMapping.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/EntityMapping.kt @@ -10,7 +10,7 @@ fun TrackLogWithManga.toTrackingLogItem(): TrackingLogItem { return TrackingLogItem( id = trackLog.id, chapters = chaptersList, - manga = manga.toManga(tags.toMangaTags()), + manga = manga.toManga(tags.toMangaTags(), null), createdAt = Instant.ofEpochMilli(trackLog.createdAt), isNew = trackLog.isUnread, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt index 569a4e1e8..95ff75e22 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt @@ -60,7 +60,7 @@ class TrackingRepository @Inject constructor( return db.getTracksDao().observeUpdatedManga(limit, filterOptions) .mapItems { MangaTracking( - manga = it.manga.toManga(it.tags.toMangaTags()), + manga = it.manga.toManga(it.tags.toMangaTags(), null), lastChapterId = it.track.lastChapterId, lastCheck = it.track.lastCheckTime.toInstantOrNull(), lastChapterDate = it.track.lastChapterDate.toInstantOrNull(), @@ -73,7 +73,7 @@ class TrackingRepository @Inject constructor( suspend fun getTracks(offset: Int, limit: Int): List { return db.getTracksDao().findAll(offset = offset, limit = limit).map { MangaTracking( - manga = it.manga.toManga(emptySet()), + manga = it.manga.toManga(emptySet(), null), lastChapterId = it.track.lastChapterId, lastCheck = it.track.lastCheckTime.toInstantOrNull(), lastChapterDate = it.track.lastChapterDate.toInstantOrNull(), diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt index b9b8866ec..a995979b0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt @@ -25,7 +25,7 @@ class TrackerDebugViewModel @Inject constructor( private fun List.toUiList(): List = map { TrackDebugItem( - manga = it.manga.toManga(emptySet()), + manga = it.manga.toManga(emptySet(), null), lastChapterId = it.track.lastChapterId, newChapters = it.track.newChapters, lastCheckTime = it.track.lastCheckTime.toInstantOrNull(), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1a8c014d..7254889a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ transition = "1.5.1" viewpager2 = "1.1.0" webkit = "1.12.1" workRuntime = "2.10.0" -workinspector = "1.0" +workinspector = "1.2" [libraries] acra-dialog = { module = "ch.acra:acra-dialog", version.ref = "acra" }