refactor(ChapterSourceSync): Simplify code and insert new chapters in

bulk
This commit is contained in:
Ahmad Ansori Palembani 2024-12-12 11:00:46 +07:00
parent 365875590f
commit ea04968581
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
5 changed files with 60 additions and 59 deletions

View file

@ -90,4 +90,8 @@ interface Chapter : SChapter, Serializable {
source_order = other.source_order source_order = other.source_order
copyFrom(other as SChapter) copyFrom(other as SChapter)
} }
fun copy() = ChapterImpl().apply {
copyFrom(this@Chapter)
}
} }

View file

@ -117,53 +117,58 @@ suspend fun syncChaptersWithSource(
// Return if there's nothing to add, delete or change, avoid unnecessary db transactions. // Return if there's nothing to add, delete or change, avoid unnecessary db transactions.
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) { if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
val newestDate = dbChapters.maxOfOrNull { it.date_upload } ?: 0L 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 manga.last_update = newestDate
val update = MangaUpdate(manga.id!!, lastUpdate = manga.last_update) val update = MangaUpdate(manga.id!!, lastUpdate = newestDate)
updateManga.await(update) updateManga.await(update)
} }
return Pair(emptyList(), emptyList()) return Pair(emptyList(), emptyList())
} }
val readded = mutableListOf<Chapter>() val reAdded = mutableListOf<Chapter>()
val deletedChapterNumbers = TreeSet<Float>() val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>() val deletedReadChapterNumbers = TreeSet<Float>()
if (toDelete.isNotEmpty()) { val deletedBookmarkedChapterNumbers = TreeSet<Float>()
for (c in toDelete) { toDelete.forEach {
if (c.read) { if (it.read) deletedReadChapterNumbers.add(it.chapter_number)
deletedReadChapterNumbers.add(c.chapter_number) if (it.bookmark) deletedBookmarkedChapterNumbers.add(it.chapter_number)
} deletedChapterNumbers.add(it.chapter_number)
deletedChapterNumbers.add(c.chapter_number)
}
deleteChapter.awaitAll(toDelete)
} }
if (toAdd.isNotEmpty()) { val now = Date().time
// Set the date fetch for new items in reverse order to allow another sorting method.
// 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. // Sources MUST return the chapters from most to less recent, which is common.
var now = Date().time var itemCount = toAdd.size
var updatedToAdd = toAdd.map { toAddItem ->
val chapter: Chapter = toAddItem.copy()
for (i in toAdd.indices.reversed()) { chapter.date_fetch = now + itemCount--
val chapter = toAdd[i]
chapter.date_fetch = now++ if (!chapter.isRecognizedNumber || chapter.chapter_number !in deletedChapterNumbers) return@map chapter
if (chapter.isRecognizedNumber && chapter.chapter_number in deletedChapterNumbers) {
// Try to mark already read chapters as read when the source deletes them chapter.read = chapter.chapter_number in deletedReadChapterNumbers
if (chapter.chapter_number in deletedReadChapterNumbers) { chapter.bookmark = chapter.chapter_number in deletedBookmarkedChapterNumbers
chapter.read = true
} // Try to use the fetch date it originally had to not pollute 'Updates' tab
// Try to to use the fetch date it originally had to not pollute 'Updates' tab
toDelete.filter { it.chapter_number == chapter.chapter_number } toDelete.filter { it.chapter_number == chapter.chapter_number }
.minByOrNull { it.date_fetch }?.let { .minByOrNull { it.date_fetch }?.let {
chapter.date_fetch = it.date_fetch chapter.date_fetch = it.date_fetch
} }
readded.add(chapter) reAdded.add(chapter)
chapter
} }
if (toDelete.isNotEmpty()) {
val idsToDelete = toDelete.mapNotNull { it.id }
deleteChapter.awaitAllById(idsToDelete)
} }
toAdd.forEach { chapter ->
chapter.id = insertChapter.await(chapter) if (updatedToAdd.isNotEmpty()) {
} updatedToAdd = insertChapter.awaitBulk(toAdd)
} }
if (toChange.isNotEmpty()) { if (toChange.isNotEmpty()) {
@ -182,24 +187,15 @@ suspend fun syncChaptersWithSource(
} }
} }
var mangaUpdate: MangaUpdate? = null
// Set this manga as updated since chapters were changed // Set this manga as updated since chapters were changed
val newestChapterDate = getChapter.awaitAll(manga, false) // Note that last_update actually represents last time the chapter list changed at all
.maxOfOrNull { it.date_upload } ?: 0L // Those changes already checked beforehand, so we can proceed to updating the manga
if (newestChapterDate == 0L) {
if (toAdd.isNotEmpty()) {
manga.last_update = Date().time manga.last_update = Date().time
mangaUpdate = MangaUpdate(manga.id!!, lastUpdate = manga.last_update) updateManga.await(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) }
val reAddedSet = readded.toSet() val reAddedSet = reAdded.toSet()
return Pair( return Pair(
toAdd.subtract(reAddedSet).toList().filterChaptersByScanlators(manga), updatedToAdd.subtract(reAddedSet).toList().filterChaptersByScanlators(manga),
toDelete - reAddedSet, toDelete - reAddedSet,
) )
} }

