Make syncChaptersWithSource use sqldelight (#7263)

* Make `syncChaptersWithSource` use sqldelight

Will break chapter list live update on current ui

Co-Authored-By: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>

* Review Changes

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
This commit is contained in:
AntsyLich
2022-06-11 21:38:39 +06:00
committed by GitHub
parent 1e64542f14
commit 120943a8b3
20 changed files with 478 additions and 162 deletions

View File

@@ -0,0 +1,13 @@
package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.chapter.model.Chapter
class ShouldUpdateDbChapter {
fun await(dbChapter: Chapter, sourceChapter: Chapter): Boolean {
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
dbChapter.dateUpload != sourceChapter.dateUpload ||
dbChapter.chapterNumber != sourceChapter.chapterNumber ||
dbChapter.sourceOrder != sourceChapter.sourceOrder
}
}

View File

@@ -0,0 +1,179 @@
package eu.kanade.domain.chapter.interactor
import eu.kanade.data.chapter.NoChaptersException
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.toChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.domain.manga.interactor.UpdateMangaLastUpdate
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.lang.Long.max
import java.util.Date
import java.util.TreeSet
class SyncChaptersWithSource(
private val downloadManager: DownloadManager = Injekt.get(),
private val chapterRepository: ChapterRepository = Injekt.get(),
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
private val updateMangaLastUpdate: UpdateMangaLastUpdate = Injekt.get(),
) {
suspend fun await(
rawSourceChapters: List<SChapter>,
manga: Manga,
source: Source,
): Pair<List<Chapter>, List<Chapter>> {
if (rawSourceChapters.isEmpty() && source.id != LocalSource.ID) {
throw NoChaptersException()
}
val sourceChapters = rawSourceChapters
.distinctBy { it.url }
.mapIndexed { i, sChapter ->
Chapter.create()
.copyFromSChapter(sChapter)
.copy(mangaId = manga.id, sourceOrder = i.toLong())
}
// Chapters from db.
val dbChapters = chapterRepository.getChapterByMangaId(manga.id)
// Chapters from the source not in db.
val toAdd = mutableListOf<Chapter>()
// Chapters whose metadata have changed.
val toChange = mutableListOf<Chapter>()
// Chapters from the db not in source.
val toDelete = dbChapters.filterNot { dbChapter ->
sourceChapters.any { sourceChapter ->
dbChapter.url == sourceChapter.url
}
}
val rightNow = Date().time
// Used to not set upload date of older chapters
// to a higher value than newer chapters
var maxSeenUploadDate = 0L
val sManga = manga.toSManga()
for (sourceChapter in sourceChapters) {
var chapter = sourceChapter
// Update metadata from source if necessary.
if (source is HttpSource) {
val sChapter = chapter.toSChapter()
source.prepareNewChapter(sChapter, sManga)
chapter = chapter.copyFromSChapter(sChapter)
}
// Recognize chapter number for the chapter.
val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber)
chapter = chapter.copy(chapterNumber = chapterNumber)
val dbChapter = dbChapters.find { it.url == chapter.url }
if (dbChapter == null) {
if (chapter.dateUpload == 0L) {
val altDateUpload = if (maxSeenUploadDate == 0L) rightNow else maxSeenUploadDate
chapter = chapter.copy(dateUpload = altDateUpload)
} else {
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
}
toAdd.add(chapter)
} else {
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
if (dbChapter.name != chapter.name && downloadManager.isChapterDownloaded(dbChapter.toDbChapter(), manga.toDbManga())) {
downloadManager.renameChapter(source, manga.toDbManga(), dbChapter.toDbChapter(), chapter.toDbChapter())
}
chapter = dbChapter.copy(
name = sourceChapter.name,
chapterNumber = sourceChapter.chapterNumber,
scanlator = sourceChapter.scanlator,
sourceOrder = sourceChapter.sourceOrder,
)
if (sourceChapter.dateUpload != 0L) {
chapter = chapter.copy(dateUpload = sourceChapter.dateUpload)
}
toChange.add(chapter)
}
}
}
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
return Pair(emptyList(), emptyList())
}
val reAdded = mutableListOf<Chapter>()
val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>()
toDelete.forEach { chapter ->
if (chapter.read) {
deletedReadChapterNumbers.add(chapter.chapterNumber)
}
deletedChapterNumbers.add(chapter.chapterNumber)
}
val deletedChapterNumberDateFetchMap = toDelete.sortedByDescending { it.dateFetch }
.associate { it.chapterNumber to it.dateFetch }
// 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.
val now = Date().time
var itemCount = toAdd.size
var updatedToAdd = toAdd.map { toAddItem ->
var chapter = toAddItem.copy(dateFetch = now + itemCount--)
if (chapter.isRecognizedNumber.not() && chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
if (chapter.chapterNumber in deletedReadChapterNumbers) {
chapter = chapter.copy(read = true)
}
// Try to to use the fetch date of the original entry to not pollute 'Updates' tab
val oldDateFetch = deletedChapterNumberDateFetchMap[chapter.chapterNumber]
oldDateFetch?.let {
chapter = chapter.copy(dateFetch = it)
}
reAdded.add(chapter)
chapter
}
if (toDelete.isNotEmpty()) {
val toDeleteIds = toDelete.map { it.id }
chapterRepository.removeChaptersWithIds(toDeleteIds)
}
if (updatedToAdd.isNotEmpty()) {
updatedToAdd = chapterRepository.addAll(updatedToAdd)
}
if (toChange.isNotEmpty()) {
val chapterUpdates = toChange.map { it.toChapterUpdate() }
chapterRepository.updateAll(chapterUpdates)
}
// Set this manga as updated since chapters were changed
// Note that last_update actually represents last time the chapter list changed at all
updateMangaLastUpdate.await(manga.id, Date().time)
@Suppress("ConvertArgumentToSet") // See tachiyomiorg/tachiyomi#6372.
return Pair(updatedToAdd.subtract(reAdded).toList(), toDelete.subtract(reAdded).toList())
}
}

