mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 16:18:55 +01:00 
			
		
		
		
	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.
This commit is contained in:
		@@ -291,14 +291,15 @@ tasks {
 | 
			
		||||
    // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
 | 
			
		||||
    withType<KotlinCompile> {
 | 
			
		||||
        kotlinOptions.freeCompilerArgs += listOf(
 | 
			
		||||
            "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
 | 
			
		||||
            "-opt-in=coil.annotation.ExperimentalCoilApi",
 | 
			
		||||
            "-opt-in=com.google.accompanist.pager.ExperimentalPagerApi",
 | 
			
		||||
            "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
 | 
			
		||||
            "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
 | 
			
		||||
            "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
 | 
			
		||||
            "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
 | 
			
		||||
            "-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi",
 | 
			
		||||
            "-opt-in=com.google.accompanist.pager.ExperimentalPagerApi",
 | 
			
		||||
            "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
 | 
			
		||||
            "-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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>)
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,6 @@ import androidx.work.WorkManager
 | 
			
		||||
import androidx.work.WorkerParameters
 | 
			
		||||
import androidx.work.workDataOf
 | 
			
		||||
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.preference.PreferencesHelper
 | 
			
		||||
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())
 | 
			
		||||
        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()))
 | 
			
		||||
            Result.success()
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.full
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import okio.buffer
 | 
			
		||||
@@ -13,10 +12,10 @@ import okio.source
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
 | 
			
		||||
 | 
			
		||||
    private val sourceManager: SourceManager = Injekt.get()
 | 
			
		||||
    private val trackManager: TrackManager = Injekt.get()
 | 
			
		||||