View file

@ -54,27 +54,26 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos
override suspend fun delete(chapter: Chapter) = override suspend fun delete(chapter: Chapter) =
try { try {
partialDelete(chapter) partialDelete(chapter.id!!)
true true
} catch (e: Exception) { } catch (e: Exception) {
Logger.e(e) { "Failed to delete chapter with id '${chapter.id}'" } Logger.e(e) { "Failed to delete chapter with id '${chapter.id}'" }
false false
} }
override suspend fun deleteAll(chapters: List<Chapter>) = override suspend fun deleteAllById(chapters: List<Long>) =
try { try {
partialDelete(*chapters.toTypedArray()) partialDelete(*chapters.toLongArray())
true true
} catch (e: Exception) { } catch (e: Exception) {
Logger.e(e) { "Failed to bulk delete chapters" } Logger.e(e) { "Failed to bulk delete chapters" }
false false
} }
private suspend fun partialDelete(vararg chapters: Chapter) { private suspend fun partialDelete(vararg chapterIds: Long) {
handler.await(inTransaction = true) { handler.await(inTransaction = true) {
chapters.forEach { chapter -> chapterIds.forEach { chapterId ->
if (chapter.id == null) return@forEach chaptersQueries.delete(chapterId)
chaptersQueries.delete(chapter.id!!)
} }
} }
} }
@ -143,7 +142,7 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos
override suspend fun insertBulk(chapters: List<Chapter>) = override suspend fun insertBulk(chapters: List<Chapter>) =
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.map { chapter ->
chaptersQueries.insert( chaptersQueries.insert(
mangaId = chapter.manga_id!!, mangaId = chapter.manga_id!!,
url = chapter.url, url = chapter.url,
@ -158,6 +157,8 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos
dateFetch = chapter.date_fetch, dateFetch = chapter.date_fetch,
dateUpload = chapter.date_upload, dateUpload = chapter.date_upload,
) )
val lastInsertId = chaptersQueries.selectLastInsertedRowId().executeAsOne()
chapter.copy().apply { id = lastInsertId }
} }
} }
} }

View file

@ -23,11 +23,11 @@ interface ChapterRepository {
fun getScanlatorsByChapterAsFlow(mangaId: Long): Flow<List<String>> fun getScanlatorsByChapterAsFlow(mangaId: Long): Flow<List<String>>
suspend fun delete(chapter: Chapter): Boolean suspend fun delete(chapter: Chapter): Boolean
suspend fun deleteAll(chapters: List<Chapter>): Boolean suspend fun deleteAllById(chapters: List<Long>): Boolean
suspend fun update(update: ChapterUpdate): Boolean suspend fun update(update: ChapterUpdate): Boolean
suspend fun updateAll(updates: List<ChapterUpdate>): Boolean suspend fun updateAll(updates: List<ChapterUpdate>): Boolean
suspend fun insert(chapter: Chapter): Long? suspend fun insert(chapter: Chapter): Long?
suspend fun insertBulk(chapters: List<Chapter>) suspend fun insertBulk(chapters: List<Chapter>): List<Chapter>
} }

View file

@ -7,5 +7,5 @@ class DeleteChapter(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
) { ) {
suspend fun await(chapter: Chapter) = chapterRepository.delete(chapter) suspend fun await(chapter: Chapter) = chapterRepository.delete(chapter)
suspend fun awaitAll(chapters: List<Chapter>) = chapterRepository.deleteAll(chapters) suspend fun awaitAllById(chapterIds: List<Long>) = chapterRepository.deleteAllById(chapterIds)
} }