|
@@ -4,11 +4,13 @@ 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.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
|
|
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
|
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
|
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
|
|
@@ -25,7 +27,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.domain.category.interactor.GetCategories
|
|
import tachiyomi.domain.category.interactor.GetCategories
|
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
|
@@ -33,9 +34,10 @@ import tachiyomi.domain.chapter.model.Chapter
|
|
import tachiyomi.domain.history.model.HistoryUpdate
|
|
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.GetManga
|
|
|
|
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
|
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
|
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 uy.kohesive.injekt.Injekt
|
|
import uy.kohesive.injekt.Injekt
|
|
@@ -53,10 +55,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(),
|
|
@@ -205,12 +208,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) } +
|
|
|
|
- backupManga.history,
|
|
|
|
- tracks = backupManga.getTrackingImpl(),
|
|
|
|
|
|
+ history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
|
|
|
|
+ tracks = backupManga.tracking,
|
|
)
|
|
)
|
|
} catch (e: Exception) {
|
|
} catch (e: Exception) {
|
|
val sourceName = sourceMapping[backupManga.source] ?: backupManga.source.toString()
|
|
val sourceName = sourceMapping[backupManga.source] ?: backupManga.source.toString()
|
|
@@ -283,20 +285,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 ->
|
|
|
|
- var updatedChapter = chapter
|
|
|
|
|
|
+ val (existingChapters, newChapters) = backupChapters
|
|
|
|
+ .mapNotNull {
|
|
|
|
+ val chapter = it.toChapterImpl()
|
|
|
|
|
|
- val dbChapter = dbChaptersByUrl[updatedChapter.url]
|
|
|
|
- if (dbChapter != null) {
|
|
|
|
- updatedChapter = updatedChapter
|
|
|
|
|
|
+ val dbChapter = dbChaptersByUrl[chapter.url]
|
|
|
|
+ ?: // New chapter
|
|
|
|
+ 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(
|
|
@@ -308,17 +320,18 @@ class BackupRestorer(
|
|
lastPageRead = dbChapter.lastPageRead,
|
|
lastPageRead = dbChapter.lastPageRead,
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
+ updatedChapter
|
|
}
|
|
}
|
|
|
|
+ .partition { it.id > 0 }
|
|
|
|
|
|
- updatedChapter.copy(mangaId = manga.id)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- val (existingChapters, newChapters) = processed.partition { it.id > 0 }
|
|
|
|
- insertChapters(newChapters)
|
|
|
|
- updateKnownChapters(existingChapters)
|
|
|
|
|
|
+ insertNewChapters(newChapters)
|
|
|
|
+ updateExistingChapters(existingChapters)
|
|
}
|
|
}
|
|
|
|
|
|
- private suspend fun insertChapters(chapters: List<Chapter>) {
|
|
|
|
|
|
+ private fun Chapter.forComparison() =
|
|
|
|
+ this.copy(id = 0L, mangaId = 0L, dateFetch = 0L, dateUpload = 0L, lastModifiedAt = 0L)
|
|
|
|
+
|
|
|
|
+ private suspend fun insertNewChapters(chapters: List<Chapter>) {
|
|
handler.await(true) {
|
|
handler.await(true) {
|
|
chapters.forEach { chapter ->
|
|
chapters.forEach { chapter ->
|
|
chaptersQueries.insert(
|
|
chaptersQueries.insert(
|
|
@@ -338,7 +351,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(
|
|
@@ -393,16 +406,16 @@ 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>,
|
|
): 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)
|
|
return manga
|
|
return manga
|
|
}
|
|
}
|
|
@@ -441,10 +454,9 @@ class BackupRestorer(
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private suspend fun restoreHistory(history: List<BackupHistory>) {
|
|
|
|
- // List containing history to be updated
|
|
|
|
|
|
+ private suspend fun restoreHistory(backupHistory: List<BackupHistory>) {
|
|
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) {
|
|
@@ -474,76 +486,53 @@ class BackupRestorer(
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- handler.await(true) {
|
|
|
|
- toUpdate.forEach { payload ->
|
|
|
|
- historyQueries.upsert(
|
|
|
|
- payload.chapterId,
|
|
|
|
- payload.readAt,
|
|
|
|
- payload.sessionReadDuration,
|
|
|
|
- )
|
|
|
|
|
|
+ if (toUpdate.isNotEmpty()) {
|
|
|
|
+ handler.await(true) {
|
|
|
|
+ toUpdate.forEach { payload ->
|
|
|
|
+ historyQueries.upsert(
|
|
|
|
+ payload.chapterId,
|
|
|
|
+ payload.readAt,
|
|
|
|
+ payload.sessionReadDuration,
|
|
|
|
+ )
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- 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
|
|
|
|
- if (toUpdate.isNotEmpty()) {
|
|
|
|
- handler.await(true) {
|
|
|
|
- toUpdate.forEach { track ->
|
|
|
|
- manga_syncQueries.update(
|
|
|
|
- track.manga_id,
|
|
|
|
- track.sync_id,
|
|
|
|
- track.remote_id,
|
|
|
|
- track.library_id,
|
|
|
|
- track.title,
|
|
|
|
- track.last_chapter_read,
|
|
|
|
- track.total_chapters,
|
|
|
|
- track.status,
|
|
|
|
- track.score,
|
|
|
|
- track.remote_url,
|
|
|
|
- track.start_date,
|
|
|
|
- track.finish_date,
|
|
|
|
- track._id,
|
|
|
|
|
|
+ private suspend fun restoreTracking(manga: Manga, backupTracks: List<BackupTracking>) {
|
|
|
|
+ val dbTrackBySyncId = getTracks.await(manga.id).associateBy { it.syncId }
|
|
|
|
+
|
|
|
|
+ val (existingTracks, newTracks) = backupTracks
|
|
|
|
+ .mapNotNull {
|
|
|
|
+ val track = it.getTrackImpl()
|
|
|
|
+ val dbTrack = dbTrackBySyncId[track.syncId]
|
|
|
|
+ ?: // New track
|
|
|
|
+ return@mapNotNull track.copy(
|
|
|
|
+ id = 0, // Let DB assign new ID
|
|
|
|
+ mangaId = manga.id,
|
|
)
|
|
)
|
|
|
|
+
|
|
|
|
+ if (track.forComparison() == dbTrack.forComparison()) {
|
|
|
|
+ // Same state; skip
|
|
|
|
+ return@mapNotNull null
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // 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 (toInsert.isNotEmpty()) {
|
|
|
|
|
|
+ if (existingTracks.isNotEmpty()) {
|
|
handler.await(true) {
|
|
handler.await(true) {
|
|
- toInsert.forEach { track ->
|
|
|
|
- manga_syncQueries.insert(
|
|
|
|
|
|
+ existingTracks.forEach { track ->
|
|
|
|
+ manga_syncQueries.update(
|
|
track.mangaId,
|
|
track.mangaId,
|
|
track.syncId,
|
|
track.syncId,
|
|
track.remoteId,
|
|
track.remoteId,
|
|
@@ -556,12 +545,15 @@ class BackupRestorer(
|
|
track.remoteUrl,
|
|
track.remoteUrl,
|
|
track.startDate,
|
|
track.startDate,
|
|
track.finishDate,
|
|
track.finishDate,
|
|
|
|
+ track.id,
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ 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)
|
|
|
|
|