View File

@@ -1,5 +1,8 @@
package eu.kanade.domain.chapter.model
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
data class Chapter(
val id: Long,
val mangaId: Long,
@@ -13,4 +16,61 @@ data class Chapter(
val dateUpload: Long,
val chapterNumber: Float,
val scanlator: String?,
)
) {
val isRecognizedNumber: Boolean
get() = chapterNumber >= 0f
fun toSChapter(): SChapter {
return SChapter.create().also {
it.url = url
it.name = name
it.date_upload = dateUpload
it.chapter_number = chapterNumber
it.scanlator = scanlator
}
}
fun copyFromSChapter(sChapter: SChapter): Chapter {
return this.copy(
name = sChapter.name,
url = sChapter.url,
dateUpload = sChapter.date_upload,
chapterNumber = sChapter.chapter_number,
scanlator = sChapter.scanlator,
)
}
companion object {
fun create(): Chapter {
return Chapter(
id = -1,
mangaId = -1,
read = false,
bookmark = false,
lastPageRead = 0,
dateFetch = 0,
sourceOrder = 0,
url = "",
name = "",
dateUpload = -1,
chapterNumber = -1f,
scanlator = null,
)
}
}
}
// TODO: Remove when all deps are migrated
fun Chapter.toDbChapter(): DbChapter = DbChapter.create().also {
it.id = id
it.manga_id = mangaId
it.url = url
it.name = name
it.scanlator = scanlator
it.read = read
it.bookmark = bookmark
it.last_page_read = lastPageRead.toInt()
it.date_fetch = dateFetch
it.chapter_number = chapterNumber
it.source_order = sourceOrder.toInt()
}

View File

@@ -14,3 +14,7 @@ data class ChapterUpdate(
val chapterNumber: Float? = null,
val scanlator: String? = null,
)
fun Chapter.toChapterUpdate(): ChapterUpdate {
return ChapterUpdate(id, mangaId, read, bookmark, lastPageRead, dateFetch, sourceOrder, url, name, dateUpload, chapterNumber, scanlator)
}

View File

@@ -1,8 +1,17 @@
package eu.kanade.domain.chapter.repository
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.ChapterUpdate
interface ChapterRepository {
suspend fun addAll(chapters: List<Chapter>): List<Chapter>
suspend fun update(chapterUpdate: ChapterUpdate)
suspend fun updateAll(chapterUpdates: List<ChapterUpdate>)
suspend fun removeChaptersWithIds(chapterIds: List<Long>)
suspend fun getChapterByMangaId(mangaId: Long): List<Chapter>
}