class BackupFileValidator(
 | 
			
		||||
    private val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
    private val trackManager: TrackManager = Injekt.get(),
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks for critical backup file data.
 | 
			
		||||
@@ -24,8 +23,8 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
 | 
			
		||||
     * @throws Exception if manga cannot be found.
 | 
			
		||||
     * @return List of missing sources or missing trackers.
 | 
			
		||||
     */
 | 
			
		||||
    override fun validate(context: Context, uri: Uri): Results {
 | 
			
		||||
        val backupManager = FullBackupManager(context)
 | 
			
		||||
    fun validate(context: Context, uri: Uri): Results {
 | 
			
		||||
        val backupManager = BackupManager(context)
 | 
			
		||||
 | 
			
		||||
        val backup = try {
 | 
			
		||||
            val backupString =
 | 
			
		||||
@@ -63,4 +62,6 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
 | 
			
		||||
 | 
			
		||||
        return Results(missingSources, missingTrackers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.full
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
@@ -9,7 +9,6 @@ import eu.kanade.data.category.categoryMapper
 | 
			
		||||
import eu.kanade.domain.category.model.Category
 | 
			
		||||
import eu.kanade.domain.history.model.HistoryUpdate
 | 
			
		||||
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_MASK
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER
 | 
			
		||||
@@ -18,16 +17,15 @@ import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY_MASK
 | 
			
		||||
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.full.models.Backup
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.backupCategoryMapper
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.backupChapterMapper
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.backupTrackMapper
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.Backup
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupSource
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Chapter
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
@@ -42,7 +40,7 @@ import java.util.Date
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
import eu.kanade.domain.manga.model.Manga as DomainManga
 | 
			
		||||
 | 
			
		||||
class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
class BackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
 | 
			
		||||
    val parser = ProtoBuf
 | 
			
		||||
 | 
			
		||||
@@ -52,6 +50,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     * @param uri path of Uri
 | 
			
		||||
     * @param isAutoBackup backup called from scheduled backup job
 | 
			
		||||
     */
 | 
			
		||||
    @Suppress("BlockingMethodInNonBlockingContext")
 | 
			
		||||
    override suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
 | 
			
		||||
        // Create root object
 | 
			
		||||
        var backup: Backup? = null
 | 
			
		||||
@@ -59,7 +58,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        val databaseManga = getFavoriteManga()
 | 
			
		||||
 | 
			
		||||
        backup = Backup(
 | 
			
		||||
            backupManga(databaseManga, flags),
 | 
			
		||||
            backupMangas(databaseManga, flags),
 | 
			
		||||
            backupCategories(flags),
 | 
			
		||||
            emptyList(),
 | 
			
		||||
            backupExtensionInfo(databaseManga),
 | 
			
		||||
@@ -83,7 +82,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
                        .forEach { it.delete() }
 | 
			
		||||
 | 
			
		||||
                    // Create new file to place backup
 | 
			
		||||
                    dir.createFile(BackupFull.getDefaultFilename())
 | 
			
		||||
                    dir.createFile(Backup.getBackupFilename())
 | 
			
		||||
                } else {
 | 
			
		||||
                    UniFile.fromUri(context, uri)
 | 
			
		||||
                }
 | 
			
		||||
@@ -106,7 +105,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
            val fileUri = file.uri
 | 
			
		||||
 | 
			
		||||
            // Make sure it's a valid backup file
 | 
			
		||||
            FullBackupRestoreValidator().validate(context, fileUri)
 | 
			
		||||
            BackupFileValidator().validate(context, fileUri)
 | 
			
		||||
 | 
			
		||||
            return fileUri.toString()
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
@@ -116,12 +115,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> {
 | 
			
		||||
        return mangas
 | 
			
		||||
            .asSequence()
 | 
			
		||||
@@ -148,6 +141,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun backupMangas(mangas: List<DomainManga>, flags: Int): List<BackupManga> {
 | 
			
		||||
        return mangas.map {
 | 
			
		||||
            backupManga(it, flags)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert a manga to Json
 | 
			
		||||
     *
 | 
			
		||||
@@ -155,7 +154,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     * @param options options for the backup
 | 
			
		||||
     * @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
 | 
			
		||||
        val mangaObject = BackupManga.copyFrom(manga)
 | 
			
		||||
 | 
			
		||||
@@ -202,7 +201,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        return mangaObject
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun restoreMangaNoFetch(manga: Manga, dbManga: Mangas) {
 | 
			
		||||
    suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas) {
 | 
			
		||||
        manga.id = dbManga._id
 | 
			
		||||
        manga.copyFrom(dbManga)
 | 
			
		||||
        updateManga(manga)
 | 
			
		||||
@@ -214,7 +213,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     * @param manga manga that needs updating
 | 
			
		||||
     * @return Updated manga info.
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun restoreManga(manga: Manga): Manga {
 | 
			
		||||
    suspend fun restoreNewManga(manga: Manga): Manga {
 | 
			
		||||
        return manga.also {
 | 
			
		||||
            it.initialized = it.description != null
 | 
			
		||||
            it.id = insertManga(it)
 | 
			
		||||
@@ -267,7 +266,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     * @param manga the manga whose categories have to be restored.
 | 
			
		||||
     * @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 mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>()
 | 
			
		||||
 | 
			
		||||
@@ -299,7 +298,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     *
 | 
			
		||||
     * @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
 | 
			
		||||
        val toUpdate = mutableListOf<HistoryUpdate>()
 | 
			
		||||
        for ((url, lastRead, readDuration) in history) {
 | 
			
		||||
@@ -349,7 +348,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     * @param manga the manga whose sync have to be restored.
 | 
			
		||||
     * @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
 | 
			
		||||
        tracks.map { it.manga_id = manga.id!! }
 | 
			
		||||
 | 
			
		||||
@@ -427,7 +426,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!!) }
 | 
			
		||||
 | 
			
		||||
        chapters.forEach { chapter ->
 | 
			
		||||
@@ -8,7 +8,6 @@ import android.os.IBinder
 | 
			
		||||
import android.os.PowerManager
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestore
 | 
			
		||||
import eu.kanade.tachiyomi.data.notification.Notifications
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
 | 
			
		||||
@@ -70,7 +69,7 @@ class BackupRestoreService : Service() {
 | 
			
		||||
    private lateinit var wakeLock: PowerManager.WakeLock
 | 
			
		||||
 | 
			
		||||
    private lateinit var ioScope: CoroutineScope
 | 
			
		||||
    private var backupRestore: AbstractBackupRestore<*>? = null
 | 
			
		||||
    private var restorer: AbstractBackupRestore<*>? = null
 | 
			
		||||
    private lateinit var notifier: BackupNotifier
 | 
			
		||||
 | 
			
		||||
    override fun onCreate() {
 | 
			
		||||
@@ -94,7 +93,7 @@ class BackupRestoreService : Service() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun destroyJob() {
 | 
			
		||||
        backupRestore?.job?.cancel()
 | 
			
		||||
        restorer?.job?.cancel()
 | 
			
		||||
        ioScope.cancel()
 | 
			
		||||
        if (wakeLock.isHeld) {
 | 
			
		||||
            wakeLock.release()
 | 
			
		||||
@@ -118,26 +117,26 @@ class BackupRestoreService : Service() {
 | 
			
		||||
        val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
 | 
			
		||||
 | 
			
		||||
        // Cancel any previous job if needed.
 | 
			
		||||
        backupRestore?.job?.cancel()
 | 
			
		||||
        restorer?.job?.cancel()
 | 
			
		||||
 | 
			
		||||
        backupRestore = FullBackupRestore(this, notifier)
 | 
			
		||||
        restorer = BackupRestorer(this, notifier)
 | 
			
		||||
 | 
			
		||||
        val handler = CoroutineExceptionHandler { _, exception ->
 | 
			
		||||
            logcat(LogPriority.ERROR, exception)
 | 
			
		||||
            backupRestore?.writeErrorLog()
 | 
			
		||||
            restorer?.writeErrorLog()
 | 
			
		||||
 | 
			
		||||
            notifier.showRestoreError(exception.message)
 | 
			
		||||
            stopSelf(startId)
 | 
			
		||||
        }
 | 
			
		||||
        val job = ioScope.launch(handler) {
 | 
			
		||||
            if (backupRestore?.restoreBackup(uri) == false) {
 | 
			
		||||
            if (restorer?.restoreBackup(uri) == false) {
 | 
			
		||||
                notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        job.invokeOnCompletion {
 | 
			
		||||
            stopSelf(startId)
 | 
			
		||||
        }
 | 
			
		||||
        backupRestore?.job = job
 | 
			
		||||
        restorer?.job = job
 | 
			
		||||
 | 
			
		||||
        return START_NOT_STICKY
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,13 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.full
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.BackupSource
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Chapter
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
@@ -18,12 +16,12 @@ import okio.gzip
 | 
			
		||||
import okio.source
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<FullBackupManager>(context, notifier) {
 | 
			
		||||
class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<BackupManager>(context, notifier) {
 | 
			
		||||
 | 
			
		||||
    @Suppress("BlockingMethodInNonBlockingContext")
 | 
			
		||||
    override suspend fun performRestore(uri: Uri): Boolean {
 | 
			
		||||
        backupManager = FullBackupManager(context)
 | 
			
		||||
        backupManager = BackupManager(context)
 | 
			
		||||
 | 
			
		||||
        @Suppress("BlockingMethodInNonBlockingContext")
 | 
			
		||||
        val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
 | 
			
		||||
        val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +66,17 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
 | 
			
		||||
        val tracks = backupManga.getTrackingImpl()
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            restoreMangaData(manga, chapters, categories, history, tracks, backupCategories)
 | 
			
		||||
            val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
 | 
			
		||||
            if (dbManga == null) {
 | 
			
		||||
                // Manga not in database
 | 
			
		||||
                restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories)
 | 
			
		||||
            } 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)
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
 | 
			
		||||
            errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
 | 
			
		||||
@@ -78,36 +86,6 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
 | 
			
		||||
        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>,
 | 
			
		||||
    ) {
 | 
			
		||||
        val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
 | 
			
		||||
        if (dbManga == null) {
 | 
			
		||||
            // Manga not in database
 | 
			
		||||
            restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories)
 | 
			
		||||
        } 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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetches manga information
 | 
			
		||||
     *
 | 
			
		||||
@@ -115,7 +93,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
 | 
			
		||||
     * @param chapters chapters of manga that needs updating
 | 
			
		||||
     * @param categories categories that need updating
 | 
			
		||||
     */
 | 
			
		||||
    private suspend fun restoreMangaFetch(
 | 
			
		||||
    private suspend fun restoreExistingManga(
 | 
			
		||||
        manga: Manga,
 | 
			
		||||
        chapters: List<Chapter>,
 | 
			
		||||
        categories: List<Int>,
 | 
			
		||||
@@ -123,19 +101,14 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
 | 
			
		||||
        tracks: List<Track>,
 | 
			
		||||
        backupCategories: List<BackupCategory>,
 | 
			
		||||
    ) {
 | 
			
		||||
        try {
 | 
			
		||||
            val fetchedManga = backupManager.restoreManga(manga)
 | 
			
		||||
            fetchedManga.id ?: return
 | 
			
		||||
        val fetchedManga = backupManager.restoreNewManga(manga)
 | 
			
		||||
        fetchedManga.id ?: return
 | 
			
		||||
 | 
			
		||||
            backupManager.restoreChaptersForManga(fetchedManga, chapters)
 | 
			
		||||
 | 
			
		||||
            restoreExtraForManga(fetchedManga, categories, history, tracks, backupCategories)
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            errors.add(Date() to "${manga.title} - ${e.message}")
 | 
			
		||||
        }
 | 
			
		||||
        backupManager.restoreChapters(fetchedManga, chapters)
 | 
			
		||||
        restoreExtras(fetchedManga, categories, history, tracks, backupCategories)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun restoreMangaNoFetch(
 | 
			
		||||
    private suspend fun restoreNewManga(
 | 
			
		||||
        backupManga: Manga,
 | 
			
		||||
        chapters: List<Chapter>,
 | 
			
		||||
        categories: List<Int>,
 | 
			
		||||
@@ -143,19 +116,13 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
 | 
			
		||||
        tracks: List<Track>,
 | 
			
		||||
        backupCategories: List<BackupCategory>,
 | 
			
		||||
    ) {
 | 
			
		||||
        backupManager.restoreChaptersForManga(backupManga, chapters)
 | 
			
		||||
 | 
			
		||||
        restoreExtraForManga(backupManga, categories, history, tracks, backupCategories)
 | 
			
		||||
        backupManager.restoreChapters(backupManga, chapters)
 | 
			
		||||
        restoreExtras(backupManga, categories, history, tracks, backupCategories)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
 | 
			
		||||
        // Restore categories
 | 
			
		||||
        backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
 | 
			
		||||
 | 
			
		||||
        // Restore history
 | 
			
		||||
        backupManager.restoreHistoryForManga(history)
 | 
			
		||||
 | 
			
		||||
        // Restore tracking
 | 
			
		||||
        backupManager.restoreTrackForManga(manga, tracks)
 | 
			
		||||
    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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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.protobuf.ProtoNumber
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Date
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Backup(
 | 
			
		||||
@@ -10,4 +13,12 @@ data class Backup(
 | 
			
		||||
    // Bump by 100 to specify this is a 0.x value
 | 
			
		||||
    @ProtoNumber(100) var backupBrokenSources: List<BrokenBackupSource> = emptyList(),
 | 
			
		||||
    @ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
 | 
			
		||||
)
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun getBackupFilename(): String {
 | 
			
		||||
            val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
 | 
			
		||||
            return "tachiyomi_$date.proto.gz"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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 kotlinx.serialization.Serializable
 | 
			
		||||
@@ -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 kotlinx.serialization.Serializable
 | 
			
		||||
@@ -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.protobuf.ProtoNumber
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class BrokenBackupHistory(
 | 
			
		||||
    @ProtoNumber(0) var url: String,
 | 
			
		||||
    @ProtoNumber(1) var lastRead: Long,
 | 
			
		||||
    @ProtoNumber(2) var readDuration: Long = 0,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class BackupHistory(
 | 
			
		||||
    @ProtoNumber(1) var url: String,
 | 
			
		||||
    @ProtoNumber(2) var lastRead: Long,
 | 
			
		||||
    @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,
 | 
			
		||||
)
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.full.models
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.models
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.manga.model.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
 | 
			
		||||
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.protobuf.ProtoNumber
 | 
			
		||||
 | 
			
		||||
@Suppress("DEPRECATION")
 | 
			
		||||
@Serializable
 | 
			
		||||
data class BackupManga(
 | 
			
		||||
    // in 1.x some of these values have different names
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.full.models
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.models
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializer
 | 
			
		||||
 | 
			
		||||
@@ -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 kotlinx.serialization.Serializable
 | 
			
		||||
@@ -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 kotlinx.serialization.Serializable
 | 
			
		||||
@@ -21,9 +21,9 @@ import com.hippo.unifile.UniFile
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.BackupConst
 | 
			
		||||
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.full.FullBackupRestoreValidator
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.models.Backup
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.bindTo
 | 
			
		||||
@@ -206,11 +206,10 @@ class SettingsBackupController : SettingsController() {
 | 
			
		||||
    fun createBackup(flags: Int) {
 | 
			
		||||
        backupFlags = flags
 | 
			
		||||
        try {
 | 
			
		||||
            // Use Android's built-in file creator
 | 
			
		||||
            val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
 | 
			
		||||
                .addCategory(Intent.CATEGORY_OPENABLE)
 | 
			
		||||
                .setType("application/*")
 | 
			
		||||
                .putExtra(Intent.EXTRA_TITLE, BackupFull.getDefaultFilename())
 | 
			
		||||
                .putExtra(Intent.EXTRA_TITLE, Backup.getBackupFilename())
 | 
			
		||||
 | 
			
		||||
            startActivityForResult(intent, CODE_BACKUP_CREATE)
 | 
			
		||||
        } catch (e: ActivityNotFoundException) {
 | 
			
		||||
@@ -270,7 +269,7 @@ class SettingsBackupController : SettingsController() {
 | 
			
		||||
            val uri: Uri = args.getParcelable(KEY_URI)!!
 | 
			
		||||
 | 
			
		||||
            return try {
 | 
			
		||||
                val results = FullBackupRestoreValidator().validate(activity, uri)
 | 
			
		||||
                val results = BackupFileValidator().validate(activity, uri)
 | 
			
		||||
 | 
			
		||||
                var message = activity.getString(R.string.backup_restore_content_full)
 | 
			
		||||
                if (results.missingSources.isNotEmpty()) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user