Skip updating unchanged chapters and tracks when restoring backup

(cherry picked from commit ad3d915fc56ecb8328861fdc2bf9e5f5c2aadbe3)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt
This commit is contained in:
arkon 2023-12-15 22:42:24 -05:00 committed by Jobobby04
parent b0d7a611f7
commit 2db5aa53ea
6 changed files with 131 additions and 144 deletions

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupFlatMetadata import eu.kanade.tachiyomi.data.backup.models.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
@ -12,6 +13,7 @@ import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
import eu.kanade.tachiyomi.data.backup.models.BackupTracking
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
@ -31,7 +33,6 @@ import tachiyomi.core.i18n.stringResource
import tachiyomi.core.preference.AndroidPreferenceStore import tachiyomi.core.preference.AndroidPreferenceStore
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Manga_sync
import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.data.manga.MangaMapper import tachiyomi.data.manga.MangaMapper
import tachiyomi.data.manga.MergedMangaMapper import tachiyomi.data.manga.MergedMangaMapper
@ -42,12 +43,13 @@ import tachiyomi.domain.history.model.HistoryUpdate
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.FetchInterval import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.interactor.GetFlatMetadataById import tachiyomi.domain.manga.interactor.GetFlatMetadataById
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
import tachiyomi.domain.manga.interactor.InsertFlatMetadata import tachiyomi.domain.manga.interactor.InsertFlatMetadata
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
@ -66,10 +68,11 @@ class BackupRestorer(
private val handler: DatabaseHandler = Injekt.get(), private val handler: DatabaseHandler = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(), private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(), private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
private val fetchInterval: FetchInterval = Injekt.get(), private val fetchInterval: FetchInterval = Injekt.get(),
private val preferenceStore: PreferenceStore = Injekt.get(), private val preferenceStore: PreferenceStore = Injekt.get(),
@ -266,12 +269,11 @@ class BackupRestorer(
restoreMangaDetails( restoreMangaDetails(
manga = restoredManga, manga = restoredManga,
chapters = backupManga.getChaptersImpl(), chapters = backupManga.chapters,
categories = backupManga.categories, categories = backupManga.categories,
backupCategories = backupCategories, backupCategories = backupCategories,
history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead, it.readDuration) } + history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
backupManga.history, tracks = backupManga.tracking,
tracks = backupManga.getTrackingImpl(),
// SY --> // SY -->
mergedMangaReferences = backupManga.mergedMangaReferences, mergedMangaReferences = backupManga.mergedMangaReferences,
flatMetadata = backupManga.flatMetadata, flatMetadata = backupManga.flatMetadata,
@ -351,20 +353,30 @@ class BackupRestorer(
) )
} }
private suspend fun restoreChapters(manga: Manga, chapters: List<Chapter>) { private suspend fun restoreChapters(manga: Manga, backupChapters: List<BackupChapter>) {
val dbChaptersByUrl = getChaptersByMangaId.await(manga.id) val dbChaptersByUrl = getChaptersByMangaId.await(manga.id)
.associateBy { it.url } .associateBy { it.url }
val processed = chapters.map { chapter -> val (existingChapters, newChapters) = backupChapters
var updatedChapter = chapter .mapNotNull {
val chapter = it.toChapterImpl()
val dbChapter = dbChaptersByUrl[updatedChapter.url] val dbChapter = dbChaptersByUrl[chapter.url]
if (dbChapter != null) { ?: // New chapter
updatedChapter = updatedChapter return@mapNotNull chapter
if (chapter.forComparison() == dbChapter.forComparison()) {
// Same state; skip
return@mapNotNull null
}
// Update to an existing chapter
var updatedChapter = chapter
.copyFrom(dbChapter) .copyFrom(dbChapter)
.copy( .copy(
id = dbChapter.id, id = dbChapter.id,
bookmark = updatedChapter.bookmark || dbChapter.bookmark, mangaId = manga.id,
bookmark = chapter.bookmark || dbChapter.bookmark,
) )
if (dbChapter.read && !updatedChapter.read) { if (dbChapter.read && !updatedChapter.read) {
updatedChapter = updatedChapter.copy( updatedChapter = updatedChapter.copy(
@ -376,17 +388,18 @@ class BackupRestorer(
lastPageRead = dbChapter.lastPageRead, lastPageRead = dbChapter.lastPageRead,
) )
} }
updatedChapter
}
.partition { it.id > 0 }
insertNewChapters(newChapters)
updateExistingChapters(existingChapters)
} }
updatedChapter.copy(mangaId = manga.id) private fun Chapter.forComparison() =
} this.copy(id = 0L, mangaId = 0L, dateFetch = 0L, dateUpload = 0L, lastModifiedAt = 0L)
val (existingChapters, newChapters) = processed.partition { it.id > 0 } private suspend fun insertNewChapters(chapters: List<Chapter>) {
insertChapters(newChapters)
updateKnownChapters(existingChapters)
}
private suspend fun insertChapters(chapters: List<Chapter>) {
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.forEach { chapter ->
chaptersQueries.insert( chaptersQueries.insert(
@ -406,7 +419,7 @@ class BackupRestorer(
} }
} }
private suspend fun updateKnownChapters(chapters: List<Chapter>) { private suspend fun updateExistingChapters(chapters: List<Chapter>) {
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.forEach { chapter ->
chaptersQueries.update( chaptersQueries.update(
@ -461,21 +474,21 @@ class BackupRestorer(
private suspend fun restoreMangaDetails( private suspend fun restoreMangaDetails(
manga: Manga, manga: Manga,
chapters: List<Chapter>, chapters: List<BackupChapter>,
categories: List<Long>, categories: List<Long>,
backupCategories: List<BackupCategory>, backupCategories: List<BackupCategory>,
history: List<BackupHistory>, history: List<BackupHistory>,
tracks: List<Track>, tracks: List<BackupTracking>,
// SY --> // SY -->
mergedMangaReferences: List<BackupMergedMangaReference>, mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?, flatMetadata: BackupFlatMetadata?,
customManga: CustomMangaInfo?, customManga: CustomMangaInfo?,
// SY <-- // SY <--
): Manga { ): Manga {
restoreChapters(manga, chapters)
restoreCategories(manga, categories, backupCategories) restoreCategories(manga, categories, backupCategories)
restoreHistory(history) restoreChapters(manga, chapters)
restoreTracking(manga, tracks) restoreTracking(manga, tracks)
restoreHistory(history)
updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow) updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow)
// SY --> // SY -->
restoreMergedMangaReferencesForManga(manga.id, mergedMangaReferences) restoreMergedMangaReferencesForManga(manga.id, mergedMangaReferences)
@ -520,10 +533,9 @@ class BackupRestorer(
} }
} }
private suspend fun restoreHistory(history: List<BackupHistory>) { private suspend fun restoreHistory(backupHistory: List<BackupHistory>) {
// List containing history to be updated
val toUpdate = mutableListOf<HistoryUpdate>() val toUpdate = mutableListOf<HistoryUpdate>()
for ((url, lastRead, readDuration) in history) { for ((url, lastRead, readDuration) in backupHistory) {
var dbHistory = handler.awaitOneOrNull { historyQueries.getHistoryByChapterUrl(url) } var dbHistory = handler.awaitOneOrNull { historyQueries.getHistoryByChapterUrl(url) }
// Check if history already in database and update // Check if history already in database and update
if (dbHistory != null) { if (dbHistory != null) {
@ -553,6 +565,7 @@ class BackupRestorer(
} }
} }
} }
if (toUpdate.isNotEmpty()) {
handler.await(true) { handler.await(true) {
toUpdate.forEach { payload -> toUpdate.forEach { payload ->
historyQueries.upsert( historyQueries.upsert(
@ -563,66 +576,42 @@ class BackupRestorer(
} }
} }
} }
private suspend fun restoreTracking(manga: Manga, tracks: List<Track>) {
// Get tracks from database
val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id) }
val toUpdate = mutableListOf<Manga_sync>()
val toInsert = mutableListOf<Track>()
tracks
// Fix foreign keys with the current manga id
.map { it.copy(mangaId = manga.id) }
.forEach { track ->
var isInDatabase = false
for (dbTrack in dbTracks) {
if (track.syncId == dbTrack.sync_id) {
// The sync is already in the db, only update its fields
var temp = dbTrack
if (track.remoteId != dbTrack.remote_id) {
temp = temp.copy(remote_id = track.remoteId)
}
if (track.libraryId != dbTrack.library_id) {
temp = temp.copy(library_id = track.libraryId)
}
temp = temp.copy(last_chapter_read = max(dbTrack.last_chapter_read, track.lastChapterRead))
isInDatabase = true
toUpdate.add(temp)
break
}
}
if (!isInDatabase) {
// Insert new sync. Let the db assign the id
toInsert.add(track.copy(id = 0))
}
} }
// Update database private suspend fun restoreTracking(manga: Manga, backupTracks: List<BackupTracking>) {
if (toUpdate.isNotEmpty()) { val dbTrackBySyncId = getTracks.await(manga.id).associateBy { it.syncId }
handler.await(true) {
toUpdate.forEach { track -> val (existingTracks, newTracks) = backupTracks
manga_syncQueries.update( .mapNotNull {
track.manga_id, val track = it.getTrackImpl()
track.sync_id, val dbTrack = dbTrackBySyncId[track.syncId]
track.remote_id, ?: // New track
track.library_id, return@mapNotNull track.copy(
track.title, id = 0, // Let DB assign new ID
track.last_chapter_read, mangaId = manga.id,
track.total_chapters, )
track.status,
track.score, if (track.forComparison() == dbTrack.forComparison()) {
track.remote_url, // Same state; skip
track.start_date, return@mapNotNull null
track.finish_date, }
track._id,
// Update to an existing track
dbTrack.copy(
remoteId = track.remoteId,
libraryId = track.libraryId,
lastChapterRead = max(dbTrack.lastChapterRead, track.lastChapterRead),
) )
} }
.partition { it.id > 0 }
if (newTracks.isNotEmpty()) {
insertTrack.awaitAll(newTracks)
} }
} if (existingTracks.isNotEmpty()) {
if (toInsert.isNotEmpty()) {
handler.await(true) { handler.await(true) {
toInsert.forEach { track -> existingTracks.forEach { track ->
manga_syncQueries.insert( manga_syncQueries.update(
track.mangaId, track.mangaId,
track.syncId, track.syncId,
track.remoteId, track.remoteId,
@ -635,6 +624,7 @@ class BackupRestorer(
track.remoteUrl, track.remoteUrl,
track.startDate, track.startDate,
track.finishDate, track.finishDate,
track.id,
) )
} }
} }
@ -707,6 +697,8 @@ class BackupRestorer(
} }
// SY <-- // SY <--
private fun Track.forComparison() = this.copy(id = 0L, mangaId = 0L)
private fun restoreAppPreferences(preferences: List<BackupPreference>) { private fun restoreAppPreferences(preferences: List<BackupPreference>) {
restorePreferences(preferences, preferenceStore) restorePreferences(preferences, preferenceStore)

View File

@ -16,4 +16,8 @@ data class BrokenBackupHistory(
@ProtoNumber(0) var url: String, @ProtoNumber(0) var url: String,
@ProtoNumber(1) var lastRead: Long, @ProtoNumber(1) var lastRead: Long,
@ProtoNumber(2) var readDuration: Long = 0, @ProtoNumber(2) var readDuration: Long = 0,
) ) {
fun toBackupHistory(): BackupHistory {
return BackupHistory(url, lastRead, readDuration)
}
}

View File

@ -4,10 +4,8 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Serializable @Serializable
@ -81,12 +79,6 @@ data class BackupManga(
) )
} }
fun getChaptersImpl(): List<Chapter> {
return chapters.map {
it.toChapterImpl()
}
}
// SY --> // SY -->
fun getCustomMangaInfo(): CustomMangaInfo? { fun getCustomMangaInfo(): CustomMangaInfo? {
if (customTitle != null || if (customTitle != null ||
@ -110,12 +102,6 @@ data class BackupManga(
} }
// SY <-- // SY <--
fun getTrackingImpl(): List<Track> {
return tracking.map {
it.getTrackingImpl()
}
}
companion object { companion object {
fun copyFrom(manga: Manga /* SY --> */, customMangaInfo: CustomMangaInfo?/* SY <-- */): BackupManga { fun copyFrom(manga: Manga /* SY --> */, customMangaInfo: CustomMangaInfo?/* SY <-- */): BackupManga {
return BackupManga( return BackupManga(

View File

@ -30,7 +30,7 @@ data class BackupTracking(
) { ) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun getTrackingImpl(): Track { fun getTrackImpl(): Track {
return Track( return Track(
id = -1, id = -1,
mangaId = -1, mangaId = -1,

View File

@ -0,0 +1,35 @@
package tachiyomi.data.track
import tachiyomi.domain.track.model.Track
object TrackMapper {
fun mapTrack(
id: Long,
mangaId: Long,
syncId: Long,
remoteId: Long,
libraryId: Long?,
title: String,
lastChapterRead: Double,
totalChapters: Long,
status: Long,
score: Double,
remoteUrl: String,
startDate: Long,
finishDate: Long,
): Track = Track(
id = id,
mangaId = mangaId,
syncId = syncId,
remoteId = remoteId,
libraryId = libraryId,
title = title,
lastChapterRead = lastChapterRead,
totalChapters = totalChapters,
status = status,
score = score,
remoteUrl = remoteUrl,
startDate = startDate,
finishDate = finishDate,
)
}

View File

@ -10,38 +10,38 @@ class TrackRepositoryImpl(
) : TrackRepository { ) : TrackRepository {
override suspend fun getTrackById(id: Long): Track? { override suspend fun getTrackById(id: Long): Track? {
return handler.awaitOneOrNull { manga_syncQueries.getTrackById(id, ::mapTrack) } return handler.awaitOneOrNull { manga_syncQueries.getTrackById(id, TrackMapper::mapTrack) }
} }
// SY --> // SY -->
override suspend fun getTracks(): List<Track> { override suspend fun getTracks(): List<Track> {
return handler.awaitList { return handler.awaitList {
manga_syncQueries.getTracks(::mapTrack) manga_syncQueries.getTracks(TrackMapper::mapTrack)
} }
} }
override suspend fun getTracksByMangaIds(mangaIds: List<Long>): List<Track> { override suspend fun getTracksByMangaIds(mangaIds: List<Long>): List<Track> {
return handler.awaitList { return handler.awaitList {
manga_syncQueries.getTracksByMangaIds(mangaIds, ::mapTrack) manga_syncQueries.getTracksByMangaIds(mangaIds, TrackMapper::mapTrack)
} }
} }
// SY <-- // SY <--
override suspend fun getTracksByMangaId(mangaId: Long): List<Track> { override suspend fun getTracksByMangaId(mangaId: Long): List<Track> {
return handler.awaitList { return handler.awaitList {
manga_syncQueries.getTracksByMangaId(mangaId, ::mapTrack) manga_syncQueries.getTracksByMangaId(mangaId, TrackMapper::mapTrack)
} }
} }
override fun getTracksAsFlow(): Flow<List<Track>> { override fun getTracksAsFlow(): Flow<List<Track>> {
return handler.subscribeToList { return handler.subscribeToList {
manga_syncQueries.getTracks(::mapTrack) manga_syncQueries.getTracks(TrackMapper::mapTrack)
} }
} }
override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow<List<Track>> { override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow<List<Track>> {
return handler.subscribeToList { return handler.subscribeToList {
manga_syncQueries.getTracksByMangaId(mangaId, ::mapTrack) manga_syncQueries.getTracksByMangaId(mangaId, TrackMapper::mapTrack)
} }
} }
@ -82,34 +82,4 @@ class TrackRepositoryImpl(
} }
} }
} }
private fun mapTrack(
id: Long,
mangaId: Long,
syncId: Long,
remoteId: Long,
libraryId: Long?,
title: String,
lastChapterRead: Double,
totalChapters: Long,
status: Long,
score: Double,
remoteUrl: String,
startDate: Long,
finishDate: Long,
): Track = Track(
id = id,
mangaId = mangaId,
syncId = syncId,
remoteId = remoteId,
libraryId = libraryId,
title = title,
lastChapterRead = lastChapterRead,
totalChapters = totalChapters,
status = status,
score = score,
remoteUrl = remoteUrl,
startDate = startDate,
finishDate = finishDate,
)
} }