Start cleaning up backup/restore code

The abstraction was useful for handling 2 systems, but it's no longer needed. Cleaning it up will make migrating to domain models easier down the line.

(cherry picked from commit a2bb81b7db457c3927eaa1f03416a9b07985842b)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupFlatMetadata.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupMergedMangaReference.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSavedSearch.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/metadata/BackupSearchMetadata.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/metadata/BackupSearchTag.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/metadata/BackupSearchTitle.kt
This commit is contained in:
arkon 2022-08-05 23:11:06 -04:00 committed by Jobobby04
parent b0fc4dd6d4
commit 6f6a9b677c
23 changed files with 131 additions and 185 deletions

View File

@ -297,14 +297,15 @@ tasks {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> { withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf( kotlinOptions.freeCompilerArgs += listOf(
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=coil.annotation.ExperimentalCoilApi", "-opt-in=coil.annotation.ExperimentalCoilApi",
"-opt-in=com.google.accompanist.pager.ExperimentalPagerApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi", "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
"-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi", "-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi",
"-opt-in=com.google.accompanist.pager.ExperimentalPagerApi", "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
) )
} }

View File

@ -1,10 +0,0 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
abstract class AbstractBackupRestoreValidator {
abstract fun validate(context: Context, uri: Uri): Results
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
}

View File

