diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt index d1ed846c03..1773e214eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt @@ -90,4 +90,8 @@ interface Chapter : SChapter, Serializable { source_order = other.source_order copyFrom(other as SChapter) } + + fun copy() = ChapterImpl().apply { + copyFrom(this@Chapter) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt index 3930be9bb8..bda3a024bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt @@ -117,53 +117,58 @@ suspend fun syncChaptersWithSource( // Return if there's nothing to add, delete or change, avoid unnecessary db transactions. if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) { val newestDate = dbChapters.maxOfOrNull { it.date_upload } ?: 0L - if (newestDate != 0L && newestDate != manga.last_update) { + if (newestDate != 0L && newestDate > manga.last_update) { manga.last_update = newestDate - val update = MangaUpdate(manga.id!!, lastUpdate = manga.last_update) + val update = MangaUpdate(manga.id!!, lastUpdate = newestDate) updateManga.await(update) } return Pair(emptyList(), emptyList()) } - val readded = mutableListOf() + val reAdded = mutableListOf() val deletedChapterNumbers = TreeSet() val deletedReadChapterNumbers = TreeSet() - if (toDelete.isNotEmpty()) { - for (c in toDelete) { - if (c.read) { - deletedReadChapterNumbers.add(c.chapter_number) - } - deletedChapterNumbers.add(c.chapter_number) - } - deleteChapter.awaitAll(toDelete) + val deletedBookmarkedChapterNumbers = TreeSet() + toDelete.forEach { + if (it.read) deletedReadChapterNumbers.add(it.chapter_number) + if (it.bookmark) deletedBookmarkedChapterNumbers.add(it.chapter_number) + deletedChapterNumbers.add(it.chapter_number) } - if (toAdd.isNotEmpty()) { - // Set the date fetch for new items in reverse order to allow another sorting method. - // Sources MUST return the chapters from most to less recent, which is common. - var now = Date().time + val now = Date().time - for (i in toAdd.indices.reversed()) { - val chapter = toAdd[i] - chapter.date_fetch = now++ - if (chapter.isRecognizedNumber && chapter.chapter_number in deletedChapterNumbers) { - // Try to mark already read chapters as read when the source deletes them - if (chapter.chapter_number in deletedReadChapterNumbers) { - chapter.read = true - } - // Try to to use the fetch date it originally had to not pollute 'Updates' tab - toDelete.filter { it.chapter_number == chapter.chapter_number } - .minByOrNull { it.date_fetch }?.let { - chapter.date_fetch = it.date_fetch - } + // Date fetch is set in such a way that the upper ones will have bigger value than the lower ones + // Sources MUST return the chapters from most to less recent, which is common. + var itemCount = toAdd.size + var updatedToAdd = toAdd.map { toAddItem -> + val chapter: Chapter = toAddItem.copy() - readded.add(chapter) + chapter.date_fetch = now + itemCount-- + + if (!chapter.isRecognizedNumber || chapter.chapter_number !in deletedChapterNumbers) return@map chapter + + chapter.read = chapter.chapter_number in deletedReadChapterNumbers + chapter.bookmark = chapter.chapter_number in deletedBookmarkedChapterNumbers + + // Try to use the fetch date it originally had to not pollute 'Updates' tab + toDelete.filter { it.chapter_number == chapter.chapter_number } + .minByOrNull { it.date_fetch }?.let { + chapter.date_fetch = it.date_fetch } - } - toAdd.forEach { chapter -> - chapter.id = insertChapter.await(chapter) - } + + reAdded.add(chapter) + + chapter + } + + if (toDelete.isNotEmpty()) { + val idsToDelete = toDelete.mapNotNull { it.id } + deleteChapter.awaitAllById(idsToDelete) + } + + if (updatedToAdd.isNotEmpty()) { + updatedToAdd = insertChapter.awaitBulk(toAdd) } if (toChange.isNotEmpty()) { @@ -182,24 +187,15 @@ suspend fun syncChaptersWithSource( } } - var mangaUpdate: MangaUpdate? = null // Set this manga as updated since chapters were changed - val newestChapterDate = getChapter.awaitAll(manga, false) - .maxOfOrNull { it.date_upload } ?: 0L - if (newestChapterDate == 0L) { - if (toAdd.isNotEmpty()) { - manga.last_update = Date().time - mangaUpdate = MangaUpdate(manga.id!!, lastUpdate = manga.last_update) - } - } else { - manga.last_update = newestChapterDate - mangaUpdate = MangaUpdate(manga.id!!, lastUpdate = manga.last_update) - } - mangaUpdate?.let { updateManga.await(it) } + // Note that last_update actually represents last time the chapter list changed at all + // Those changes already checked beforehand, so we can proceed to updating the manga + manga.last_update = Date().time + updateManga.await(MangaUpdate(manga.id!!, lastUpdate = manga.last_update)) - val reAddedSet = readded.toSet() + val reAddedSet = reAdded.toSet() return Pair( - toAdd.subtract(reAddedSet).toList().filterChaptersByScanlators(manga), + updatedToAdd.subtract(reAddedSet).toList().filterChaptersByScanlators(manga), toDelete - reAddedSet, ) } diff --git a/app/src/main/java/yokai/data/chapter/ChapterRepositoryImpl.kt b/app/src/main/java/yokai/data/chapter/ChapterRepositoryImpl.kt index feabecc904..1b38b71a9f 100644 --- a/app/src/main/java/yokai/data/chapter/ChapterRepositoryImpl.kt +++ b/app/src/main/java/yokai/data/chapter/ChapterRepositoryImpl.kt @@ -54,27 +54,26 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos override suspend fun delete(chapter: Chapter) = try { - partialDelete(chapter) + partialDelete(chapter.id!!) true } catch (e: Exception) { Logger.e(e) { "Failed to delete chapter with id '${chapter.id}'" } false } - override suspend fun deleteAll(chapters: List) = + override suspend fun deleteAllById(chapters: List) = try { - partialDelete(*chapters.toTypedArray()) + partialDelete(*chapters.toLongArray()) true } catch (e: Exception) { Logger.e(e) { "Failed to bulk delete chapters" } false } - private suspend fun partialDelete(vararg chapters: Chapter) { + private suspend fun partialDelete(vararg chapterIds: Long) { handler.await(inTransaction = true) { - chapters.forEach { chapter -> - if (chapter.id == null) return@forEach - chaptersQueries.delete(chapter.id!!) + chapterIds.forEach { chapterId -> + chaptersQueries.delete(chapterId) } } } @@ -143,7 +142,7 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos override suspend fun insertBulk(chapters: List) = handler.await(true) { - chapters.forEach { chapter -> + chapters.map { chapter -> chaptersQueries.insert( mangaId = chapter.manga_id!!, url = chapter.url, @@ -158,6 +157,8 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos dateFetch = chapter.date_fetch, dateUpload = chapter.date_upload, ) + val lastInsertId = chaptersQueries.selectLastInsertedRowId().executeAsOne() + chapter.copy().apply { id = lastInsertId } } } } diff --git a/app/src/main/java/yokai/domain/chapter/ChapterRepository.kt b/app/src/main/java/yokai/domain/chapter/ChapterRepository.kt index dbeae5b66a..dd39b017a1 100644 --- a/app/src/main/java/yokai/domain/chapter/ChapterRepository.kt +++ b/app/src/main/java/yokai/domain/chapter/ChapterRepository.kt @@ -23,11 +23,11 @@ interface ChapterRepository { fun getScanlatorsByChapterAsFlow(mangaId: Long): Flow> suspend fun delete(chapter: Chapter): Boolean - suspend fun deleteAll(chapters: List): Boolean + suspend fun deleteAllById(chapters: List): Boolean suspend fun update(update: ChapterUpdate): Boolean suspend fun updateAll(updates: List): Boolean suspend fun insert(chapter: Chapter): Long? - suspend fun insertBulk(chapters: List) + suspend fun insertBulk(chapters: List): List } diff --git a/app/src/main/java/yokai/domain/chapter/interactor/DeleteChapter.kt b/app/src/main/java/yokai/domain/chapter/interactor/DeleteChapter.kt index d3c09abbc9..9e71f51dc5 100644 --- a/app/src/main/java/yokai/domain/chapter/interactor/DeleteChapter.kt +++ b/app/src/main/java/yokai/domain/chapter/interactor/DeleteChapter.kt @@ -7,5 +7,5 @@ class DeleteChapter( private val chapterRepository: ChapterRepository, ) { suspend fun await(chapter: Chapter) = chapterRepository.delete(chapter) - suspend fun awaitAll(chapters: List) = chapterRepository.deleteAll(chapters) + suspend fun awaitAllById(chapterIds: List) = chapterRepository.deleteAllById(chapterIds) }