|
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup
|
|
|
|
|
|
import android.content.Context
|
|
import android.content.Context
|
|
import android.net.Uri
|
|
import android.net.Uri
|
|
|
|
+import eu.kanade.domain.chapter.model.copyFrom
|
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
|
import eu.kanade.tachiyomi.R
|
|
import eu.kanade.tachiyomi.R
|
|
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
|
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
|
@@ -16,6 +17,7 @@ import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
|
|
import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
|
|
import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
|
|
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
|
|
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
|
|
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
|
|
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
|
|
|
|
+import eu.kanade.tachiyomi.source.model.copyFrom
|
|
import eu.kanade.tachiyomi.source.sourcePreferences
|
|
import eu.kanade.tachiyomi.source.sourcePreferences
|
|
import eu.kanade.tachiyomi.util.BackupUtil
|
|
import eu.kanade.tachiyomi.util.BackupUtil
|
|
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
|
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
|
@@ -23,7 +25,14 @@ import kotlinx.coroutines.coroutineScope
|
|
import kotlinx.coroutines.isActive
|
|
import kotlinx.coroutines.isActive
|
|
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.Manga_sync
|
|
|
|
+import tachiyomi.data.Mangas
|
|
|
|
+import tachiyomi.data.UpdateStrategyColumnAdapter
|
|
|
|
+import tachiyomi.domain.category.interactor.GetCategories
|
|
import tachiyomi.domain.chapter.model.Chapter
|
|
import tachiyomi.domain.chapter.model.Chapter
|
|
|
|
+import tachiyomi.domain.history.model.HistoryUpdate
|
|
|
|
+import tachiyomi.domain.library.service.LibraryPreferences
|
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
|
import tachiyomi.domain.manga.model.Manga
|
|
import tachiyomi.domain.manga.model.Manga
|
|
import tachiyomi.domain.track.model.Track
|
|
import tachiyomi.domain.track.model.Track
|
|
@@ -34,20 +43,24 @@ import java.text.SimpleDateFormat
|
|
import java.time.ZonedDateTime
|
|
import java.time.ZonedDateTime
|
|
import java.util.Date
|
|
import java.util.Date
|
|
import java.util.Locale
|
|
import java.util.Locale
|
|
|
|
+import kotlin.math.max
|
|
|
|
|
|
class BackupRestorer(
|
|
class BackupRestorer(
|
|
private val context: Context,
|
|
private val context: Context,
|
|
private val notifier: BackupNotifier,
|
|
private val notifier: BackupNotifier,
|
|
) {
|
|
) {
|
|
|
|
+
|
|
|
|
+ private val handler: DatabaseHandler = Injekt.get()
|
|
private val updateManga: UpdateManga = Injekt.get()
|
|
private val updateManga: UpdateManga = Injekt.get()
|
|
|
|
+ private val getCategories: GetCategories = 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()
|
|
|
|
+ private val libraryPreferences: LibraryPreferences = Injekt.get()
|
|
|
|
|
|
private var now = ZonedDateTime.now()
|
|
private var now = ZonedDateTime.now()
|
|
private var currentFetchWindow = fetchInterval.getWindow(now)
|
|
private var currentFetchWindow = fetchInterval.getWindow(now)
|
|
|
|
|
|
- private var backupManager = BackupManager(context)
|
|
|
|
-
|
|
|
|
private var restoreAmount = 0
|
|
private var restoreAmount = 0
|
|
private var restoreProgress = 0
|
|
private var restoreProgress = 0
|
|
|
|
|
|
@@ -102,7 +115,7 @@ class BackupRestorer(
|
|
private suspend fun performRestore(uri: Uri, sync: Boolean): Boolean {
|
|
private suspend fun performRestore(uri: Uri, sync: Boolean): Boolean {
|
|
val backup = BackupUtil.decodeBackup(context, uri)
|
|
val backup = BackupUtil.decodeBackup(context, uri)
|
|
|
|
|
|
- restoreAmount = backup.backupManga.size + 1 // +1 for categories
|
|
|
|
|
|
+ restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs
|
|
|
|
|
|
// Restore categories
|
|
// Restore categories
|
|
if (backup.backupCategories.isNotEmpty()) {
|
|
if (backup.backupCategories.isNotEmpty()) {
|
|
@@ -134,7 +147,38 @@ class BackupRestorer(
|
|
}
|
|
}
|
|
|
|
|
|
private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
|
|
private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
|
|
- backupManager.restoreCategories(backupCategories)
|
|
|
|
|
|
+ // Get categories from file and from db
|
|
|
|
+ val dbCategories = getCategories.await()
|
|
|
|
+
|
|
|
|
+ val categories = backupCategories.map {
|
|
|
|
+ var category = it.getCategory()
|
|
|
|
+ var found = false
|
|
|
|
+ for (dbCategory in dbCategories) {
|
|
|
|
+ // If the category is already in the db, assign the id to the file's category
|
|
|
|
+ // and do nothing
|
|
|
|
+ if (category.name == dbCategory.name) {
|
|
|
|
+ category = category.copy(id = dbCategory.id)
|
|
|
|
+ found = true
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (!found) {
|
|
|
|
+ // Let the db assign the id
|
|
|
|
+ val id = handler.awaitOneExecutable {
|
|
|
|
+ categoriesQueries.insert(category.name, category.order, category.flags)
|
|
|
|
+ categoriesQueries.selectLastInsertedRowId()
|
|
|
|
+ }
|
|
|
|
+ category = category.copy(id = id)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ category
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ libraryPreferences.categorizedDisplaySettings().set(
|
|
|
|
+ (dbCategories + categories)
|
|
|
|
+ .distinctBy { it.flags }
|
|
|
|
+ .size > 1,
|
|
|
|
+ )
|
|
|
|
|
|
restoreProgress += 1
|
|
restoreProgress += 1
|
|
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories), context.getString(R.string.restoring_backup))
|
|
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories), context.getString(R.string.restoring_backup))
|
|
@@ -149,14 +193,14 @@ class BackupRestorer(
|
|
val tracks = backupManga.getTrackingImpl()
|
|
val tracks = backupManga.getTrackingImpl()
|
|
|
|
|
|
try {
|
|
try {
|
|
- val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
|
|
|
|
|
|
+ val dbManga = getMangaFromDatabase(manga.url, manga.source)
|
|
val restoredManga = if (dbManga == null) {
|
|
val restoredManga = if (dbManga == null) {
|
|
// Manga not in database
|
|
// Manga not in database
|
|
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories)
|
|
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories)
|
|
} else {
|
|
} else {
|
|
// Manga in database
|
|
// Manga in database
|
|
// Copy information from manga already in database
|
|
// Copy information from manga already in database
|
|
- val updatedManga = backupManager.restoreExistingManga(manga, dbManga)
|
|
|
|
|
|
+ val updatedManga = restoreExistingManga(manga, dbManga)
|
|
// Fetch rest of manga information
|
|
// Fetch rest of manga information
|
|
restoreNewManga(updatedManga, chapters, categories, history, tracks, backupCategories)
|
|
restoreNewManga(updatedManga, chapters, categories, history, tracks, backupCategories)
|
|
}
|
|
}
|
|
@@ -174,6 +218,50 @@ class BackupRestorer(
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns manga
|
|
|
|
+ *
|
|
|
|
+ * @return [Manga], null if not found
|
|
|
|
+ */
|
|
|
|
+ private suspend fun getMangaFromDatabase(url: String, source: Long): Mangas? {
|
|
|
|
+ return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, source) }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas): Manga {
|
|
|
|
+ var updatedManga = manga.copy(id = dbManga._id)
|
|
|
|
+ updatedManga = updatedManga.copyFrom(dbManga)
|
|
|
|
+ updateManga(updatedManga)
|
|
|
|
+ return updatedManga
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private suspend fun updateManga(manga: Manga): Long {
|
|
|
|
+ handler.await(true) {
|
|
|
|
+ mangasQueries.update(
|
|
|
|
+ source = manga.source,
|
|
|
|
+ url = manga.url,
|
|
|
|
+ artist = manga.artist,
|
|
|
|
+ author = manga.author,
|
|
|
|
+ description = manga.description,
|
|
|
|
+ genre = manga.genre?.joinToString(separator = ", "),
|
|
|
|
+ title = manga.title,
|
|
|
|
+ status = manga.status,
|
|
|
|
+ thumbnailUrl = manga.thumbnailUrl,
|
|
|
|
+ favorite = manga.favorite,
|
|
|
|
+ lastUpdate = manga.lastUpdate,
|
|
|
|
+ nextUpdate = null,
|
|
|
|
+ calculateInterval = null,
|
|
|
|
+ initialized = manga.initialized,
|
|
|
|
+ viewer = manga.viewerFlags,
|
|
|
|
+ chapterFlags = manga.chapterFlags,
|
|
|
|
+ coverLastModified = manga.coverLastModified,
|
|
|
|
+ dateAdded = manga.dateAdded,
|
|
|
|
+ mangaId = manga.id,
|
|
|
|
+ updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ return manga.id
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Fetches manga information
|
|
* Fetches manga information
|
|
*
|
|
*
|
|
@@ -189,12 +277,131 @@ class BackupRestorer(
|
|
tracks: List<Track>,
|
|
tracks: List<Track>,
|
|
backupCategories: List<BackupCategory>,
|
|
backupCategories: List<BackupCategory>,
|
|
): Manga {
|
|
): Manga {
|
|
- val fetchedManga = backupManager.restoreNewManga(manga)
|
|
|
|
- backupManager.restoreChapters(fetchedManga, chapters)
|
|
|
|
|
|
+ val fetchedManga = restoreNewManga(manga)
|
|
|
|
+ restoreChapters(fetchedManga, chapters)
|
|
restoreExtras(fetchedManga, categories, history, tracks, backupCategories)
|
|
restoreExtras(fetchedManga, categories, history, tracks, backupCategories)
|
|
return fetchedManga
|
|
return fetchedManga
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private suspend fun restoreChapters(manga: Manga, chapters: List<Chapter>) {
|
|
|
|
+ val dbChapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id) }
|
|
|
|
+
|
|
|
|
+ val processed = chapters.map { chapter ->
|
|
|
|
+ var updatedChapter = chapter
|
|
|
|
+ val dbChapter = dbChapters.find { it.url == updatedChapter.url }
|
|
|
|
+ if (dbChapter != null) {
|
|
|
|
+ updatedChapter = updatedChapter.copy(id = dbChapter._id)
|
|
|
|
+ updatedChapter = updatedChapter.copyFrom(dbChapter)
|
|
|
|
+ if (dbChapter.read && !updatedChapter.read) {
|
|
|
|
+ updatedChapter = updatedChapter.copy(read = true, lastPageRead = dbChapter.last_page_read)
|
|
|
|
+ } else if (updatedChapter.lastPageRead == 0L && dbChapter.last_page_read != 0L) {
|
|
|
|
+ updatedChapter = updatedChapter.copy(lastPageRead = dbChapter.last_page_read)
|
|
|
|
+ }
|
|
|
|
+ if (!updatedChapter.bookmark && dbChapter.bookmark) {
|
|
|
|
+ updatedChapter = updatedChapter.copy(bookmark = true)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ updatedChapter.copy(mangaId = manga.id)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ val newChapters = processed.groupBy { it.id > 0 }
|
|
|
|
+ newChapters[true]?.let { updateKnownChapters(it) }
|
|
|
|
+ newChapters[false]?.let { insertChapters(it) }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Inserts list of chapters
|
|
|
|
+ */
|
|
|
|
+ private suspend fun insertChapters(chapters: List<Chapter>) {
|
|
|
|
+ handler.await(true) {
|
|
|
|
+ chapters.forEach { chapter ->
|
|
|
|
+ chaptersQueries.insert(
|
|
|
|
+ chapter.mangaId,
|
|
|
|
+ chapter.url,
|
|
|
|
+ chapter.name,
|
|
|
|
+ chapter.scanlator,
|
|
|
|
+ chapter.read,
|
|
|
|
+ chapter.bookmark,
|
|
|
|
+ chapter.lastPageRead,
|
|
|
|
+ chapter.chapterNumber,
|
|
|
|
+ chapter.sourceOrder,
|
|
|
|
+ chapter.dateFetch,
|
|
|
|
+ chapter.dateUpload,
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Updates a list of chapters with known database ids
|
|
|
|
+ */
|
|
|
|
+ private suspend fun updateKnownChapters(chapters: List<Chapter>) {
|
|
|
|
+ handler.await(true) {
|
|
|
|
+ chapters.forEach { chapter ->
|
|
|
|
+ chaptersQueries.update(
|
|
|
|
+ mangaId = null,
|
|
|
|
+ url = null,
|
|
|
|
+ name = null,
|
|
|
|
+ scanlator = null,
|
|
|
|
+ read = chapter.read,
|
|
|
|
+ bookmark = chapter.bookmark,
|
|
|
|
+ lastPageRead = chapter.lastPageRead,
|
|
|
|
+ chapterNumber = null,
|
|
|
|
+ sourceOrder = null,
|
|
|
|
+ dateFetch = null,
|
|
|
|
+ dateUpload = null,
|
|
|
|
+ chapterId = chapter.id,
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Fetches manga information
|
|
|
|
+ *
|
|
|
|
+ * @param manga manga that needs updating
|
|
|
|
+ * @return Updated manga info.
|
|
|
|
+ */
|
|
|
|
+ private suspend fun restoreNewManga(manga: Manga): Manga {
|
|
|
|
+ return manga.copy(
|
|
|
|
+ initialized = manga.description != null,
|
|
|
|
+ id = insertManga(manga),
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Inserts manga and returns id
|
|
|
|
+ *
|
|
|
|
+ * @return id of [Manga], null if not found
|
|
|
|
+ */
|
|
|
|
+ private suspend fun insertManga(manga: Manga): Long {
|
|
|
|
+ return handler.awaitOneExecutable(true) {
|
|
|
|
+ mangasQueries.insert(
|
|
|
|
+ source = manga.source,
|
|
|
|
+ url = manga.url,
|
|
|
|
+ artist = manga.artist,
|
|
|
|
+ author = manga.author,
|
|
|
|
+ description = manga.description,
|
|
|
|
+ genre = manga.genre,
|
|
|
|
+ title = manga.title,
|
|
|
|
+ status = manga.status,
|
|
|
|
+ thumbnailUrl = manga.thumbnailUrl,
|
|
|
|
+ favorite = manga.favorite,
|
|
|
|
+ lastUpdate = manga.lastUpdate,
|
|
|
|
+ nextUpdate = 0L,
|
|
|
|
+ calculateInterval = 0L,
|
|
|
|
+ initialized = manga.initialized,
|
|
|
|
+ viewerFlags = manga.viewerFlags,
|
|
|
|
+ chapterFlags = manga.chapterFlags,
|
|
|
|
+ coverLastModified = manga.coverLastModified,
|
|
|
|
+ dateAdded = manga.dateAdded,
|
|
|
|
+ updateStrategy = manga.updateStrategy,
|
|
|
|
+ )
|
|
|
|
+ mangasQueries.selectLastInsertedRowId()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
private suspend fun restoreNewManga(
|
|
private suspend fun restoreNewManga(
|
|
backupManga: Manga,
|
|
backupManga: Manga,
|
|
chapters: List<Chapter>,
|
|
chapters: List<Chapter>,
|
|
@@ -203,19 +410,187 @@ class BackupRestorer(
|
|
tracks: List<Track>,
|
|
tracks: List<Track>,
|
|
backupCategories: List<BackupCategory>,
|
|
backupCategories: List<BackupCategory>,
|
|
): Manga {
|
|
): Manga {
|
|
- backupManager.restoreChapters(backupManga, chapters)
|
|
|
|
|
|
+ restoreChapters(backupManga, chapters)
|
|
restoreExtras(backupManga, categories, history, tracks, backupCategories)
|
|
restoreExtras(backupManga, categories, history, tracks, backupCategories)
|
|
return backupManga
|
|
return backupManga
|
|
}
|
|
}
|
|
|
|
|
|
private suspend fun restoreExtras(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
|
|
private suspend fun restoreExtras(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
|
|
- backupManager.restoreCategories(manga, categories, backupCategories)
|
|
|
|
- backupManager.restoreHistory(history)
|
|
|
|
- backupManager.restoreTracking(manga, tracks)
|
|
|
|
|
|
+ restoreCategories(manga, categories, backupCategories)
|
|
|
|
+ restoreHistory(history)
|
|
|
|
+ restoreTracking(manga, tracks)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Restores the categories a manga is in.
|
|
|
|
+ *
|
|
|
|
+ * @param manga the manga whose categories have to be restored.
|
|
|
|
+ * @param categories the categories to restore.
|
|
|
|
+ */
|
|
|
|
+ private suspend fun restoreCategories(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
|
|
|
|
+ val dbCategories = getCategories.await()
|
|
|
|
+ val mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>()
|
|
|
|
+
|
|
|
|
+ categories.forEach { backupCategoryOrder ->
|
|
|
|
+ backupCategories.firstOrNull {
|
|
|
|
+ it.order == backupCategoryOrder.toLong()
|
|
|
|
+ }?.let { backupCategory ->
|
|
|
|
+ dbCategories.firstOrNull { dbCategory ->
|
|
|
|
+ dbCategory.name == backupCategory.name
|
|
|
|
+ }?.let { dbCategory ->
|
|
|
|
+ mangaCategoriesToUpdate.add(Pair(manga.id, dbCategory.id))
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Update database
|
|
|
|
+ if (mangaCategoriesToUpdate.isNotEmpty()) {
|
|
|
|
+ handler.await(true) {
|
|
|
|
+ mangas_categoriesQueries.deleteMangaCategoryByMangaId(manga.id)
|
|
|
|
+ mangaCategoriesToUpdate.forEach { (mangaId, categoryId) ->
|
|
|
|
+ mangas_categoriesQueries.insert(mangaId, categoryId)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Restore history from Json
|
|
|
|
+ *
|
|
|
|
+ * @param history list containing history to be restored
|
|
|
|
+ */
|
|
|
|
+ private suspend fun restoreHistory(history: List<BackupHistory>) {
|
|
|
|
+ // List containing history to be updated
|
|
|
|
+ val toUpdate = mutableListOf<HistoryUpdate>()
|
|
|
|
+ for ((url, lastRead, readDuration) in history) {
|
|
|
|
+ var dbHistory = handler.awaitOneOrNull { historyQueries.getHistoryByChapterUrl(url) }
|
|
|
|
+ // Check if history already in database and update
|
|
|
|
+ if (dbHistory != null) {
|
|
|
|
+ dbHistory = dbHistory.copy(
|
|
|
|
+ last_read = Date(max(lastRead, dbHistory.last_read?.time ?: 0L)),
|
|
|
|
+ time_read = max(readDuration, dbHistory.time_read) - dbHistory.time_read,
|
|
|
|
+ )
|
|
|
|
+ toUpdate.add(
|
|
|
|
+ HistoryUpdate(
|
|
|
|
+ chapterId = dbHistory.chapter_id,
|
|
|
|
+ readAt = dbHistory.last_read!!,
|
|
|
|
+ sessionReadDuration = dbHistory.time_read,
|
|
|
|
+ ),
|
|
|
|
+ )
|
|
|
|
+ } else {
|
|
|
|
+ // If not in database create
|
|
|
|
+ handler
|
|
|
|
+ .awaitOneOrNull { chaptersQueries.getChapterByUrl(url) }
|
|
|
|
+ ?.let {
|
|
|
|
+ toUpdate.add(
|
|
|
|
+ HistoryUpdate(
|
|
|
|
+ chapterId = it._id,
|
|
|
|
+ readAt = Date(lastRead),
|
|
|
|
+ sessionReadDuration = readDuration,
|
|
|
|
+ ),
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ handler.await(true) {
|
|
|
|
+ toUpdate.forEach { payload ->
|
|
|
|
+ historyQueries.upsert(
|
|
|
|
+ payload.chapterId,
|
|
|
|
+ payload.readAt,
|
|
|
|
+ payload.sessionReadDuration,
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Restores the sync of a manga.
|
|
|
|
+ *
|
|
|
|
+ * @param manga the manga whose sync have to be restored.
|
|
|
|
+ * @param tracks the track list to restore.
|
|
|
|
+ */
|
|
|
|
+ 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,
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (toInsert.isNotEmpty()) {
|
|
|
|
+ handler.await(true) {
|
|
|
|
+ toInsert.forEach { track ->
|
|
|
|
+ manga_syncQueries.insert(
|
|
|
|
+ track.mangaId,
|
|
|
|
+ track.syncId,
|
|
|
|
+ track.remoteId,
|
|
|
|
+ track.libraryId,
|
|
|
|
+ track.title,
|
|
|
|
+ track.lastChapterRead,
|
|
|
|
+ track.totalChapters,
|
|
|
|
+ track.status,
|
|
|
|
+ track.score,
|
|
|
|
+ track.remoteUrl,
|
|
|
|
+ track.startDate,
|
|
|
|
+ track.finishDate,
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
private fun restoreAppPreferences(preferences: List<BackupPreference>) {
|
|
private fun restoreAppPreferences(preferences: List<BackupPreference>) {
|
|
restorePreferences(preferences, preferenceStore)
|
|
restorePreferences(preferences, preferenceStore)
|
|
|
|
+
|
|
|
|
+ restoreProgress += 1
|
|
|
|
+ showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.app_settings), context.getString(R.string.restoring_backup))
|
|
}
|
|
}
|
|
|
|
|
|
private fun restoreSourcePreferences(preferences: List<BackupSourcePreferences>) {
|
|
private fun restoreSourcePreferences(preferences: List<BackupSourcePreferences>) {
|
|
@@ -223,6 +598,9 @@ class BackupRestorer(
|
|
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
|
|
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
|
|
restorePreferences(it.prefs, sourcePrefs)
|
|
restorePreferences(it.prefs, sourcePrefs)
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ restoreProgress += 1
|
|
|
|
+ showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.source_settings), context.getString(R.string.restoring_backup))
|
|
}
|
|
}
|
|
|
|
|
|
private fun restorePreferences(
|
|
private fun restorePreferences(
|