@ -13,7 +13,6 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
@ -36,7 +35,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
context.notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build()) context.notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
return try { return try {
val location = FullBackupManager(context).createBackup(uri, flags, isAutoBackup) val location = BackupManager(context).createBackup(uri, flags, isAutoBackup)
if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri())) if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
Result.success() Result.success()
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,10 +1,9 @@
package eu.kanade.tachiyomi.data.backup.full 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.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import okio.buffer import okio.buffer
@ -13,10 +12,10 @@ import okio.source
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() { class BackupFileValidator(
private val sourceManager: SourceManager = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get() private val trackManager: TrackManager = Injekt.get(),
private val trackManager: TrackManager = Injekt.get() ) {
/** /**
* Checks for critical backup file data. * Checks for critical backup file data.
@ -24,8 +23,8 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
* @throws Exception if manga cannot be found. * @throws Exception if manga cannot be found.
* @return List of missing sources or missing trackers. * @return List of missing sources or missing trackers.
*/ */
override fun validate(context: Context, uri: Uri): Results { fun validate(context: Context, uri: Uri): Results {
val backupManager = FullBackupManager(context) val backupManager = BackupManager(context)
val backup = try { val backup = try {
val backupString = val backupString =
@ -63,4 +62,6 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
return Results(missingSources, missingTrackers) return Results(missingSources, missingTrackers)
} }
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
} }

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full package eu.kanade.tachiyomi.data.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
@ -11,7 +11,6 @@ import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.history.model.HistoryUpdate import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER
@ -24,21 +23,20 @@ import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA_MASK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.full.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupFlatMetadata import eu.kanade.tachiyomi.data.backup.models.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupMergedMangaReference
import eu.kanade.tachiyomi.data.backup.full.models.BackupMergedMangaReference import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupCategoryMapper import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupChapterMapper import eu.kanade.tachiyomi.data.backup.models.backupMergedMangaReferenceMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupMergedMangaReferenceMapper import eu.kanade.tachiyomi.data.backup.models.backupSavedSearchMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupSavedSearchMapper import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupTrackMapper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@ -57,7 +55,7 @@ import java.util.Date
import kotlin.math.max import kotlin.math.max
import eu.kanade.domain.manga.model.Manga as DomainManga import eu.kanade.domain.manga.model.Manga as DomainManga
class FullBackupManager(context: Context) : AbstractBackupManager(context) { class BackupManager(context: Context) : AbstractBackupManager(context) {
val parser = ProtoBuf val parser = ProtoBuf
@ -67,6 +65,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param uri path of Uri * @param uri path of Uri
* @param isAutoBackup backup called from scheduled backup job * @param isAutoBackup backup called from scheduled backup job
*/ */
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { override suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
// Create root object // Create root object
var backup: Backup? = null var backup: Backup? = null
@ -78,7 +77,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
} + getMergedManga() // SY <-- } + getMergedManga() // SY <--
backup = Backup( backup = Backup(
backupManga(databaseManga, flags), backupMangas(databaseManga, flags),
backupCategories(flags), backupCategories(flags),
emptyList(), emptyList(),
backupExtensionInfo(databaseManga), backupExtensionInfo(databaseManga),
@ -105,7 +104,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
.forEach { it.delete() } .forEach { it.delete() }
// Create new file to place backup // Create new file to place backup
dir.createFile(BackupFull.getDefaultFilename()) dir.createFile(Backup.getBackupFilename())
} else { } else {
UniFile.fromUri(context, uri) UniFile.fromUri(context, uri)
} }
@ -128,7 +127,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val fileUri = file.uri val fileUri = file.uri
// Make sure it's a valid backup file // Make sure it's a valid backup file
FullBackupRestoreValidator().validate(context, fileUri) BackupFileValidator().validate(context, fileUri)
return fileUri.toString() return fileUri.toString()
} catch (e: Exception) { } catch (e: Exception) {
@ -138,12 +137,6 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
} }
} }
private suspend fun backupManga(mangas: List<DomainManga>, flags: Int): List<BackupManga> {
return mangas.map {
backupMangaObject(it, flags)
}
}
private fun backupExtensionInfo(mangas: List<DomainManga>): List<BackupSource> { private fun backupExtensionInfo(mangas: List<DomainManga>): List<BackupSource> {
return mangas return mangas
.asSequence() .asSequence()
@ -170,6 +163,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
} }
} }
private suspend fun backupMangas(mangas: List<DomainManga>, flags: Int): List<BackupManga> {
return mangas.map {
backupManga(it, flags)
}
}
// SY --> // SY -->
/** /**
* Backup the saved searches from sources * Backup the saved searches from sources
@ -188,7 +187,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param options options for the backup * @param options options for the backup
* @return [BackupManga] containing manga in a serializable form * @return [BackupManga] containing manga in a serializable form
*/ */
private suspend fun backupMangaObject(manga: DomainManga, options: Int): BackupManga { private suspend fun backupManga(manga: DomainManga, options: Int): BackupManga {
// Entry for this manga // Entry for this manga
val mangaObject = BackupManga.copyFrom(manga /* SY --> */, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null /* SY <-- */) val mangaObject = BackupManga.copyFrom(manga /* SY --> */, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null /* SY <-- */)
@ -248,7 +247,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
return mangaObject return mangaObject
} }
suspend fun restoreMangaNoFetch(manga: Manga, dbManga: Mangas) { suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas) {
manga.id = dbManga._id manga.id = dbManga._id
manga.copyFrom(dbManga) manga.copyFrom(dbManga)
updateManga(manga) updateManga(manga)
@ -260,7 +259,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param manga manga that needs updating * @param manga manga that needs updating
* @return Updated manga info. * @return Updated manga info.
*/ */
suspend fun restoreManga(manga: Manga): Manga { suspend fun restoreNewManga(manga: Manga): Manga {
return manga.also { return manga.also {
it.initialized = it.description != null it.initialized = it.description != null
it.id = insertManga(it) it.id = insertManga(it)
@ -313,7 +312,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param manga the manga whose categories have to be restored. * @param manga the manga whose categories have to be restored.
* @param categories the categories to restore. * @param categories the categories to restore.
*/ */
internal suspend fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) { internal suspend fun restoreCategories(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = handler.awaitList { categoriesQueries.getCategories() } val dbCategories = handler.awaitList { categoriesQueries.getCategories() }
val mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>() val mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>()
@ -345,7 +344,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* *
* @param history list containing history to be restored * @param history list containing history to be restored
*/ */
internal suspend fun restoreHistoryForManga(history: List<BackupHistory>) { internal suspend fun restoreHistory(history: List<BackupHistory>) {
// List containing history to be updated // 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 history) {
@ -395,7 +394,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param manga the manga whose sync have to be restored. * @param manga the manga whose sync have to be restored.
* @param tracks the track list to restore. * @param tracks the track list to restore.
*/ */
internal suspend fun restoreTrackForManga(manga: Manga, tracks: List<Track>) { internal suspend fun restoreTracking(manga: Manga, tracks: List<Track>) {
// Fix foreign keys with the current manga id // Fix foreign keys with the current manga id
tracks.map { it.manga_id = manga.id!! } tracks.map { it.manga_id = manga.id!! }
@ -473,7 +472,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
} }
} }
internal suspend fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) { internal suspend fun restoreChapters(manga: Manga, chapters: List<Chapter>) {
val dbChapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) } val dbChapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) }
chapters.forEach { chapter -> chapters.forEach { chapter ->

View File

@ -8,7 +8,6 @@ import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestore
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
@ -70,7 +69,7 @@ class BackupRestoreService : Service() {
private lateinit var wakeLock: PowerManager.WakeLock private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var ioScope: CoroutineScope private lateinit var ioScope: CoroutineScope
private var backupRestore: AbstractBackupRestore<*>? = null private var restorer: AbstractBackupRestore<*>? = null
private lateinit var notifier: BackupNotifier private lateinit var notifier: BackupNotifier
override fun onCreate() { override fun onCreate() {
@ -94,7 +93,7 @@ class BackupRestoreService : Service() {
} }
private fun destroyJob() { private fun destroyJob() {
backupRestore?.job?.cancel() restorer?.job?.cancel()
ioScope.cancel() ioScope.cancel()
if (wakeLock.isHeld) { if (wakeLock.isHeld) {
wakeLock.release() wakeLock.release()
@ -118,26 +117,26 @@ class BackupRestoreService : Service() {
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
// Cancel any previous job if needed. // Cancel any previous job if needed.
backupRestore?.job?.cancel() restorer?.job?.cancel()
backupRestore = FullBackupRestore(this, notifier) restorer = BackupRestorer(this, notifier)
val handler = CoroutineExceptionHandler { _, exception -> val handler = CoroutineExceptionHandler { _, exception ->
logcat(LogPriority.ERROR, exception) logcat(LogPriority.ERROR, exception)
backupRestore?.writeErrorLog() restorer?.writeErrorLog()
notifier.showRestoreError(exception.message) notifier.showRestoreError(exception.message)
stopSelf(startId) stopSelf(startId)
} }
val job = ioScope.launch(handler) { val job = ioScope.launch(handler) {
if (backupRestore?.restoreBackup(uri) == false) { if (restorer?.restoreBackup(uri) == false) {
notifier.showRestoreError(getString(R.string.restoring_backup_canceled)) notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
} }
} }
job.invokeOnCompletion { job.invokeOnCompletion {
stopSelf(startId) stopSelf(startId)
} }
backupRestore?.job = job restorer?.job = job
return START_NOT_STICKY return START_NOT_STICKY
} }

View File

@ -1,18 +1,16 @@
package eu.kanade.tachiyomi.data.backup.full 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.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.BackupNotifier import eu.kanade.tachiyomi.data.backup.models.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupFlatMetadata import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupMergedMangaReference
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.full.models.BackupMergedMangaReference import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@ -24,12 +22,12 @@ import okio.gzip
import okio.source import okio.source
import java.util.Date import java.util.Date
class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<FullBackupManager>(context, notifier) { class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<BackupManager>(context, notifier) {
override suspend fun performRestore(uri: Uri): Boolean {
backupManager = FullBackupManager(context)
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
override suspend fun performRestore(uri: Uri): Boolean {
backupManager = BackupManager(context)
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() } val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString) val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
@ -98,7 +96,17 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
// SY <-- // SY <--
try { try {
restoreMangaData(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */) val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
if (dbManga == null) {
// Manga not in database
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} else {
// Manga in database
// Copy information from manga already in database
backupManager.restoreExistingManga(manga, dbManga)
// Fetch rest of manga information
restoreNewManga(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
}
} catch (e: Exception) { } catch (e: Exception) {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString() val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}") errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
@ -108,41 +116,6 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
showRestoreProgress(restoreProgress, restoreAmount, manga.title) showRestoreProgress(restoreProgress, restoreAmount, manga.title)
} }
/**
* Returns a manga restore observable
*
* @param manga manga data from json
* @param chapters chapters data from json
* @param categories categories data from json
* @param history history data from json
* @param tracks tracking data from json
*/
private suspend fun restoreMangaData(
manga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
customManga: CustomMangaManager.MangaJson?,
// SY -->
) {
val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
if (dbManga == null) {
// Manga not in database
restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} else {
// Manga in database
// Copy information from manga already in database
backupManager.restoreMangaNoFetch(manga, dbManga)
// Fetch rest of manga information
restoreMangaNoFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
}
}
/** /**
* Fetches manga information * Fetches manga information
* *
@ -150,7 +123,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
* @param chapters chapters of manga that needs updating * @param chapters chapters of manga that needs updating
* @param categories categories that need updating * @param categories categories that need updating
*/ */
private suspend fun restoreMangaFetch( private suspend fun restoreExistingManga(
manga: Manga, manga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
@ -163,18 +136,14 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
customManga: CustomMangaManager.MangaJson?, customManga: CustomMangaManager.MangaJson?,
// SY <-- // SY <--
) { ) {
try { val fetchedManga = backupManager.restoreNewManga(manga)
val fetchedManga = backupManager.restoreManga(manga)
fetchedManga.id ?: return fetchedManga.id ?: return
backupManager.restoreChaptersForManga(fetchedManga, chapters)
restoreExtraForManga(fetchedManga, categories, history, tracks, backupCategories /* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */) backupManager.restoreChapters(fetchedManga, chapters)
} catch (e: Exception) { restoreExtras(fetchedManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
errors.add(Date() to "${manga.title} - ${e.message}")
}
} }
private suspend fun restoreMangaNoFetch( private suspend fun restoreNewManga(
backupManga: Manga, backupManga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
@ -187,12 +156,11 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
customManga: CustomMangaManager.MangaJson?, customManga: CustomMangaManager.MangaJson?,
// SY <-- // SY <--
) { ) {
backupManager.restoreChaptersForManga(backupManga, chapters) backupManager.restoreChapters(backupManga, chapters)
restoreExtras(backupManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} }
private suspend fun restoreExtraForManga( private suspend fun restoreExtras(
manga: Manga, manga: Manga,
categories: List<Int>, categories: List<Int>,
history: List<BackupHistory>, history: List<BackupHistory>,
@ -204,23 +172,12 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
customManga: CustomMangaManager.MangaJson?, customManga: CustomMangaManager.MangaJson?,
// SY <-- // SY <--
) { ) {
// Restore categories backupManager.restoreCategories(manga, categories, backupCategories)
backupManager.restoreCategoriesForManga(manga, categories, backupCategories) backupManager.restoreHistory(history)
backupManager.restoreTracking(manga, tracks)
// Restore history
backupManager.restoreHistoryForManga(history)
// Restore tracking
backupManager.restoreTrackForManga(manga, tracks)
// SY --> // SY -->
// Restore merged manga references if its a merged manga
backupManager.restoreMergedMangaReferencesForManga(manga.id!!, mergedMangaReferences) backupManager.restoreMergedMangaReferencesForManga(manga.id!!, mergedMangaReferences)
// Restore flat metadata for metadata sources
flatMetadata?.let { backupManager.restoreFlatMetadata(manga.id!!, it) } flatMetadata?.let { backupManager.restoreFlatMetadata(manga.id!!, it) }
// Restore Custom Info
customManga?.id = manga.id!! customManga?.id = manga.id!!
customManga?.let { customMangaManager.saveMangaInfo(it) } customManga?.let { customMangaManager.saveMangaInfo(it) }
// SY <-- // SY <--

View File

@ -1,12 +0,0 @@
package eu.kanade.tachiyomi.data.backup.full.models
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object BackupFull {
fun getDefaultFilename(): String {
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
return "tachiyomi_$date.proto.gz"
}
}

View File

@ -1,7 +1,10 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@Serializable @Serializable
data class Backup( data class Backup(
@ -12,4 +15,12 @@ data class Backup(
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(), @ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
// SY specific values // SY specific values
@ProtoNumber(600) var backupSavedSearches: List<BackupSavedSearch> = emptyList(), @ProtoNumber(600) var backupSavedSearches: List<BackupSavedSearch> = emptyList(),
) ) {
companion object {
fun getBackupFilename(): String {
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
return "tachiyomi_$date.proto.gz"
}
}
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.ChapterImpl import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.backup.full.models.metadata.BackupSearchMetadata import eu.kanade.tachiyomi.data.backup.models.metadata.BackupSearchMetadata
import eu.kanade.tachiyomi.data.backup.full.models.metadata.BackupSearchTag import eu.kanade.tachiyomi.data.backup.models.metadata.BackupSearchTag
import eu.kanade.tachiyomi.data.backup.full.models.metadata.BackupSearchTitle import eu.kanade.tachiyomi.data.backup.models.metadata.BackupSearchTitle
import exh.metadata.metadata.base.FlatMetadata import exh.metadata.metadata.base.FlatMetadata
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,18 +1,19 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BrokenBackupHistory(
@ProtoNumber(0) var url: String,
@ProtoNumber(1) var lastRead: Long,
@ProtoNumber(2) var readDuration: Long = 0,
)
@Serializable @Serializable
data class BackupHistory( data class BackupHistory(
@ProtoNumber(1) var url: String, @ProtoNumber(1) var url: String,
@ProtoNumber(2) var lastRead: Long, @ProtoNumber(2) var lastRead: Long,
@ProtoNumber(3) var readDuration: Long = 0, @ProtoNumber(3) var readDuration: Long = 0,
) )
@Deprecated("Replaced with BackupHistory. This is retained for legacy reasons.")
@Serializable
data class BrokenBackupHistory(
@ProtoNumber(0) var url: String,
@ProtoNumber(1) var lastRead: Long,
@ProtoNumber(2) var readDuration: Long = 0,
)

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.data.listOfStringsAndAdapter import eu.kanade.data.listOfStringsAndAdapter
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@Suppress("DEPRECATION")
@Serializable @Serializable
data class BackupManga( data class BackupManga(
// in 1.x some of these values have different names // in 1.x some of these values have different names

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializer import kotlinx.serialization.Serializer

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.TrackImpl import eu.kanade.tachiyomi.data.database.models.TrackImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models.metadata package eu.kanade.tachiyomi.data.backup.models.metadata
import exh.metadata.sql.models.SearchMetadata import exh.metadata.sql.models.SearchMetadata
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models.metadata package eu.kanade.tachiyomi.data.backup.models.metadata
import exh.metadata.sql.models.SearchTag import exh.metadata.sql.models.SearchTag
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models.metadata package eu.kanade.tachiyomi.data.backup.models.metadata
import exh.metadata.sql.models.SearchTitle import exh.metadata.sql.models.SearchTitle
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -21,9 +21,9 @@ import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst import eu.kanade.tachiyomi.data.backup.BackupConst
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.backup.BackupRestoreService
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestoreValidator import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.util.preference.bindTo import eu.kanade.tachiyomi.util.preference.bindTo
@ -206,11 +206,10 @@ class SettingsBackupController : SettingsController() {
fun createBackup(flags: Int) { fun createBackup(flags: Int) {
backupFlags = flags backupFlags = flags
try { try {
// Use Android's built-in file creator
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE) .addCategory(Intent.CATEGORY_OPENABLE)
.setType("application/*") .setType("application/*")
.putExtra(Intent.EXTRA_TITLE, BackupFull.getDefaultFilename()) .putExtra(Intent.EXTRA_TITLE, Backup.getBackupFilename())
startActivityForResult(intent, CODE_BACKUP_CREATE) startActivityForResult(intent, CODE_BACKUP_CREATE)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
@ -278,7 +277,7 @@ class SettingsBackupController : SettingsController() {
val uri: Uri = args.getParcelable(KEY_URI)!! val uri: Uri = args.getParcelable(KEY_URI)!!
return try { return try {
val results = FullBackupRestoreValidator().validate(activity, uri) val results = BackupFileValidator().validate(activity, uri)
var message = activity.getString(R.string.backup_restore_content_full) var message = activity.getString(R.string.backup_restore_content_full)
if (results.missingSources.isNotEmpty()) { if (results.missingSources.isNotEmpty()) {