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
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.
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<Chapter>()
val reAdded = mutableListOf<Chapter>()
val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>()
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<Float>()
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,
)
}

View file

@ -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<Chapter>) =
override suspend fun deleteAllById(chapters: List<Long>) =
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<Chapter>) =
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 }
}
}
}

View file

@ -23,11 +23,11 @@ interface ChapterRepository {
fun getScanlatorsByChapterAsFlow(mangaId: Long): Flow<List<String>>
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 updateAll(updates: List<ChapterUpdate>): Boolean
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,
) {
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)
}