mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-15 05:27:28 +01:00
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:
@@ -43,7 +43,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
||||
internal suspend fun restoreChapters(source: Source, manga: Manga, chapters: List<Chapter>): Pair<List<Chapter>, List<Chapter>> {
|
||||
val fetchedChapters = source.getChapterList(manga.toMangaInfo())
|
||||
.map { it.toSChapter() }
|
||||
val syncedChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
|
||||
val syncedChapters = syncChaptersWithSource(fetchedChapters, manga, source)
|
||||
if (syncedChapters.first.isNotEmpty()) {
|
||||
chapters.forEach { it.manga_id = manga.id }
|
||||
updateChapters(chapters)
|
||||
|
||||
@@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import tachiyomi.source.model.MangaInfo
|
||||
import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||
|
||||
interface Manga : SManga {
|
||||
|
||||
@@ -128,3 +129,26 @@ fun Manga.toMangaInfo(): MangaInfo {
|
||||
title = this.title,
|
||||
)
|
||||
}
|
||||
|
||||
fun Manga.toDomainManga(): DomainManga? {
|
||||
val mangaId = id ?: return null
|
||||
return DomainManga(
|
||||
id = mangaId,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = last_update,
|
||||
dateAdded = date_added,
|
||||
viewerFlags = viewer_flags.toLong(),
|
||||
chapterFlags = chapter_flags.toLong(),
|
||||
coverLastModified = cover_last_modified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = getGenres(),
|
||||
status = status.toLong(),
|
||||
thumbnailUrl = thumbnail_url,
|
||||
initialized = initialized,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ class LibraryUpdateService(
|
||||
|
||||
// [dbmanga] was used so that manga data doesn't get overwritten
|
||||
// in case manga gets new chapter
|
||||
return syncChaptersWithSource(db, chapters, dbManga, source)
|
||||
return syncChaptersWithSource(chapters, dbManga, source)
|
||||
}
|
||||
|
||||
private suspend fun updateCovers() {
|
||||
|
||||
@@ -362,8 +362,7 @@ abstract class HttpSource : CatalogueSource {
|
||||
* @param chapter the chapter to be added.
|
||||
* @param manga the manga of the chapter.
|
||||
*/
|
||||
open fun prepareNewChapter(chapter: SChapter, manga: SManga) {
|
||||
}
|
||||
open fun prepareNewChapter(chapter: SChapter, manga: SManga) {}
|
||||
|
||||
/**
|
||||
* Returns the list of filters for the source.
|
||||
|
||||
@@ -115,7 +115,7 @@ class SearchPresenter(
|
||||
// Update chapters read
|
||||
if (migrateChapters) {
|
||||
try {
|
||||
syncChaptersWithSource(db, sourceChapters, manga, source)
|
||||
syncChaptersWithSource(sourceChapters, manga, source)
|
||||
} catch (e: Exception) {
|
||||
// Worst case, chapters won't be synced
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ class MangaPresenter(
|
||||
val chapters = source.getChapterList(manga.toMangaInfo())
|
||||
.map { it.toSChapter() }
|
||||
|
||||
val (newChapters, _) = syncChaptersWithSource(db, chapters, manga, source)
|
||||
val (newChapters, _) = syncChaptersWithSource(chapters, manga, source)
|
||||
if (manualFetch) {
|
||||
downloadNewChapters(newChapters)
|
||||
}
|
||||
|
||||
@@ -1,175 +1,37 @@
|
||||
package eu.kanade.tachiyomi.util.chapter
|
||||
|
||||
import eu.kanade.data.chapter.NoChaptersException
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
import kotlin.math.max
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga as DbManga
|
||||
|
||||
/**
|
||||
* Helper method for syncing the list of chapters from the source with the ones from the database.
|
||||
*
|
||||
* @param db the database.
|
||||
* @param rawSourceChapters a list of chapters from the source.
|
||||
* @param manga the manga of the chapters.
|
||||
* @param source the source of the chapters.
|
||||
* @return a pair of new insertions and deletions.
|
||||
*/
|
||||
fun syncChaptersWithSource(
|
||||
db: DatabaseHelper,
|
||||
rawSourceChapters: List<SChapter>,
|
||||
manga: Manga,
|
||||
manga: DbManga,
|
||||
source: Source,
|
||||
): Pair<List<Chapter>, List<Chapter>> {
|
||||
if (rawSourceChapters.isEmpty() && source !is LocalSource) {
|
||||
throw NoChaptersException()
|
||||
syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||
): Pair<List<DbChapter>, List<DbChapter>> {
|
||||
val domainManga = manga.toDomainManga() ?: return Pair(emptyList(), emptyList())
|
||||
val (added, deleted) = runBlocking {
|
||||
syncChaptersWithSource.await(rawSourceChapters, domainManga, source)
|
||||
}
|
||||
|
||||
val downloadManager: DownloadManager = Injekt.get()
|
||||
val addedDbChapters = added.map { it.toDbChapter() }
|
||||
val deletedDbChapters = deleted.map { it.toDbChapter() }
|
||||
|
||||
// Chapters from db.
|
||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||
|
||||
val sourceChapters = rawSourceChapters
|
||||
.distinctBy { it.url }
|
||||
.mapIndexed { i, sChapter ->
|
||||
Chapter.create().apply {
|
||||
copyFrom(sChapter)
|
||||
manga_id = manga.id
|
||||
source_order = i
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
var maxTimestamp = 0L // in previous chapters to add
|
||||
val rightNow = Date().time
|
||||
|
||||
for (sourceChapter in sourceChapters) {
|
||||
// This forces metadata update for the main viewable things in the chapter list.
|
||||
if (source is HttpSource) {
|
||||
source.prepareNewChapter(sourceChapter, manga)
|
||||
}
|
||||
// Recognize chapter number for the chapter.
|
||||
sourceChapter.chapter_number = ChapterRecognition.parseChapterNumber(manga.title, sourceChapter.name, sourceChapter.chapter_number)
|
||||
|
||||
val dbChapter = dbChapters.find { it.url == sourceChapter.url }
|
||||
|
||||
// Add the chapter if not in db already, or update if the metadata changed.
|
||||
if (dbChapter == null) {
|
||||
if (sourceChapter.date_upload == 0L) {
|
||||
sourceChapter.date_upload = if (maxTimestamp == 0L) rightNow else maxTimestamp
|
||||
} else {
|
||||
maxTimestamp = max(maxTimestamp, sourceChapter.date_upload)
|
||||
}
|
||||
toAdd.add(sourceChapter)
|
||||
} else {
|
||||
if (shouldUpdateDbChapter(dbChapter, sourceChapter)) {
|
||||
if (dbChapter.name != sourceChapter.name && downloadManager.isChapterDownloaded(dbChapter, manga)) {
|
||||
downloadManager.renameChapter(source, manga, dbChapter, sourceChapter)
|
||||
}
|
||||
dbChapter.scanlator = sourceChapter.scanlator
|
||||
dbChapter.name = sourceChapter.name
|
||||
dbChapter.chapter_number = sourceChapter.chapter_number
|
||||
dbChapter.source_order = sourceChapter.source_order
|
||||
if (sourceChapter.date_upload != 0L) {
|
||||
dbChapter.date_upload = sourceChapter.date_upload
|
||||
}
|
||||
toChange.add(dbChapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// Keep it a List instead of a Set. See #6372.
|
||||
val readded = mutableListOf<Chapter>()
|
||||
|
||||
db.inTransaction {
|
||||
val deletedChapterNumbers = TreeSet<Float>()
|
||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||
|
||||
if (toDelete.isNotEmpty()) {
|
||||
for (chapter in toDelete) {
|
||||
if (chapter.read) {
|
||||
deletedReadChapterNumbers.add(chapter.chapter_number)
|
||||
}
|
||||
deletedChapterNumbers.add(chapter.chapter_number)
|
||||
}
|
||||
db.deleteChapters(toDelete).executeAsBlocking()
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
readded.add(chapter)
|
||||
}
|
||||
}
|
||||
val chapters = db.insertChapters(toAdd).executeAsBlocking()
|
||||
toAdd.forEach { chapter ->
|
||||
chapter.id = chapters.results().getValue(chapter).insertedId()
|
||||
}
|
||||
}
|
||||
|
||||
if (toChange.isNotEmpty()) {
|
||||
db.insertChapters(toChange).executeAsBlocking()
|
||||
}
|
||||
|
||||
// Fix order in source.
|
||||
db.fixChaptersSourceOrder(sourceChapters).executeAsBlocking()
|
||||
|
||||
// Set this manga as updated since chapters were changed
|
||||
// Note that last_update actually represents last time the chapter list changed at all
|
||||
manga.last_update = Date().time
|
||||
db.updateLastUpdated(manga).executeAsBlocking()
|
||||
}
|
||||
|
||||
@Suppress("ConvertArgumentToSet")
|
||||
return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList())
|
||||
}
|
||||
|
||||
private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: Chapter): Boolean {
|
||||
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
|
||||
dbChapter.date_upload != sourceChapter.date_upload ||
|
||||
dbChapter.chapter_number != sourceChapter.chapter_number ||
|
||||
dbChapter.source_order != sourceChapter.source_order
|
||||
return Pair(addedDbChapters, deletedDbChapters)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user