diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt index b22a29bd6..e90e541cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -62,9 +62,11 @@ class MangaBackupCreator( if (options.history) { val historyByMangaId = getHistory.await(manga.id) if (historyByMangaId.isNotEmpty()) { - val history = historyByMangaId.map { history -> - val chapter = checkNotNull(chapterRepository.getChapterById(history.chapterId)) - BackupHistory(chapter.url, history.readAt?.time ?: 0L, history.readDuration) + val chapterUrlsById = chapterRepository.getChapterByMangaId(manga.id).associate { it.id to it.url } + val history = historyByMangaId.mapNotNull { history -> + // A chapter removed since its history was read takes that history with it + val url = chapterUrlsById[history.chapterId] ?: return@mapNotNull null + BackupHistory(url, history.readAt?.time ?: 0L, history.readDuration) } if (history.isNotEmpty()) { mangaObject.history = history diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt index bb5814957..eecef476f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt @@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.util.removeCovers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.WhileSubscribed import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter @@ -56,6 +57,7 @@ import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.source.interactor.GetRemoteManga import tachiyomi.domain.source.service.SourceManager import kotlin.time.Clock +import kotlin.time.Duration.Companion.seconds import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter @AssistedInject @@ -133,7 +135,7 @@ class BrowseSourceViewModel( pagingData.map { manga -> getManga.subscribe(manga.url, manga.source) .map { it ?: manga } - .stateIn(viewModelScope) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), manga) } .filter { !hideInLibraryItems || !it.value.favorite } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt index 9d992eed6..53334628a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt @@ -458,34 +458,19 @@ class LibraryViewModel( } } - /** - * Returns the common categories for the given list of manga. - * - * @param mangas the list of manga. - */ - private suspend fun getCommonCategories(mangas: List): Collection { - if (mangas.isEmpty()) return emptyList() - return mangas - .map { getCategories.await(it.id).toSet() } - .reduce { set1, set2 -> set1.intersect(set2) } + private suspend fun getCategoryIds(manga: Manga): Set { + return state.value.libraryData.favoritesById[manga.id] + ?.libraryManga + ?.categories + // The library reports no category as 0 + ?.filterTo(mutableSetOf()) { it != 0L } + ?: getCategories.await(manga.id).mapTo(mutableSetOf()) { it.id } } suspend fun getNextUnreadChapter(manga: Manga): Chapter? { return getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true).getNextUnread(manga, downloadManager) } - /** - * Returns the mix (non-common) categories for the given list of manga. - * - * @param mangas the list of manga. - */ - private suspend fun getMixCategories(mangas: List): Collection { - if (mangas.isEmpty()) return emptyList() - val mangaCategories = mangas.map { getCategories.await(it.id).toSet() } - val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2) } - return mangaCategories.flatten().distinct().subtract(common) - } - /** * Queues the amount specified of unread chapters from the list of selected manga */ @@ -599,8 +584,7 @@ class LibraryViewModel( fun setMangaCategories(mangaList: List, addCategories: List, removeCategories: List) { viewModelScope.launchNonCancellable { mangaList.forEach { manga -> - val categoryIds = getCategories.await(manga.id) - .map { it.id } + val categoryIds = getCategoryIds(manga) .subtract(removeCategories.toSet()) .plus(addCategories) .toList() @@ -729,13 +713,12 @@ class LibraryViewModel( // Hide the default category because it has a different behavior than the ones from db. val categories = state.value.displayedCategories.filter { it.id != 0L } - // Get indexes of the common categories to preselect. - val common = getCommonCategories(mangaList) - // Get indexes of the mix categories to preselect. - val mix = getMixCategories(mangaList) + val mangaCategoryIds = mangaList.map { getCategoryIds(it) } + val common = mangaCategoryIds.reduceOrNull { set1, set2 -> set1 intersect set2 }.orEmpty() + val mix = mangaCategoryIds.flatten().toSet() - common val preselected = categories .map { - when (it) { + when (it.id) { in common -> CheckboxState.State.Checked(it) in mix -> CheckboxState.TriState.Exclude(it) else -> CheckboxState.State.None(it) diff --git a/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt index 31610b0ab..562c915c1 100644 --- a/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt @@ -34,11 +34,12 @@ class RestoreRepositoryImpl( private val trackRepository: TrackRepository, ) : RestoreRepository { - override suspend fun getMangaUrlsBySourceId(): Map> { + override suspend fun getMangaUrlsBySourceId(): Map> { return database.mangaQueries .getAllMangaSourceAndUrl() .awaitAsList() .groupBy({ it.source_id }, { it.remote_url }) + .mapValues { it.value.toSet() } } override suspend fun restoreManga(entries: List, update: suspend (Manga) -> MangaUpdate) { @@ -93,49 +94,49 @@ class RestoreRepositoryImpl( private suspend fun updateManga(manga: Manga): Manga { database.mangaQueries.updateFromBackup( - favoriteAt = manga.favoriteAt, - artist = manga.artist, - author = manga.author, - description = manga.description, - genre = manga.genre, - title = manga.title, - status = manga.status, - thumbnailUrl = manga.thumbnailUrl, - lastUpdate = manga.lastUpdate, - initialized = manga.initialized, - viewer = manga.viewerFlags, - chapterFlags = manga.chapterFlags, - coverLastModified = manga.coverLastModified, - mangaId = manga.id, - updateStrategy = manga.updateStrategy, - notes = manga.notes, - memo = manga.memo, + userFavoriteAt = manga.favoriteAt, + remoteArtist = manga.artist, + remoteAuthor = manga.author, + remoteDescription = manga.description, + remoteGenre = manga.genre, + remoteTitle = manga.title, + remoteStatus = manga.status, + remoteCover = manga.thumbnailUrl, + stateChapterLastUpdate = manga.lastUpdate, + stateInitialized = manga.initialized, + userReaderFlags = manga.viewerFlags, + userChapterFlags = manga.chapterFlags, + stateCoverLastModified = manga.coverLastModified, + id = manga.id, + remoteUpdateStrategy = manga.updateStrategy, + userNotes = manga.notes, + remoteMemo = manga.memo, ) return manga } private suspend fun insertManga(manga: Manga): Long { return database.mangaQueries.insertReturningId( - favoriteAt = manga.favoriteAt, - source = manga.source, - url = manga.url, - artist = manga.artist, - author = manga.author, - description = manga.description, - genre = manga.genre, - title = manga.title, - status = manga.status, - thumbnailUrl = manga.thumbnailUrl, - lastUpdate = manga.lastUpdate, - nextUpdate = 0L, - calculateInterval = 0L, - initialized = manga.initialized, - viewerFlags = manga.viewerFlags, - chapterFlags = manga.chapterFlags, - coverLastModified = manga.coverLastModified, - updateStrategy = manga.updateStrategy, - notes = manga.notes, - memo = manga.memo, + userFavoriteAt = manga.favoriteAt, + sourceId = manga.source, + remoteUrl = manga.url, + remoteArtist = manga.artist, + remoteAuthor = manga.author, + remoteDescription = manga.description, + remoteGenre = manga.genre, + remoteTitle = manga.title, + remoteStatus = manga.status, + remoteCover = manga.thumbnailUrl, + stateChapterLastUpdate = manga.lastUpdate, + stateChapterNextUpdate = 0L, + stateChapterFetchInterval = 0L, + stateInitialized = manga.initialized, + userReaderFlags = manga.viewerFlags, + userChapterFlags = manga.chapterFlags, + stateCoverLastModified = manga.coverLastModified, + remoteUpdateStrategy = manga.updateStrategy, + userNotes = manga.notes, + remoteMemo = manga.memo, ) .awaitAsOne() } @@ -183,28 +184,29 @@ class RestoreRepositoryImpl( .partition { it.id > 0 } newChapters.forEach { chapter -> - database.chapterQueries.insert( + database.chapterQueries.insertReturningId( mangaId = chapter.mangaId, - url = chapter.url, - name = chapter.name, - scanlator = chapter.scanlator, - read = chapter.read, - bookmark = chapter.bookmark, - lastPageRead = chapter.lastPageRead, - chapterNumber = chapter.chapterNumber, - sourceOrder = chapter.sourceOrder, - dateFetch = chapter.dateFetch, - dateUpload = chapter.dateUpload, - memo = chapter.memo, + remoteUrl = chapter.url, + remoteName = chapter.name, + remoteScanlator = chapter.scanlator, + userRead = chapter.read, + userBookmark = chapter.bookmark, + userLastPageRead = chapter.lastPageRead, + remoteChapterNumber = chapter.chapterNumber, + remoteOrder = chapter.sourceOrder, + stateDateFetch = chapter.dateFetch, + remoteDateUpload = chapter.dateUpload, + remoteMemo = chapter.memo, ) + .awaitAsOneOrNull() } existingChapters.forEach { chapter -> database.chapterQueries.updateFromBackup( - read = chapter.read, - bookmark = chapter.bookmark, - lastPageRead = chapter.lastPageRead, - chapterId = chapter.id, - memo = chapter.memo, + userRead = chapter.read, + userBookmark = chapter.bookmark, + userLastPageRead = chapter.lastPageRead, + id = chapter.id, + remoteMemo = chapter.memo, ) } } @@ -213,28 +215,30 @@ class RestoreRepositoryImpl( this.copy(id = 0L, mangaId = 0L, dateFetch = 0L, dateUpload = 0L) private suspend fun restoreHistory(manga: Manga, restoredHistory: List) { + if (restoredHistory.isEmpty()) return + val chapterIdsByUrl = chapterRepository.getChapterByMangaId(manga.id).associate { it.url to it.id } + val dbHistoryByChapterId = database.historyQueries + .getHistoryByMangaId(manga.id) + .awaitAsList() + .associateBy { it.chapter_id } + val toUpdate = restoredHistory - .groupBy { it.chapterUrl } - .mapNotNull { (chapterUrl, copies) -> + // Chapter doesn't exist; skip + .filter { it.chapterUrl in chapterIdsByUrl } + // A backup of a library that still had duplicate chapters carries a history entry for + // each copy; they are one chapter now, read as late as any copy and for as long as all + .groupBy { chapterIdsByUrl.getValue(it.chapterUrl) } + .map { (chapterId, copies) -> val readAt = copies.maxOf { it.readAt?.time ?: 0L } val readDuration = copies.sumOf { it.readDuration } - val dbHistory = database.historyQueries - .getHistoryByChapterUrlAndMangaId(chapterUrl, manga.id) - .awaitAsOneOrNull() - - if (dbHistory == null) { - val chapter = database.chapterQueries - .getChapterByUrlAndMangaId(chapterUrl, manga.id) - .awaitAsOneOrNull() - // Chapter doesn't exist; skip - ?: return@mapNotNull null + val dbHistory = dbHistoryByChapterId[chapterId] // New history entry - return@mapNotNull Triple(chapter.id, Date(readAt), readDuration) - } + ?: return@map Triple(chapterId, Date(readAt), readDuration) - // Update history entry + // Update history entry. 0 is kept rather than written as NULL, since it marks history + // the user removed. Triple( - dbHistory.chapter_id, + chapterId, Date(max(readAt, dbHistory.read_at?.time ?: 0L)), max(readDuration, dbHistory.read_duration) - dbHistory.read_duration, ) @@ -271,15 +275,15 @@ class RestoreRepositoryImpl( existingTracks.forEach { track -> database.manga_trackQueries.update( mangaId = track.mangaId, - syncId = track.trackerId, - mediaId = track.remoteId, + trackerId = track.trackerId, + remoteId = track.remoteId, libraryId = track.libraryId, title = track.title, lastChapterRead = track.lastChapterRead, - totalChapter = track.totalChapters, + totalChapters = track.totalChapters, status = track.status, score = track.score, - trackingUrl = track.remoteUrl, + remoteUrl = track.remoteUrl, startDate = track.startDate, finishDate = track.finishDate, `private` = track.private, @@ -289,11 +293,6 @@ class RestoreRepositoryImpl( } private suspend fun restoreExcludedScanlators(manga: Manga, excludedScanlators: List) { - if (excludedScanlators.isEmpty()) return - val existingExcludedScanlators = database.excluded_scanlatorQueries - .getExcludedScanlatorsByMangaId(manga.id) - .awaitAsList() - val toInsert = excludedScanlators.filter { it !in existingExcludedScanlators } - toInsert.forEach { database.excluded_scanlatorQueries.insert(manga.id, it) } + excludedScanlators.forEach { database.excluded_scanlatorQueries.insert(manga.id, it) } } } diff --git a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt index fed1d96df..4ad24bf0d 100644 --- a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt @@ -61,14 +61,14 @@ class CategoryRepositoryImpl( } override suspend fun updateName(categoryId: Long, name: String) { - database.categoryQueries.updateName(name = name, categoryId = categoryId) + database.categoryQueries.updateName(name = name, id = categoryId) } override suspend fun updateFlags(categoryId: Long, flags: Long) { - database.categoryQueries.updateFlags(flags = flags, categoryId = categoryId) + database.categoryQueries.updateFlags(flags = flags, id = categoryId) } - override suspend fun updateAllFlags(flags: Long?) { + override suspend fun updateAllFlags(flags: Long) { database.categoryQueries.updateAllFlags(flags = flags) } @@ -77,16 +77,16 @@ class CategoryRepositoryImpl( val current = database.categoryQueries.getUserCategoryIds().awaitAsList() val ids = orderedIds.filter { it in current } + current.filterNot { it in orderedIds } ids.forEachIndexed { index, categoryId -> - database.categoryQueries.updateOrder(order = -index - 2L, categoryId = categoryId) + database.categoryQueries.updateOrder(order = -index - 2L, id = categoryId) } ids.forEachIndexed { index, categoryId -> - database.categoryQueries.updateOrder(order = index.toLong(), categoryId = categoryId) + database.categoryQueries.updateOrder(order = index.toLong(), id = categoryId) } } } override suspend fun delete(categoryId: Long) { - database.categoryQueries.delete(categoryId = categoryId) + database.categoryQueries.delete(id = categoryId) } private fun mapCategory( diff --git a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt index cb20c697f..5642f1e7d 100644 --- a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt @@ -57,30 +57,30 @@ class ChapterRepositoryImpl( val stored = added.filter { existing.add(it.mangaId to it.url) }.map { chapter -> val chapterId = database.chapterQueries.insertReturningId( mangaId = chapter.mangaId, - url = chapter.url, - name = chapter.name, - scanlator = chapter.scanlator, - read = chapter.read, - bookmark = chapter.bookmark, - lastPageRead = chapter.lastPageRead, - chapterNumber = chapter.chapterNumber, - sourceOrder = chapter.sourceOrder, - dateFetch = chapter.dateFetch, - dateUpload = chapter.dateUpload, - memo = chapter.memo, + remoteUrl = chapter.url, + remoteName = chapter.name, + remoteScanlator = chapter.scanlator, + userRead = chapter.read, + userBookmark = chapter.bookmark, + userLastPageRead = chapter.lastPageRead, + remoteChapterNumber = chapter.chapterNumber, + remoteOrder = chapter.sourceOrder, + stateDateFetch = chapter.dateFetch, + remoteDateUpload = chapter.dateUpload, + remoteMemo = chapter.memo, ) .awaitAsOne() chapter.copy(id = chapterId) } updated.forEach { chapterUpdate -> database.chapterQueries.updateRemote( - name = chapterUpdate.name, - scanlator = chapterUpdate.scanlator, - chapterNumber = chapterUpdate.chapterNumber, - sourceOrder = chapterUpdate.sourceOrder, - dateUpload = chapterUpdate.dateUpload, - chapterId = chapterUpdate.id, - memo = chapterUpdate.memo, + remoteName = chapterUpdate.name, + remoteScanlator = chapterUpdate.scanlator, + remoteChapterNumber = chapterUpdate.chapterNumber, + remoteOrder = chapterUpdate.sourceOrder, + remoteDateUpload = chapterUpdate.dateUpload, + id = chapterUpdate.id, + remoteMemo = chapterUpdate.memo, ) } stored @@ -91,11 +91,11 @@ class ChapterRepositoryImpl( database.transaction { chapterUpdates.forEach { chapterUpdate -> database.chapterQueries.update( - read = chapterUpdate.read, - bookmark = chapterUpdate.bookmark, - lastPageRead = chapterUpdate.lastPageRead, - dateFetch = chapterUpdate.dateFetch, - chapterId = chapterUpdate.id, + userRead = chapterUpdate.read, + userBookmark = chapterUpdate.bookmark, + userLastPageRead = chapterUpdate.lastPageRead, + stateDateFetch = chapterUpdate.dateFetch, + id = chapterUpdate.id, ) } } @@ -147,38 +147,37 @@ class ChapterRepositoryImpl( override suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? { return database.chapterQueries - .getChapterByUrlAndMangaId(url, mangaId, ::mapChapter) + .getChapterByUrlAndMangaId(mangaId = mangaId, remoteUrl = url, mapper = ::mapChapter) .awaitAsOneOrNull() } - @Suppress("UNUSED_PARAMETER") private fun mapChapter( id: Long, mangaId: Long, - url: String, - name: String, - scanlator: String?, - read: Boolean, - bookmark: Boolean, - lastPageRead: Long, - chapterNumber: Double, - sourceOrder: Long, - dateFetch: Long, - dateUpload: Long, - memo: JsonObject, + remoteUrl: String, + remoteName: String, + remoteScanlator: String?, + remoteChapterNumber: Double, + remoteDateUpload: Long, + remoteOrder: Long, + remoteMemo: JsonObject, + userRead: Boolean, + userBookmark: Boolean, + userLastPageRead: Long, + stateDateFetch: Long, ): Chapter = Chapter( id = id, mangaId = mangaId, - read = read, - bookmark = bookmark, - lastPageRead = lastPageRead, - dateFetch = dateFetch, - sourceOrder = sourceOrder, - url = url, - name = name, - dateUpload = dateUpload, - chapterNumber = chapterNumber, - scanlator = scanlator, - memo = memo, + read = userRead, + bookmark = userBookmark, + lastPageRead = userLastPageRead, + dateFetch = stateDateFetch, + sourceOrder = remoteOrder, + url = remoteUrl, + name = remoteName, + dateUpload = remoteDateUpload, + chapterNumber = remoteChapterNumber, + scanlator = remoteScanlator, + memo = remoteMemo, ) } diff --git a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt index ef7225827..9e8825bfb 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt @@ -9,72 +9,72 @@ import tachiyomi.domain.manga.model.MangaWithChapterCount object MangaMapper { fun mapManga( id: Long, - source: Long, - url: String, - artist: String?, - author: String?, - description: String?, - genre: List?, - title: String, - status: Long, - thumbnailUrl: String?, - lastUpdate: Long?, - nextUpdate: Long?, - initialized: Boolean, - viewerFlags: Long, - chapterFlags: Long, - coverLastModified: Long, - updateStrategy: UpdateStrategy, - calculateInterval: Long, - notes: String, - memo: JsonObject, - favoriteAt: Long?, + sourceId: Long, + remoteUrl: String, + remoteTitle: String, + remoteAuthor: String?, + remoteArtist: String?, + remoteDescription: String?, + remoteGenre: List?, + remoteStatus: Long, + remoteCover: String?, + remoteUpdateStrategy: UpdateStrategy, + remoteMemo: JsonObject, + userFavoriteAt: Long?, + userNotes: String, + userReaderFlags: Long, + userChapterFlags: Long, + stateChapterLastUpdate: Long?, + stateChapterNextUpdate: Long?, + stateChapterFetchInterval: Long, + stateCoverLastModified: Long, + stateInitialized: Boolean, ): Manga = Manga( id = id, - source = source, - lastUpdate = lastUpdate ?: 0, - nextUpdate = nextUpdate ?: 0, - fetchInterval = calculateInterval.toInt(), - favoriteAt = favoriteAt, - viewerFlags = viewerFlags, - chapterFlags = chapterFlags, - coverLastModified = coverLastModified, - url = url, - title = title, - artist = artist, - author = author, - description = description, - genre = genre, - status = status, - thumbnailUrl = thumbnailUrl, - updateStrategy = updateStrategy, - initialized = initialized, - notes = notes, - memo = memo, + source = sourceId, + favoriteAt = userFavoriteAt, + lastUpdate = stateChapterLastUpdate ?: 0, + nextUpdate = stateChapterNextUpdate ?: 0, + fetchInterval = stateChapterFetchInterval.toInt(), + viewerFlags = userReaderFlags, + chapterFlags = userChapterFlags, + coverLastModified = stateCoverLastModified, + url = remoteUrl, + title = remoteTitle, + artist = remoteArtist, + author = remoteAuthor, + description = remoteDescription, + genre = remoteGenre, + status = remoteStatus, + thumbnailUrl = remoteCover, + updateStrategy = remoteUpdateStrategy, + initialized = stateInitialized, + notes = userNotes, + memo = remoteMemo, ) fun mapLibraryManga( id: Long, - source: Long, - url: String, - artist: String?, - author: String?, - description: String?, - genre: List?, - title: String, - status: Long, - thumbnailUrl: String?, - lastUpdate: Long?, - nextUpdate: Long?, - initialized: Boolean, - viewerFlags: Long, - chapterFlags: Long, - coverLastModified: Long, - updateStrategy: UpdateStrategy, - calculateInterval: Long, - notes: String, - memo: JsonObject, - favoriteAt: Long?, + sourceId: Long, + remoteUrl: String, + remoteTitle: String, + remoteAuthor: String?, + remoteArtist: String?, + remoteDescription: String?, + remoteGenre: List?, + remoteStatus: Long, + remoteCover: String?, + remoteUpdateStrategy: UpdateStrategy, + remoteMemo: JsonObject, + userFavoriteAt: Long, + userNotes: String, + userReaderFlags: Long, + userChapterFlags: Long, + stateChapterLastUpdate: Long?, + stateChapterNextUpdate: Long?, + stateChapterFetchInterval: Long, + stateCoverLastModified: Long, + stateInitialized: Boolean, totalCount: Long, readCount: Double, latestUpload: Long, @@ -85,26 +85,26 @@ object MangaMapper { ): LibraryManga = LibraryManga( manga = mapManga( id = id, - source = source, - url = url, - artist = artist, - author = author, - description = description, - genre = genre, - title = title, - status = status, - thumbnailUrl = thumbnailUrl, - lastUpdate = lastUpdate, - nextUpdate = nextUpdate, - initialized = initialized, - viewerFlags = viewerFlags, - chapterFlags = chapterFlags, - coverLastModified = coverLastModified, - updateStrategy = updateStrategy, - calculateInterval = calculateInterval, - notes = notes, - memo = memo, - favoriteAt = favoriteAt, + sourceId = sourceId, + remoteUrl = remoteUrl, + remoteTitle = remoteTitle, + remoteAuthor = remoteAuthor, + remoteArtist = remoteArtist, + remoteDescription = remoteDescription, + remoteGenre = remoteGenre, + remoteStatus = remoteStatus, + remoteCover = remoteCover, + remoteUpdateStrategy = remoteUpdateStrategy, + remoteMemo = remoteMemo, + userFavoriteAt = userFavoriteAt, + userNotes = userNotes, + userReaderFlags = userReaderFlags, + userChapterFlags = userChapterFlags, + stateChapterLastUpdate = stateChapterLastUpdate, + stateChapterNextUpdate = stateChapterNextUpdate, + stateChapterFetchInterval = stateChapterFetchInterval, + stateCoverLastModified = stateCoverLastModified, + stateInitialized = stateInitialized, ), categories = categories.split(",").map { it.toLong() }, totalChapters = totalCount, @@ -117,51 +117,51 @@ object MangaMapper { fun mapMangaWithChapterCount( id: Long, - source: Long, - url: String, - artist: String?, - author: String?, - description: String?, - genre: List?, - title: String, - status: Long, - thumbnailUrl: String?, - lastUpdate: Long?, - nextUpdate: Long?, - initialized: Boolean, - viewerFlags: Long, - chapterFlags: Long, - coverLastModified: Long, - updateStrategy: UpdateStrategy, - calculateInterval: Long, - notes: String, - memo: JsonObject, - favoriteAt: Long?, - totalCount: Long, + sourceId: Long, + remoteUrl: String, + remoteTitle: String, + remoteAuthor: String?, + remoteArtist: String?, + remoteDescription: String?, + remoteGenre: List?, + remoteStatus: Long, + remoteCover: String?, + remoteUpdateStrategy: UpdateStrategy, + remoteMemo: JsonObject, + userFavoriteAt: Long?, + userNotes: String, + userReaderFlags: Long, + userChapterFlags: Long, + stateChapterLastUpdate: Long?, + stateChapterNextUpdate: Long?, + stateChapterFetchInterval: Long, + stateCoverLastModified: Long, + stateInitialized: Boolean, + chapterCount: Long, ): MangaWithChapterCount = MangaWithChapterCount( manga = mapManga( id = id, - source = source, - url = url, - artist = artist, - author = author, - description = description, - genre = genre, - title = title, - status = status, - thumbnailUrl = thumbnailUrl, - lastUpdate = lastUpdate, - nextUpdate = nextUpdate, - initialized = initialized, - viewerFlags = viewerFlags, - chapterFlags = chapterFlags, - coverLastModified = coverLastModified, - updateStrategy = updateStrategy, - calculateInterval = calculateInterval, - notes = notes, - memo = memo, - favoriteAt = favoriteAt, + sourceId = sourceId, + remoteUrl = remoteUrl, + remoteTitle = remoteTitle, + remoteAuthor = remoteAuthor, + remoteArtist = remoteArtist, + remoteDescription = remoteDescription, + remoteGenre = remoteGenre, + remoteStatus = remoteStatus, + remoteCover = remoteCover, + remoteUpdateStrategy = remoteUpdateStrategy, + remoteMemo = remoteMemo, + userFavoriteAt = userFavoriteAt, + userNotes = userNotes, + userReaderFlags = userReaderFlags, + userChapterFlags = userChapterFlags, + stateChapterLastUpdate = stateChapterLastUpdate, + stateChapterNextUpdate = stateChapterNextUpdate, + stateChapterFetchInterval = stateChapterFetchInterval, + stateCoverLastModified = stateCoverLastModified, + stateInitialized = stateInitialized, ), - chapterCount = totalCount, + chapterCount = chapterCount, ) } diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index dc3b44549..078c2bff8 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -48,13 +48,13 @@ class MangaRepositoryImpl( override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? { return database.mangaQueries - .getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga) + .getMangaByUrlAndSource(sourceId = sourceId, remoteUrl = url, mapper = MangaMapper::mapManga) .awaitAsOneOrNull() } override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow { return database.mangaQueries - .getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga) + .getMangaByUrlAndSource(sourceId = sourceId, remoteUrl = url, mapper = MangaMapper::mapManga) .subscribeToOneOrNull() } @@ -161,7 +161,11 @@ class MangaRepositoryImpl( excludedScanlators.minus(current).forEach { scanlator -> database.excluded_scanlatorQueries.insert(mangaId, scanlator) } - database.excluded_scanlatorQueries.remove(mangaId, current.minus(excludedScanlators)) + val toRemove = current.minus(excludedScanlators) + // An empty delete still tells every chapter query to run again + if (toRemove.isNotEmpty()) { + database.excluded_scanlatorQueries.remove(mangaId, toRemove) + } } } @@ -189,25 +193,25 @@ class MangaRepositoryImpl( return database.transactionWithResult { manga.map { database.mangaQueries.insertNetworkManga( - source = it.source, - url = it.url, - artist = it.artist, - author = it.author, - description = it.description, - genre = it.genre, - title = it.title, - status = it.status, - thumbnailUrl = it.thumbnailUrl, - favoriteAt = it.favoriteAt, - lastUpdate = it.lastUpdate, - nextUpdate = it.nextUpdate, - calculateInterval = it.fetchInterval.toLong(), - initialized = it.initialized, - viewerFlags = it.viewerFlags, - chapterFlags = it.chapterFlags, - coverLastModified = it.coverLastModified, - updateStrategy = it.updateStrategy, - memo = it.memo, + sourceId = it.source, + remoteUrl = it.url, + remoteArtist = it.artist, + remoteAuthor = it.author, + remoteDescription = it.description, + remoteGenre = it.genre, + remoteTitle = it.title, + remoteStatus = it.status, + remoteCover = it.thumbnailUrl, + userFavoriteAt = it.favoriteAt, + stateChapterLastUpdate = it.lastUpdate, + stateChapterNextUpdate = it.nextUpdate, + stateChapterFetchInterval = it.fetchInterval.toLong(), + stateInitialized = it.initialized, + userReaderFlags = it.viewerFlags, + userChapterFlags = it.chapterFlags, + stateCoverLastModified = it.coverLastModified, + remoteUpdateStrategy = it.updateStrategy, + remoteMemo = it.memo, updateTitle = it.title.isNotBlank(), updateCover = !it.thumbnailUrl.isNullOrBlank(), updateDetails = it.initialized, @@ -221,18 +225,18 @@ class MangaRepositoryImpl( override suspend fun updateRemote(update: MangaRemoteUpdate): Boolean { return try { database.mangaQueries.updateRemote( - artist = update.artist, - author = update.author, - description = update.description, - genre = update.genre, - title = update.title, - status = update.status, - thumbnailUrl = update.thumbnailUrl, - initialized = update.initialized, - coverLastModified = update.coverLastModified, - updateStrategy = update.updateStrategy, - memo = update.memo, - mangaId = update.id, + remoteArtist = update.artist, + remoteAuthor = update.author, + remoteDescription = update.description, + remoteGenre = update.genre, + remoteTitle = update.title, + remoteStatus = update.status, + remoteCover = update.thumbnailUrl, + stateInitialized = update.initialized, + stateCoverLastModified = update.coverLastModified, + remoteUpdateStrategy = update.updateStrategy, + remoteMemo = update.memo, + id = update.id, ) true } catch (e: Exception) { @@ -246,16 +250,16 @@ class MangaRepositoryImpl( mangaUpdates.forEach { value -> with(value) { database.mangaQueries.update( - favoriteAtSet = isSet(::favoriteAt), - favoriteAt = favoriteAt, - lastUpdate = lastUpdate, - nextUpdate = nextUpdate, - calculateInterval = fetchInterval?.toLong(), - viewer = viewerFlags, - chapterFlags = chapterFlags, - coverLastModified = coverLastModified, - mangaId = id, - notes = notes, + userFavoriteAtSet = isSet(::favoriteAt), + userFavoriteAt = favoriteAt, + stateChapterLastUpdate = lastUpdate, + stateChapterNextUpdate = nextUpdate, + stateChapterFetchInterval = fetchInterval?.toLong(), + userReaderFlags = viewerFlags, + userChapterFlags = chapterFlags, + stateCoverLastModified = coverLastModified, + id = id, + userNotes = notes, ) } } diff --git a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt index 8797d3c26..f5701c7bb 100644 --- a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt @@ -31,12 +31,12 @@ class StubSourceRepositoryImpl( } override suspend fun upsertStubSource(id: Long, lang: String, name: String) { - database.sourceQueries.upsert(id = id, lang = lang, name = name) + database.sourceQueries.upsert(id = id, name = name, language = lang) } private fun mapStubSource( id: Long, - lang: String, name: String, + lang: String, ): StubSource = StubSource(id = id, lang = lang, name = name) } diff --git a/data/src/main/java/tachiyomi/data/track/TrackMapper.kt b/data/src/main/java/tachiyomi/data/track/TrackMapper.kt index 83a3f5b86..183bedbcb 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackMapper.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackMapper.kt @@ -6,22 +6,22 @@ object TrackMapper { fun mapTrack( id: Long, mangaId: Long, - syncId: Long, + trackerId: Long, remoteId: Long, + remoteUrl: String, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Double, - remoteUrl: String, startDate: Long, finishDate: Long, private: Boolean, ): Track = Track( id = id, mangaId = mangaId, - trackerId = syncId, + trackerId = trackerId, remoteId = remoteId, libraryId = libraryId, title = title, diff --git a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt index af5aaaa1d..dc2650211 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt @@ -46,7 +46,7 @@ class TrackRepositoryImpl( override suspend fun delete(mangaId: Long, trackerId: Long) { database.manga_trackQueries.delete( mangaId = mangaId, - syncId = trackerId, + trackerId = trackerId, ) } @@ -63,7 +63,7 @@ class TrackRepositoryImpl( tracks.forEach { mangaTrack -> database.manga_trackQueries.upsert( mangaId = mangaTrack.mangaId, - syncId = mangaTrack.trackerId, + trackerId = mangaTrack.trackerId, remoteId = mangaTrack.remoteId, libraryId = mangaTrack.libraryId, title = mangaTrack.title, diff --git a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt index 651143628..5fe3ca017 100644 --- a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt @@ -1,5 +1,6 @@ package tachiyomi.data.updates +import app.cash.sqldelight.Query import app.cash.sqldelight.async.coroutines.awaitAsList import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding @@ -25,14 +26,7 @@ class UpdatesRepositoryImpl( after: Long, limit: Long, ): List { - return database.updatesViewQueries - .getUpdatesByReadStatus( - read = read, - after = after, - limit = limit, - mapper = ::mapUpdatesWithRelations, - ) - .awaitAsList() + return getUpdatesByReadStatus(read = read, after = after, limit = limit).awaitAsList() } override fun subscribeAll( @@ -67,14 +61,23 @@ class UpdatesRepositoryImpl( after: Long, limit: Long, ): Flow> { - return database.updatesViewQueries - .getUpdatesByReadStatus( - read = read, + return getUpdatesByReadStatus(read = read, after = after, limit = limit).subscribeToList() + } + + private fun getUpdatesByReadStatus(read: Boolean, after: Long, limit: Long): Query { + return if (read) { + database.updatesViewQueries.getReadUpdates( after = after, limit = limit, mapper = ::mapUpdatesWithRelations, ) - .subscribeToList() + } else { + database.updatesViewQueries.getUnreadUpdates( + after = after, + limit = limit, + mapper = ::mapUpdatesWithRelations, + ) + } } @Suppress("UNUSED_PARAMETER") diff --git a/data/src/main/sqldelight/tachiyomi/data/category.sq b/data/src/main/sqldelight/tachiyomi/data/category.sq index 4cd3d0343..512de4f82 100644 --- a/data/src/main/sqldelight/tachiyomi/data/category.sq +++ b/data/src/main/sqldelight/tachiyomi/data/category.sq @@ -19,24 +19,15 @@ END; getCategory: SELECT id, name, `order`, flags FROM category -WHERE id = :id -LIMIT 1; +WHERE id = :id; getCategories: -SELECT -id, -name, -`order`, -flags +SELECT id, name, `order`, flags FROM category ORDER BY `order`; getCategoriesByMangaId: -SELECT -C.id, -C.name, -C.`order`, -C.flags +SELECT C.id, C.name, C.`order`, C.flags FROM category C JOIN manga_category MC ON C.id = MC.category_id @@ -44,18 +35,19 @@ WHERE MC.manga_id = :mangaId ORDER BY C.`order`; insert: +-- The position is taken inside the insert, so two categories created at once cannot claim the same one INSERT INTO category(name, `order`, flags) SELECT :name, coalesce(max(`order`), -1) + 1, :flags FROM category; delete: DELETE FROM category -WHERE id = :categoryId; +WHERE id = :id; updateName: UPDATE category SET name = :name -WHERE id = :categoryId; +WHERE id = :id; getUserCategoryIds: SELECT id @@ -66,13 +58,13 @@ ORDER BY `order`; updateOrder: UPDATE category SET `order` = :order -WHERE id = :categoryId; +WHERE id = :id; updateFlags: UPDATE category SET flags = :flags -WHERE id = :categoryId; +WHERE id = :id; updateAllFlags: -UPDATE category SET -flags = coalesce(:flags, flags); +UPDATE category +SET flags = :flags; diff --git a/data/src/main/sqldelight/tachiyomi/data/chapter.sq b/data/src/main/sqldelight/tachiyomi/data/chapter.sq index 97dcd3c00..33aa3d321 100644 --- a/data/src/main/sqldelight/tachiyomi/data/chapter.sq +++ b/data/src/main/sqldelight/tachiyomi/data/chapter.sq @@ -25,19 +25,17 @@ CREATE INDEX chapter_unread_state_date_fetch_index ON chapter(state_date_fetch) getChapterById: SELECT - id, manga_id, remote_url, remote_name, remote_scanlator, - user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, - state_date_fetch, remote_date_upload, - remote_memo + id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number, + remote_date_upload, remote_order, remote_memo, user_read, user_bookmark, + user_last_page_read, state_date_fetch FROM chapter WHERE id = :id; getChaptersByMangaId: SELECT - C.id, C.manga_id, C.remote_url, C.remote_name, C.remote_scanlator, - C.user_read, C.user_bookmark, C.user_last_page_read, C.remote_chapter_number, C.remote_order, - C.state_date_fetch, C.remote_date_upload, - C.remote_memo + C.id, C.manga_id, C.remote_url, C.remote_name, C.remote_scanlator, C.remote_chapter_number, + C.remote_date_upload, C.remote_order, C.remote_memo, C.user_read, C.user_bookmark, + C.user_last_page_read, C.state_date_fetch FROM chapter C LEFT JOIN excluded_scanlator ES ON C.manga_id = ES.manga_id @@ -49,75 +47,70 @@ AND ( ); getScanlatorsByMangaId: -SELECT remote_scanlator +SELECT DISTINCT remote_scanlator FROM chapter WHERE manga_id = :mangaId; getBookmarkedChaptersByMangaId: SELECT - id, manga_id, remote_url, remote_name, remote_scanlator, - user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, - state_date_fetch, remote_date_upload, - remote_memo + id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number, + remote_date_upload, remote_order, remote_memo, user_read, user_bookmark, + user_last_page_read, state_date_fetch FROM chapter -WHERE user_bookmark -AND manga_id = :mangaId; - -getChapterByUrl: -SELECT - id, manga_id, remote_url, remote_name, remote_scanlator, - user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, - state_date_fetch, remote_date_upload, - remote_memo -FROM chapter -WHERE remote_url = :chapterUrl; +WHERE manga_id = :mangaId +AND user_bookmark; getChapterByUrlAndMangaId: SELECT - id, manga_id, remote_url, remote_name, remote_scanlator, - user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, - state_date_fetch, remote_date_upload, - remote_memo + id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number, + remote_date_upload, remote_order, remote_memo, user_read, user_bookmark, + user_last_page_read, state_date_fetch FROM chapter -WHERE remote_url = :chapterUrl -AND manga_id = :mangaId -LIMIT 1; +WHERE manga_id = :mangaId +AND remote_url = :remoteUrl; removeChaptersWithIds: DELETE FROM chapter WHERE id IN :chapterIds; -insert: -INSERT INTO chapter(manga_id, remote_url, remote_name, remote_scanlator, user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, state_date_fetch, remote_date_upload, remote_memo) -VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo); - insertReturningId: -INSERT INTO chapter(manga_id, remote_url, remote_name, remote_scanlator, user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, state_date_fetch, remote_date_upload, remote_memo) -VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo) +INSERT INTO chapter( + manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number, + remote_date_upload, remote_order, remote_memo, user_read, user_bookmark, + user_last_page_read, state_date_fetch +) +VALUES ( + :mangaId, :remoteUrl, :remoteName, :remoteScanlator, :remoteChapterNumber, + :remoteDateUpload, :remoteOrder, :remoteMemo, :userRead, :userBookmark, + :userLastPageRead, :stateDateFetch +) +-- Two syncs of the same entry can race; the chapter the other one already stored is skipped rather +-- than failing the whole batch +ON CONFLICT(manga_id, remote_url) DO NOTHING RETURNING id; update: UPDATE chapter -SET user_read = coalesce(:read, user_read), - user_bookmark = coalesce(:bookmark, user_bookmark), - user_last_page_read = coalesce(:lastPageRead, user_last_page_read), - state_date_fetch = coalesce(:dateFetch, state_date_fetch) -WHERE id = :chapterId; +SET user_read = coalesce(:userRead, user_read), + user_bookmark = coalesce(:userBookmark, user_bookmark), + user_last_page_read = coalesce(:userLastPageRead, user_last_page_read), + state_date_fetch = coalesce(:stateDateFetch, state_date_fetch) +WHERE id = :id; updateFromBackup: UPDATE chapter -SET user_read = coalesce(:read, user_read), - user_bookmark = coalesce(:bookmark, user_bookmark), - user_last_page_read = coalesce(:lastPageRead, user_last_page_read), - remote_memo = coalesce(:memo, remote_memo) -WHERE id = :chapterId; +SET user_read = coalesce(:userRead, user_read), + user_bookmark = coalesce(:userBookmark, user_bookmark), + user_last_page_read = coalesce(:userLastPageRead, user_last_page_read), + remote_memo = coalesce(:remoteMemo, remote_memo) +WHERE id = :id; updateRemote: UPDATE chapter -SET remote_name = :name, - remote_scanlator = coalesce(:scanlator, remote_scanlator), - remote_chapter_number = :chapterNumber, - remote_order = :sourceOrder, - remote_date_upload = coalesce(:dateUpload, remote_date_upload), - remote_memo = :memo -WHERE id = :chapterId; +SET remote_name = :remoteName, + remote_scanlator = coalesce(:remoteScanlator, remote_scanlator), + remote_chapter_number = :remoteChapterNumber, + remote_order = :remoteOrder, + remote_date_upload = coalesce(:remoteDateUpload, remote_date_upload), + remote_memo = :remoteMemo +WHERE id = :id; diff --git a/data/src/main/sqldelight/tachiyomi/data/excluded_scanlator.sq b/data/src/main/sqldelight/tachiyomi/data/excluded_scanlator.sq index 114a0f5c3..96758dbec 100644 --- a/data/src/main/sqldelight/tachiyomi/data/excluded_scanlator.sq +++ b/data/src/main/sqldelight/tachiyomi/data/excluded_scanlator.sq @@ -9,7 +9,8 @@ CREATE TABLE excluded_scanlator( insert: INSERT INTO excluded_scanlator(manga_id, scanlator) -VALUES (:mangaId, :scanlator); +VALUES (:mangaId, :scanlator) +ON CONFLICT(manga_id, scanlator) DO NOTHING; remove: DELETE FROM excluded_scanlator diff --git a/data/src/main/sqldelight/tachiyomi/data/extension_store.sq b/data/src/main/sqldelight/tachiyomi/data/extension_store.sq index 5b395104d..c95213d29 100644 --- a/data/src/main/sqldelight/tachiyomi/data/extension_store.sq +++ b/data/src/main/sqldelight/tachiyomi/data/extension_store.sq @@ -21,23 +21,26 @@ SELECT FROM extension_store; getCount: -SELECT COUNT(*) +SELECT count(*) FROM extension_store; upsert: -INSERT INTO extension_store(index_url, name, badge_label, signing_key, contact_website, contact_discord, is_legacy, extension_list_url) -VALUES (:indexUrl, :name, :badgeLabel, :signingKey, :contactWebsite, :contactDiscord, :isLegacy, :extensionListUrl) -ON CONFLICT(index_url) -DO UPDATE -SET - name = :name, - badge_label = :badgeLabel, - signing_key =: signingKey, - contact_website = :contactWebsite, - contact_discord = :contactDiscord, - is_legacy = :isLegacy, - extension_list_url = :extensionListUrl -WHERE index_url = :indexUrl; +INSERT INTO extension_store( + index_url, name, badge_label, signing_key, contact_website, contact_discord, is_legacy, + extension_list_url +) +VALUES ( + :indexUrl, :name, :badgeLabel, :signingKey, :contactWebsite, :contactDiscord, :isLegacy, + :extensionListUrl +) +ON CONFLICT(index_url) DO UPDATE SET + name = excluded.name, + badge_label = excluded.badge_label, + signing_key = excluded.signing_key, + contact_website = excluded.contact_website, + contact_discord = excluded.contact_discord, + is_legacy = excluded.is_legacy, + extension_list_url = excluded.extension_list_url; delete: DELETE FROM extension_store diff --git a/data/src/main/sqldelight/tachiyomi/data/history.sq b/data/src/main/sqldelight/tachiyomi/data/history.sq index ab0554e42..1607b04a0 100644 --- a/data/src/main/sqldelight/tachiyomi/data/history.sq +++ b/data/src/main/sqldelight/tachiyomi/data/history.sq @@ -16,30 +16,14 @@ CREATE INDEX history_manga_id_index ON history(manga_id); CREATE INDEX history_read_at_index ON history(read_at); getHistoryByMangaId: -SELECT -H.id, -H.chapter_id, -H.read_at, -H.read_duration -FROM history H -WHERE H.manga_id = :mangaId; - -getHistoryByChapterUrlAndMangaId: -SELECT -H.id, -H.chapter_id, -H.read_at, -H.read_duration -FROM history H -JOIN chapter C -ON H.chapter_id = C.id -WHERE C.remote_url = :chapterUrl AND C.manga_id = :mangaId -LIMIT 1; +SELECT id, chapter_id, read_at, read_duration +FROM history +WHERE manga_id = :mangaId; resetHistoryById: UPDATE history SET read_at = 0 -WHERE id = :historyId; +WHERE id = :id; resetHistoryByMangaId: UPDATE history @@ -55,15 +39,12 @@ WHERE read_at = 0; upsert: INSERT INTO history(manga_id, chapter_id, read_at, read_duration) -SELECT C.manga_id, :chapterId, :readAt, :readDuration -FROM chapter C -WHERE C.id = :chapterId -ON CONFLICT(chapter_id) -DO UPDATE -SET - read_at = :readAt, - read_duration = read_duration + :readDuration -WHERE chapter_id = :chapterId; +SELECT manga_id, id, :readAt, :readDuration +FROM chapter +WHERE id = :chapterId +ON CONFLICT(chapter_id) DO UPDATE SET + read_at = excluded.read_at, + read_duration = read_duration + excluded.read_duration; getReadDuration: SELECT coalesce(sum(read_duration), 0) diff --git a/data/src/main/sqldelight/tachiyomi/data/manga.sq b/data/src/main/sqldelight/tachiyomi/data/manga.sq index fa570e857..e968e8085 100644 --- a/data/src/main/sqldelight/tachiyomi/data/manga.sq +++ b/data/src/main/sqldelight/tachiyomi/data/manga.sq @@ -33,73 +33,68 @@ CREATE INDEX manga_user_favorite_at_index ON manga(user_favorite_at) WHERE user_ getMangaById: SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized FROM manga WHERE id = :id; --- TODO: this should ideally never really have more than 1 result getMangaByUrlAndSource: SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized FROM manga -WHERE remote_url = :url -AND source_id = :source -LIMIT 1; +WHERE source_id = :sourceId +AND remote_url = :remoteUrl; getFavorites: SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized FROM manga WHERE user_favorite_at IS NOT NULL; getReadMangaNotInLibrary: SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized FROM manga -WHERE user_favorite_at IS NULL AND id IN ( - SELECT DISTINCT chapter.manga_id +WHERE user_favorite_at IS NULL +AND EXISTS ( + SELECT 1 FROM chapter - WHERE user_read = 1 OR user_last_page_read != 0 + WHERE chapter.manga_id = manga.id + AND (user_read = 1 OR user_last_page_read != 0) ); -getAllManga: -SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at -FROM manga; - getAllMangaSourceAndUrl: SELECT source_id, remote_url FROM manga; getSourceIdWithFavoriteCount: -SELECT -source_id, -count(*) +SELECT source_id, count(*) FROM manga WHERE user_favorite_at IS NOT NULL GROUP BY source_id; getFavoriteBySourceId: SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized FROM manga WHERE user_favorite_at IS NOT NULL AND source_id = :sourceId; @@ -107,57 +102,43 @@ AND source_id = :sourceId; getDuplicateLibraryManga: WITH track_dupes AS ( - SELECT DISTINCT S2.manga_id - FROM manga_track S1 - INNER JOIN manga_track S2 - ON S1.tracker_id = S2.tracker_id - AND S1.remote_id = S2.remote_id - AND S1.remote_id != 0 - AND S1.manga_id != S2.manga_id - WHERE S1.manga_id = :id -), -duplicates AS ( - SELECT - M.id, M.source_id, M.remote_url, M.remote_artist, M.remote_author, M.remote_description, M.remote_genre, M.remote_title, - M.remote_status, M.remote_cover, M.state_chapter_last_update, M.state_chapter_next_update, M.state_initialized, - M.user_reader_flags, M.user_chapter_flags, M.state_cover_last_modified, M.remote_update_strategy, - M.state_chapter_fetch_interval, M.user_notes, M.remote_memo, M.user_favorite_at - FROM manga M - LEFT JOIN track_dupes D - ON D.manga_id = id - WHERE user_favorite_at IS NOT NULL - AND id != :id - AND (lower(remote_title) LIKE '%' || lower(:title) || '%' OR D.manga_id IS NOT NULL) -), -chapter_counts AS ( - SELECT - M.id AS manga_id, - count(*) AS chapter_count - FROM duplicates M - JOIN chapter C - ON M.id = C.manga_id - LEFT JOIN excluded_scanlator ES - ON C.manga_id = ES.manga_id - AND C.remote_scanlator = ES.scanlator - WHERE ES.scanlator IS NULL - GROUP BY M.id + SELECT DISTINCT T2.manga_id + FROM manga_track T1 + JOIN manga_track T2 + ON T1.tracker_id = T2.tracker_id + AND T1.remote_id = T2.remote_id + AND T1.remote_id != 0 + AND T1.manga_id != T2.manga_id + WHERE T1.manga_id = :id ) SELECT - M.id, M.source_id, M.remote_url, M.remote_artist, M.remote_author, M.remote_description, M.remote_genre, M.remote_title, - M.remote_status, M.remote_cover, M.state_chapter_last_update, M.state_chapter_next_update, M.state_initialized, - M.user_reader_flags, M.user_chapter_flags, M.state_cover_last_modified, M.remote_update_strategy, - M.state_chapter_fetch_interval, M.user_notes, M.remote_memo, M.user_favorite_at, - coalesce(CC.chapter_count, 0) AS chapter_count -FROM duplicates M -LEFT JOIN chapter_counts CC -ON M.id = CC.manga_id; + M.id, M.source_id, M.remote_url, M.remote_title, M.remote_author, M.remote_artist, + M.remote_description, M.remote_genre, M.remote_status, M.remote_cover, + M.remote_update_strategy, M.remote_memo, M.user_favorite_at, M.user_notes, + M.user_reader_flags, M.user_chapter_flags, M.state_chapter_last_update, + M.state_chapter_next_update, M.state_chapter_fetch_interval, + M.state_cover_last_modified, M.state_initialized, + ( + SELECT count(*) + FROM chapter C + LEFT JOIN excluded_scanlator ES + ON C.manga_id = ES.manga_id + AND C.remote_scanlator = ES.scanlator + WHERE C.manga_id = M.id + AND ES.scanlator IS NULL + ) AS chapter_count +FROM manga M +WHERE M.user_favorite_at IS NOT NULL +AND M.id != :id +AND (lower(M.remote_title) LIKE '%' || lower(:remoteTitle) || '%' OR M.id IN (SELECT manga_id FROM track_dupes)); getUpcomingManga: SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized FROM manga WHERE state_chapter_next_update >= :startOfDay AND user_favorite_at IS NOT NULL @@ -168,7 +149,7 @@ AND ( OR EXISTS ( SELECT 1 FROM manga_category WHERE manga_category.manga_id = manga.id - AND COALESCE(manga_category.category_id, 0) IN :includedCategories + AND manga_category.category_id IN :includedCategories ) OR (0 IN :includedCategories AND NOT EXISTS ( SELECT 1 FROM manga_category WHERE manga_category.manga_id = manga.id) @@ -181,7 +162,7 @@ AND ( NOT EXISTS ( SELECT 1 FROM manga_category WHERE manga_category.manga_id = manga.id - AND COALESCE(manga_category.category_id, 0) IN :excludedCategories + AND manga_category.category_id IN :excludedCategories ) AND ( 0 NOT IN :excludedCategories @@ -193,10 +174,11 @@ ORDER BY state_chapter_next_update ASC; resetViewerFlags: UPDATE manga -SET user_reader_flags = 0; +SET user_reader_flags = 0 +WHERE user_reader_flags != 0; getSourceIdsWithNonLibraryManga: -SELECT source_id, COUNT(*) AS manga_count +SELECT source_id, count(*) AS manga_count FROM manga WHERE user_favorite_at IS NULL GROUP BY source_id; @@ -207,105 +189,119 @@ WHERE user_favorite_at IS NULL AND source_id IN :sourceIds AND ( :keepReadManga = 0 - OR id NOT IN ( - SELECT DISTINCT manga_id + OR NOT EXISTS ( + SELECT 1 FROM chapter - WHERE user_read = 1 - OR user_last_page_read != 0 + WHERE chapter.manga_id = manga.id + AND (user_read = 1 OR user_last_page_read != 0) ) ); insertReturningId: -INSERT INTO manga(source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, remote_status, remote_cover, user_favorite_at, state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, state_chapter_fetch_interval, user_notes, remote_memo) -VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :updateStrategy, :calculateInterval, :notes, :memo) +INSERT INTO manga( + source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized +) +VALUES ( + :sourceId, :remoteUrl, :remoteTitle, :remoteAuthor, :remoteArtist, :remoteDescription, + :remoteGenre, :remoteStatus, :remoteCover, :remoteUpdateStrategy, :remoteMemo, + :userFavoriteAt, :userNotes, :userReaderFlags, :userChapterFlags, + :stateChapterLastUpdate, :stateChapterNextUpdate, :stateChapterFetchInterval, + :stateCoverLastModified, :stateInitialized +) RETURNING id; update: UPDATE manga SET -- NULL is a real value here (not in the library), which coalesce cannot write - user_favorite_at = CASE WHEN :favoriteAtSet THEN :favoriteAt ELSE user_favorite_at END, - state_chapter_last_update = coalesce(:lastUpdate, state_chapter_last_update), - state_chapter_next_update = coalesce(:nextUpdate, state_chapter_next_update), - user_reader_flags = coalesce(:viewer, user_reader_flags), - user_chapter_flags = coalesce(:chapterFlags, user_chapter_flags), - state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified), - state_chapter_fetch_interval = coalesce(:calculateInterval, state_chapter_fetch_interval), - user_notes = coalesce(:notes, user_notes) -WHERE id = :mangaId; + user_favorite_at = CASE WHEN :userFavoriteAtSet THEN :userFavoriteAt ELSE user_favorite_at END, + user_notes = coalesce(:userNotes, user_notes), + user_reader_flags = coalesce(:userReaderFlags, user_reader_flags), + user_chapter_flags = coalesce(:userChapterFlags, user_chapter_flags), + state_chapter_last_update = coalesce(:stateChapterLastUpdate, state_chapter_last_update), + state_chapter_next_update = coalesce(:stateChapterNextUpdate, state_chapter_next_update), + state_chapter_fetch_interval = coalesce(:stateChapterFetchInterval, state_chapter_fetch_interval), + state_cover_last_modified = coalesce(:stateCoverLastModified, state_cover_last_modified) +WHERE id = :id; updateFromBackup: UPDATE manga SET - remote_artist = coalesce(:artist, remote_artist), - remote_author = coalesce(:author, remote_author), - remote_description = coalesce(:description, remote_description), - remote_genre = coalesce(:genre, remote_genre), - remote_title = coalesce(:title, remote_title), - remote_status = coalesce(:status, remote_status), - remote_cover = coalesce(:thumbnailUrl, remote_cover), - user_favorite_at = :favoriteAt, - state_chapter_last_update = coalesce(:lastUpdate, state_chapter_last_update), - state_initialized = coalesce(:initialized, state_initialized), - user_reader_flags = coalesce(:viewer, user_reader_flags), - user_chapter_flags = coalesce(:chapterFlags, user_chapter_flags), - state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified), - remote_update_strategy = coalesce(:updateStrategy, remote_update_strategy), - user_notes = coalesce(:notes, user_notes), - remote_memo = coalesce(:memo, remote_memo) -WHERE id = :mangaId; + remote_title = coalesce(:remoteTitle, remote_title), + remote_author = coalesce(:remoteAuthor, remote_author), + remote_artist = coalesce(:remoteArtist, remote_artist), + remote_description = coalesce(:remoteDescription, remote_description), + remote_genre = coalesce(:remoteGenre, remote_genre), + remote_status = coalesce(:remoteStatus, remote_status), + remote_cover = coalesce(:remoteCover, remote_cover), + remote_update_strategy = coalesce(:remoteUpdateStrategy, remote_update_strategy), + remote_memo = coalesce(:remoteMemo, remote_memo), + user_favorite_at = :userFavoriteAt, + user_notes = coalesce(:userNotes, user_notes), + user_reader_flags = coalesce(:userReaderFlags, user_reader_flags), + user_chapter_flags = coalesce(:userChapterFlags, user_chapter_flags), + state_chapter_last_update = coalesce(:stateChapterLastUpdate, state_chapter_last_update), + state_cover_last_modified = coalesce(:stateCoverLastModified, state_cover_last_modified), + state_initialized = coalesce(:stateInitialized, state_initialized) +WHERE id = :id; updateRemote: UPDATE manga SET - remote_artist = coalesce(:artist, remote_artist), - remote_author = coalesce(:author, remote_author), - remote_description = coalesce(:description, remote_description), - remote_genre = coalesce(:genre, remote_genre), - remote_title = coalesce(:title, remote_title), - remote_status = :status, - remote_cover = coalesce(:thumbnailUrl, remote_cover), - state_initialized = :initialized, - state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified), - remote_update_strategy = :updateStrategy, - remote_memo = :memo -WHERE id = :mangaId; + remote_title = coalesce(:remoteTitle, remote_title), + remote_author = coalesce(:remoteAuthor, remote_author), + remote_artist = coalesce(:remoteArtist, remote_artist), + remote_description = coalesce(:remoteDescription, remote_description), + remote_genre = coalesce(:remoteGenre, remote_genre), + remote_status = :remoteStatus, + remote_cover = coalesce(:remoteCover, remote_cover), + remote_update_strategy = :remoteUpdateStrategy, + remote_memo = :remoteMemo, + state_cover_last_modified = coalesce(:stateCoverLastModified, state_cover_last_modified), + state_initialized = :stateInitialized +WHERE id = :id; insertNetworkManga { - -- Insert the manga if it doesn't exist already + -- A new entry is inserted as the source reported it. An existing one takes the fresh details + -- only while it is outside the library, so a library entry keeps what the user already has. INSERT INTO manga( - source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, remote_status, remote_cover, user_favorite_at, - state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, user_chapter_flags, state_cover_last_modified, - remote_update_strategy, state_chapter_fetch_interval, user_notes, remote_memo + source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized ) - SELECT - :source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, - :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, - :updateStrategy, :calculateInterval, '', :memo - WHERE NOT EXISTS(SELECT 0 FROM manga WHERE source_id = :source AND remote_url = :url); + VALUES ( + :sourceId, :remoteUrl, :remoteTitle, :remoteAuthor, :remoteArtist, :remoteDescription, + :remoteGenre, :remoteStatus, :remoteCover, :remoteUpdateStrategy, :remoteMemo, + :userFavoriteAt, '', :userReaderFlags, :userChapterFlags, + :stateChapterLastUpdate, :stateChapterNextUpdate, :stateChapterFetchInterval, + :stateCoverLastModified, :stateInitialized + ) + ON CONFLICT(source_id, remote_url) DO UPDATE SET + remote_title = CASE WHEN :updateTitle THEN excluded.remote_title ELSE remote_title END, + remote_cover = CASE WHEN :updateCover THEN excluded.remote_cover ELSE remote_cover END, + remote_author = CASE WHEN :updateDetails THEN excluded.remote_author ELSE remote_author END, + remote_artist = CASE WHEN :updateDetails THEN excluded.remote_artist ELSE remote_artist END, + remote_description = CASE WHEN :updateDetails THEN excluded.remote_description ELSE remote_description END, + remote_genre = CASE WHEN :updateDetails THEN excluded.remote_genre ELSE remote_genre END, + remote_status = CASE WHEN :updateDetails THEN excluded.remote_status ELSE remote_status END, + remote_update_strategy = CASE + WHEN :updateDetails THEN excluded.remote_update_strategy + ELSE remote_update_strategy + END, + remote_memo = excluded.remote_memo, + state_initialized = :updateDetails + WHERE user_favorite_at IS NULL; - -- Update the relevant details if applicable and not favorite - UPDATE manga - SET - remote_title = CASE WHEN :updateTitle THEN :title ELSE remote_title END, - remote_cover = CASE WHEN :updateCover THEN :thumbnailUrl ELSE remote_cover END, - remote_author = CASE WHEN :updateDetails THEN :author ELSE remote_author END, - remote_artist = CASE WHEN :updateDetails THEN :artist ELSE remote_artist END, - remote_description = CASE WHEN :updateDetails THEN :description ELSE remote_description END, - remote_genre = CASE WHEN :updateDetails THEN :genre ELSE remote_genre END, - remote_status = CASE WHEN :updateDetails THEN :status ELSE remote_status END, - remote_update_strategy = CASE WHEN :updateDetails THEN :updateStrategy ELSE remote_update_strategy END, - state_initialized = :updateDetails, - remote_memo = :memo - WHERE source_id = :source - AND remote_url = :url - AND user_favorite_at IS NULL; - - -- Finally return the manga SELECT - id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, - remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized, - user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, - state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized FROM manga - WHERE source_id = :source - AND remote_url = :url - LIMIT 1; + WHERE source_id = :sourceId + AND remote_url = :remoteUrl; } diff --git a/data/src/main/sqldelight/tachiyomi/data/manga_category.sq b/data/src/main/sqldelight/tachiyomi/data/manga_category.sq index 4974db32d..d70b36648 100644 --- a/data/src/main/sqldelight/tachiyomi/data/manga_category.sq +++ b/data/src/main/sqldelight/tachiyomi/data/manga_category.sq @@ -12,7 +12,8 @@ CREATE INDEX manga_category_category_id_index ON manga_category(category_id); insert: INSERT INTO manga_category(manga_id, category_id) -VALUES (:mangaId, :categoryId); +VALUES (:mangaId, :categoryId) +ON CONFLICT(manga_id, category_id) DO NOTHING; deleteMangaCategoryByMangaId: DELETE FROM manga_category diff --git a/data/src/main/sqldelight/tachiyomi/data/manga_track.sq b/data/src/main/sqldelight/tachiyomi/data/manga_track.sq index 9e1eef029..608fdc4a4 100644 --- a/data/src/main/sqldelight/tachiyomi/data/manga_track.sq +++ b/data/src/main/sqldelight/tachiyomi/data/manga_track.sq @@ -24,63 +24,64 @@ CREATE INDEX manga_track_tracker_id_remote_id_index ON manga_track(tracker_id, r delete: DELETE FROM manga_track -WHERE manga_id = :mangaId AND tracker_id = :syncId; +WHERE manga_id = :mangaId +AND tracker_id = :trackerId; getTracks: SELECT - id, manga_id, tracker_id, remote_id, library_id, - title, last_chapter_read, total_chapters, status, score, - remote_url, start_date, finish_date, private + id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read, + total_chapters, status, score, start_date, finish_date, private FROM manga_track; getTrackById: SELECT - id, manga_id, tracker_id, remote_id, library_id, - title, last_chapter_read, total_chapters, status, score, - remote_url, start_date, finish_date, private + id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read, + total_chapters, status, score, start_date, finish_date, private FROM manga_track WHERE id = :id; getTracksByMangaId: SELECT - id, manga_id, tracker_id, remote_id, library_id, - title, last_chapter_read, total_chapters, status, score, - remote_url, start_date, finish_date, private + id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read, + total_chapters, status, score, start_date, finish_date, private FROM manga_track WHERE manga_id = :mangaId; upsert: -INSERT INTO manga_track(manga_id,tracker_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date,private) -VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate,:private) -ON CONFLICT(manga_id, tracker_id) -DO UPDATE -SET - remote_id = :remoteId, - library_id = :libraryId, - title = :title, - last_chapter_read = :lastChapterRead, - total_chapters = :totalChapters, - status = :status, - score = :score, - remote_url = :remoteUrl, - start_date = :startDate, - finish_date = :finishDate, - private = :private -WHERE manga_id = :mangaId AND tracker_id = :syncId; +INSERT INTO manga_track( + manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read, + total_chapters, status, score, start_date, finish_date, private +) +VALUES ( + :mangaId, :trackerId, :remoteId, :remoteUrl, :libraryId, :title, :lastChapterRead, + :totalChapters, :status, :score, :startDate, :finishDate, :private +) +ON CONFLICT(manga_id, tracker_id) DO UPDATE SET + remote_id = excluded.remote_id, + remote_url = excluded.remote_url, + library_id = excluded.library_id, + title = excluded.title, + last_chapter_read = excluded.last_chapter_read, + total_chapters = excluded.total_chapters, + status = excluded.status, + score = excluded.score, + start_date = excluded.start_date, + finish_date = excluded.finish_date, + private = excluded.private; update: UPDATE manga_track SET manga_id = coalesce(:mangaId, manga_id), - tracker_id = coalesce(:syncId, tracker_id), - remote_id = coalesce(:mediaId, remote_id), + tracker_id = coalesce(:trackerId, tracker_id), + remote_id = coalesce(:remoteId, remote_id), + remote_url = coalesce(:remoteUrl, remote_url), library_id = coalesce(:libraryId, library_id), title = coalesce(:title, title), last_chapter_read = coalesce(:lastChapterRead, last_chapter_read), - total_chapters = coalesce(:totalChapter, total_chapters), + total_chapters = coalesce(:totalChapters, total_chapters), status = coalesce(:status, status), score = coalesce(:score, score), - remote_url = coalesce(:trackingUrl, remote_url), start_date = coalesce(:startDate, start_date), finish_date = coalesce(:finishDate, finish_date), private = coalesce(:private, private) diff --git a/data/src/main/sqldelight/tachiyomi/data/source.sq b/data/src/main/sqldelight/tachiyomi/data/source.sq index 93477cec6..07e290898 100644 --- a/data/src/main/sqldelight/tachiyomi/data/source.sq +++ b/data/src/main/sqldelight/tachiyomi/data/source.sq @@ -5,20 +5,17 @@ CREATE TABLE source( ); findAll: -SELECT id, language, name +SELECT id, name, language FROM source; findOne: -SELECT id, language, name +SELECT id, name, language FROM source WHERE id = :id; upsert: -INSERT INTO source(id, language, name) -VALUES (:id, :lang, :name) -ON CONFLICT(id) -DO UPDATE -SET - language = :lang, - name = :name -WHERE id = :id; +INSERT INTO source(id, name, language) +VALUES (:id, :name, :language) +ON CONFLICT(id) DO UPDATE SET + name = excluded.name, + language = excluded.language; diff --git a/data/src/main/sqldelight/tachiyomi/view/historyView.sq b/data/src/main/sqldelight/tachiyomi/view/historyView.sq index de09bc2e9..ec2fba9e9 100644 --- a/data/src/main/sqldelight/tachiyomi/view/historyView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/historyView.sq @@ -46,18 +46,22 @@ ORDER BY readAt DESC; getLatestHistory: SELECT -id, -mangaId, -chapterId, -title, -thumbnailUrl, -sourceId, -favorite, -coverLastModified, -chapterNumber, -readAt, -readDuration -FROM historyView -WHERE historyView.readAt > 0 -ORDER BY readAt DESC +history.id AS id, +manga.id AS mangaId, +chapter.id AS chapterId, +manga.remote_title AS title, +manga.remote_cover AS thumbnailUrl, +manga.source_id AS sourceId, +manga.user_favorite_at IS NOT NULL AS favorite, +manga.state_cover_last_modified AS coverLastModified, +chapter.remote_chapter_number AS chapterNumber, +history.read_at AS readAt, +history.read_duration AS readDuration +FROM history +JOIN manga +ON manga.id = history.manga_id +JOIN chapter +ON chapter.id = history.chapter_id +WHERE history.read_at > 0 +ORDER BY history.read_at DESC LIMIT 1; diff --git a/data/src/main/sqldelight/tachiyomi/view/libraryView.sq b/data/src/main/sqldelight/tachiyomi/view/libraryView.sq index 96449ec01..8c76d6adc 100644 --- a/data/src/main/sqldelight/tachiyomi/view/libraryView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/libraryView.sq @@ -40,11 +40,10 @@ WHERE M.user_favorite_at IS NOT NULL; library: SELECT - id, source_id, remote_url, remote_artist, remote_author, - remote_description, remote_genre, remote_title, remote_status, remote_cover, - state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, - user_chapter_flags, state_cover_last_modified, remote_update_strategy, state_chapter_fetch_interval, - user_notes, remote_memo, - user_favorite_at, totalCount, readCount, latestUpload, chapterFetchedAt, - lastRead, bookmarkCount, categories + id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description, + remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo, + user_favorite_at, user_notes, user_reader_flags, user_chapter_flags, + state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval, + state_cover_last_modified, state_initialized, totalCount, readCount, latestUpload, + chapterFetchedAt, lastRead, bookmarkCount, categories FROM libraryView; diff --git a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq index 9fb01696d..bfaa967ab 100644 --- a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq @@ -25,23 +25,11 @@ WHERE user_favorite_at IS NOT NULL AND state_date_fetch > user_favorite_at ORDER BY state_date_fetch DESC; -getRecentUpdates: -SELECT - mangaId, mangaTitle, chapterId, chapterName, - scanlator, chapterUrl, read, bookmark, - lastPageRead, sourceId, favorite, thumbnailUrl, - coverLastModified, dateUpload, dateFetch, excludedScanlator -FROM updatesView -WHERE dateUpload > :after -ORDER BY dateFetch DESC -LIMIT :limit; - getRecentUpdatesWithFilters: SELECT - mangaId, mangaTitle, chapterId, chapterName, - scanlator, chapterUrl, read, bookmark, - lastPageRead, sourceId, favorite, thumbnailUrl, - coverLastModified, dateUpload, dateFetch, excludedScanlator + mangaId, mangaTitle, chapterId, chapterName, scanlator, chapterUrl, read, bookmark, + lastPageRead, sourceId, favorite, thumbnailUrl, coverLastModified, dateUpload, dateFetch, + excludedScanlator FROM updatesView WHERE dateUpload > :after AND (:read IS NULL OR read = :read) @@ -52,19 +40,17 @@ AND ( OR (:started = 0 AND lastPageRead = 0 AND read = 0) ) AND (:bookmarked IS NULL OR bookmark = :bookmarked) -AND ( - (excludedScanlator IS NULL OR :hideExcludedScanlators = 0) -) +AND (excludedScanlator IS NULL OR :hideExcludedScanlators = 0) AND ( -- includedEmpty being true expresses "don't care" state and bypasses the membership filter :includedEmpty OR EXISTS ( SELECT 1 FROM manga_category WHERE manga_category.manga_id = mangaId - AND COALESCE(manga_category.category_id, 0) IN :includedCategories + AND manga_category.category_id IN :includedCategories ) OR (0 IN :includedCategories AND NOT EXISTS ( - SELECT 1 FROM manga_category mc WHERE mc.manga_id = mangaId) + SELECT 1 FROM manga_category WHERE manga_category.manga_id = mangaId) ) ) AND ( @@ -74,7 +60,7 @@ AND ( NOT EXISTS ( SELECT 1 FROM manga_category WHERE manga_category.manga_id = mangaId - AND COALESCE(manga_category.category_id, 0) IN :excludedCategories + AND manga_category.category_id IN :excludedCategories ) AND ( 0 NOT IN :excludedCategories @@ -85,14 +71,24 @@ AND ( ORDER BY dateFetch DESC LIMIT :limit; -getUpdatesByReadStatus: +getReadUpdates: SELECT - mangaId, mangaTitle, chapterId, chapterName, - scanlator, chapterUrl, read, bookmark, - lastPageRead, sourceId, favorite, thumbnailUrl, - coverLastModified, dateUpload, dateFetch, excludedScanlator + mangaId, mangaTitle, chapterId, chapterName, scanlator, chapterUrl, read, bookmark, + lastPageRead, sourceId, favorite, thumbnailUrl, coverLastModified, dateUpload, dateFetch, + excludedScanlator FROM updatesView -WHERE read = :read +WHERE read = 1 +AND dateUpload > :after +ORDER BY dateFetch DESC +LIMIT :limit; + +getUnreadUpdates: +SELECT + mangaId, mangaTitle, chapterId, chapterName, scanlator, chapterUrl, read, bookmark, + lastPageRead, sourceId, favorite, thumbnailUrl, coverLastModified, dateUpload, dateFetch, + excludedScanlator +FROM updatesView +WHERE read = 0 AND dateUpload > :after ORDER BY dateFetch DESC LIMIT :limit; diff --git a/domain/src/main/java/tachiyomi/domain/backup/repository/RestoreRepository.kt b/domain/src/main/java/tachiyomi/domain/backup/repository/RestoreRepository.kt index e442918b4..79413f6c4 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/repository/RestoreRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/repository/RestoreRepository.kt @@ -6,7 +6,7 @@ import tachiyomi.domain.manga.model.MangaUpdate interface RestoreRepository { - suspend fun getMangaUrlsBySourceId(): Map> + suspend fun getMangaUrlsBySourceId(): Map> /** * Restores [entries] all together: either every one of them is restored or none is. [update] is what to change on each diff --git a/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt b/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt index 9c425862f..548a92153 100644 --- a/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/category/repository/CategoryRepository.kt @@ -24,7 +24,7 @@ interface CategoryRepository { suspend fun updateFlags(categoryId: Long, flags: Long) - suspend fun updateAllFlags(flags: Long?) + suspend fun updateAllFlags(flags: Long) suspend fun updateAllOrders(orderedIds: List) diff --git a/domain/src/main/java/tachiyomi/domain/chapter/interactor/SetMangaDefaultChapterFlags.kt b/domain/src/main/java/tachiyomi/domain/chapter/interactor/SetMangaDefaultChapterFlags.kt index ec29364cf..5fa8144b6 100644 --- a/domain/src/main/java/tachiyomi/domain/chapter/interactor/SetMangaDefaultChapterFlags.kt +++ b/domain/src/main/java/tachiyomi/domain/chapter/interactor/SetMangaDefaultChapterFlags.kt @@ -16,23 +16,27 @@ class SetMangaDefaultChapterFlags( suspend fun await(manga: Manga) { withNonCancellableContext { - with(libraryPreferences) { - setMangaChapterFlags.awaitSetAllFlags( - mangaId = manga.id, - unreadFilter = filterChapterByRead.get(), - downloadedFilter = filterChapterByDownloaded.get(), - bookmarkedFilter = filterChapterByBookmarked.get(), - sortingMode = sortChapterBySourceOrNumber.get(), - sortingDirection = sortChapterByAscendingOrDescending.get(), - displayMode = displayChapterByNameOrNumber.get(), - ) - } + setDefaultFlags(listOf(manga.id)) } } suspend fun awaitAll() { withNonCancellableContext { - getFavorites.await().forEach { await(it) } + setDefaultFlags(getFavorites.await().map { it.id }) + } + } + + private suspend fun setDefaultFlags(mangaIds: List) { + with(libraryPreferences) { + setMangaChapterFlags.awaitSetAllFlags( + mangaIds = mangaIds, + unreadFilter = filterChapterByRead.get(), + downloadedFilter = filterChapterByDownloaded.get(), + bookmarkedFilter = filterChapterByBookmarked.get(), + sortingMode = sortChapterBySourceOrNumber.get(), + sortingDirection = sortChapterByAscendingOrDescending.get(), + displayMode = displayChapterByNameOrNumber.get(), + ) } } } diff --git a/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaChapterFlags.kt b/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaChapterFlags.kt index 150105295..9e0521976 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaChapterFlags.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaChapterFlags.kt @@ -67,7 +67,7 @@ class SetMangaChapterFlags( } suspend fun awaitSetAllFlags( - mangaId: Long, + mangaIds: List, unreadFilter: Long, downloadedFilter: Long, bookmarkedFilter: Long, @@ -75,16 +75,13 @@ class SetMangaChapterFlags( sortingDirection: Long, displayMode: Long, ): Boolean { - return mangaRepository.update( - MangaUpdate(mangaId) { - chapterFlags = 0L.setFlag(unreadFilter, Manga.CHAPTER_UNREAD_MASK) - .setFlag(downloadedFilter, Manga.CHAPTER_DOWNLOADED_MASK) - .setFlag(bookmarkedFilter, Manga.CHAPTER_BOOKMARKED_MASK) - .setFlag(sortingMode, Manga.CHAPTER_SORTING_MASK) - .setFlag(sortingDirection, Manga.CHAPTER_SORT_DIR_MASK) - .setFlag(displayMode, Manga.CHAPTER_DISPLAY_MASK) - }, - ) + val flags = 0L.setFlag(unreadFilter, Manga.CHAPTER_UNREAD_MASK) + .setFlag(downloadedFilter, Manga.CHAPTER_DOWNLOADED_MASK) + .setFlag(bookmarkedFilter, Manga.CHAPTER_BOOKMARKED_MASK) + .setFlag(sortingMode, Manga.CHAPTER_SORTING_MASK) + .setFlag(sortingDirection, Manga.CHAPTER_SORT_DIR_MASK) + .setFlag(displayMode, Manga.CHAPTER_DISPLAY_MASK) + return mangaRepository.updateAll(mangaIds.map { MangaUpdate(it) { chapterFlags = flags } }) } private fun Long.setFlag(flag: Long, mask: Long): Long {