mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Remove logic for restoring legacy JSON backups
- Protobuf backups have been around for 1.5 years now - The ability to restore online-dependant data from JSON backups gets harder as time goes on and sources drift - If users really need a way to restore them, they can use an older version of the app, or a separate tool for translating between the formats could be created
This commit is contained in:
		@@ -12,14 +12,15 @@ import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.toSChapter
 | 
			
		||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
abstract class AbstractBackupManager(protected val context: Context) {
 | 
			
		||||
 | 
			
		||||
    internal val databaseHelper: DatabaseHelper by injectLazy()
 | 
			
		||||
    internal val sourceManager: SourceManager by injectLazy()
 | 
			
		||||
    internal val trackManager: TrackManager by injectLazy()
 | 
			
		||||
    protected val preferences: PreferencesHelper by injectLazy()
 | 
			
		||||
    internal val db: DatabaseHelper = Injekt.get()
 | 
			
		||||
    internal val sourceManager: SourceManager = Injekt.get()
 | 
			
		||||
    internal val trackManager: TrackManager = Injekt.get()
 | 
			
		||||
    protected val preferences: PreferencesHelper = Injekt.get()
 | 
			
		||||
 | 
			
		||||
    abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +30,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
 | 
			
		||||
     * @return [Manga], null if not found
 | 
			
		||||
     */
 | 
			
		||||
    internal fun getMangaFromDatabase(manga: Manga): Manga? =
 | 
			
		||||
        databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
 | 
			
		||||
        db.getManga(manga.url, manga.source).executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetches chapter information.
 | 
			
		||||
@@ -42,7 +43,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
 | 
			
		||||
    internal suspend fun restoreChapters(source: Source, manga: Manga, chapters: List<Chapter>): Pair<List<Chapter>, List<Chapter>> {
 | 
			
		||||
        val fetchedChapters = source.getChapterList(manga.toMangaInfo())
 | 
			
		||||
            .map { it.toSChapter() }
 | 
			
		||||
        val syncedChapters = syncChaptersWithSource(databaseHelper, fetchedChapters, manga, source)
 | 
			
		||||
        val syncedChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
 | 
			
		||||
        if (syncedChapters.first.isNotEmpty()) {
 | 
			
		||||
            chapters.forEach { it.manga_id = manga.id }
 | 
			
		||||
            updateChapters(chapters)
 | 
			
		||||
@@ -56,7 +57,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
 | 
			
		||||
     * @return [Manga] from library
 | 
			
		||||
     */
 | 
			
		||||
    protected fun getFavoriteManga(): List<Manga> =
 | 
			
		||||
        databaseHelper.getFavoriteMangas().executeAsBlocking()
 | 
			
		||||
        db.getFavoriteMangas().executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Inserts manga and returns id
 | 
			
		||||
@@ -64,27 +65,27 @@ abstract class AbstractBackupManager(protected val context: Context) {
 | 
			
		||||
     * @return id of [Manga], null if not found
 | 
			
		||||
     */
 | 
			
		||||
    internal fun insertManga(manga: Manga): Long? =
 | 
			
		||||
        databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
 | 
			
		||||
        db.insertManga(manga).executeAsBlocking().insertedId()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Inserts list of chapters
 | 
			
		||||
     */
 | 
			
		||||
    protected fun insertChapters(chapters: List<Chapter>) {
 | 
			
		||||
        databaseHelper.insertChapters(chapters).executeAsBlocking()
 | 
			
		||||
        db.insertChapters(chapters).executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a list of chapters
 | 
			
		||||
     */
 | 
			
		||||
    protected fun updateChapters(chapters: List<Chapter>) {
 | 
			
		||||
        databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
 | 
			
		||||
        db.updateChaptersBackup(chapters).executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a list of chapters with known database ids
 | 
			
		||||
     */
 | 
			
		||||
    protected fun updateKnownChapters(chapters: List<Chapter>) {
 | 
			
		||||
        databaseHelper.updateKnownChaptersBackup(chapters).executeAsBlocking()
 | 
			
		||||
        db.updateKnownChaptersBackup(chapters).executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -2,17 +2,9 @@ package eu.kanade.tachiyomi.data.backup
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
abstract class AbstractBackupRestoreValidator {
 | 
			
		||||
    protected val sourceManager: SourceManager by injectLazy()
 | 
			
		||||
    protected val trackManager: TrackManager by injectLazy()
 | 
			
		||||
 | 
			
		||||
    abstract fun validate(context: Context, uri: Uri): Results
 | 
			
		||||
 | 
			
		||||
    data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ValidatorParseException(e: Exception) : RuntimeException(e)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,6 @@ object BackupConst {
 | 
			
		||||
 | 
			
		||||
    private const val NAME = "BackupRestoreServices"
 | 
			
		||||
    const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
 | 
			
		||||
    const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
 | 
			
		||||
    const val EXTRA_MODE = "$ID.$NAME.EXTRA_MODE"
 | 
			
		||||
 | 
			
		||||
    const val BACKUP_TYPE_LEGACY = 0
 | 
			
		||||
    const val BACKUP_TYPE_FULL = 1
 | 
			
		||||
 | 
			
		||||
    // Filter options
 | 
			
		||||
    internal const val BACKUP_CATEGORY = 0x1
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ 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.backup.legacy.LegacyBackupRestore
 | 
			
		||||
import eu.kanade.tachiyomi.data.notification.Notifications
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
 | 
			
		||||
@@ -44,11 +43,10 @@ class BackupRestoreService : Service() {
 | 
			
		||||
         * @param context context of application
 | 
			
		||||
         * @param uri path of Uri
 | 
			
		||||
         */
 | 
			
		||||
        fun start(context: Context, uri: Uri, mode: Int) {
 | 
			
		||||
        fun start(context: Context, uri: Uri) {
 | 
			
		||||
            if (!isRunning(context)) {
 | 
			
		||||
                val intent = Intent(context, BackupRestoreService::class.java).apply {
 | 
			
		||||
                    putExtra(BackupConst.EXTRA_URI, uri)
 | 
			
		||||
                    putExtra(BackupConst.EXTRA_MODE, mode)
 | 
			
		||||
                }
 | 
			
		||||
                ContextCompat.startForegroundService(context, intent)
 | 
			
		||||
            }
 | 
			
		||||
@@ -118,15 +116,11 @@ class BackupRestoreService : Service() {
 | 
			
		||||
     */
 | 
			
		||||
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
 | 
			
		||||
        val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
 | 
			
		||||
        val mode = intent.getIntExtra(BackupConst.EXTRA_MODE, BackupConst.BACKUP_TYPE_FULL)
 | 
			
		||||
 | 
			
		||||
        // Cancel any previous job if needed.
 | 
			
		||||
        backupRestore?.job?.cancel()
 | 
			
		||||
 | 
			
		||||
        backupRestore = when (mode) {
 | 
			
		||||
            BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier)
 | 
			
		||||
            else -> LegacyBackupRestore(this, notifier)
 | 
			
		||||
        }
 | 
			
		||||
        backupRestore = FullBackupRestore(this, notifier)
 | 
			
		||||
 | 
			
		||||
        val handler = CoroutineExceptionHandler { _, exception ->
 | 
			
		||||
            logcat(LogPriority.ERROR, exception)
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        // Create root object
 | 
			
		||||
        var backup: Backup? = null
 | 
			
		||||
 | 
			
		||||
        databaseHelper.inTransaction {
 | 
			
		||||
        db.inTransaction {
 | 
			
		||||
            val databaseManga = getFavoriteManga()
 | 
			
		||||
 | 
			
		||||
            backup = Backup(
 | 
			
		||||
@@ -136,7 +136,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
    private fun backupCategories(options: Int): List<BackupCategory> {
 | 
			
		||||
        // Check if user wants category information in backup
 | 
			
		||||
        return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
 | 
			
		||||
            databaseHelper.getCategories()
 | 
			
		||||
            db.getCategories()
 | 
			
		||||
                .executeAsBlocking()
 | 
			
		||||
                .map { BackupCategory.copyFrom(it) }
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -158,7 +158,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        // Check if user wants chapter information in backup
 | 
			
		||||
        if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
 | 
			
		||||
            // Backup all the chapters
 | 
			
		||||
            val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
 | 
			
		||||
            val chapters = db.getChapters(manga).executeAsBlocking()
 | 
			
		||||
            if (chapters.isNotEmpty()) {
 | 
			
		||||
                mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) }
 | 
			
		||||
            }
 | 
			
		||||
@@ -167,7 +167,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        // Check if user wants category information in backup
 | 
			
		||||
        if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
 | 
			
		||||
            // Backup categories for this manga
 | 
			
		||||
            val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
 | 
			
		||||
            val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
 | 
			
		||||
            if (categoriesForManga.isNotEmpty()) {
 | 
			
		||||
                mangaObject.categories = categoriesForManga.mapNotNull { it.order }
 | 
			
		||||
            }
 | 
			
		||||
@@ -175,7 +175,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
 | 
			
		||||
        // Check if user wants track information in backup
 | 
			
		||||
        if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
 | 
			
		||||
            val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
 | 
			
		||||
            val tracks = db.getTracks(manga).executeAsBlocking()
 | 
			
		||||
            if (tracks.isNotEmpty()) {
 | 
			
		||||
                mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) }
 | 
			
		||||
            }
 | 
			
		||||
@@ -183,10 +183,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
 | 
			
		||||
        // Check if user wants history information in backup
 | 
			
		||||
        if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
 | 
			
		||||
            val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
 | 
			
		||||
            val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking()
 | 
			
		||||
            if (historyForManga.isNotEmpty()) {
 | 
			
		||||
                val history = historyForManga.mapNotNull { history ->
 | 
			
		||||
                    val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
 | 
			
		||||
                    val url = db.getChapter(history.chapter_id).executeAsBlocking()?.url
 | 
			
		||||
                    url?.let { BackupHistory(url, history.last_read) }
 | 
			
		||||
                }
 | 
			
		||||
                if (history.isNotEmpty()) {
 | 
			
		||||
@@ -224,7 +224,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     */
 | 
			
		||||
    internal fun restoreCategories(backupCategories: List<BackupCategory>) {
 | 
			
		||||
        // Get categories from file and from db
 | 
			
		||||
        val dbCategories = databaseHelper.getCategories().executeAsBlocking()
 | 
			
		||||
        val dbCategories = db.getCategories().executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
        // Iterate over them
 | 
			
		||||
        backupCategories.map { it.getCategoryImpl() }.forEach { category ->
 | 
			
		||||
@@ -244,7 +244,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
            if (!found) {
 | 
			
		||||
                // Let the db assign the id
 | 
			
		||||
                category.id = null
 | 
			
		||||
                val result = databaseHelper.insertCategory(category).executeAsBlocking()
 | 
			
		||||
                val result = db.insertCategory(category).executeAsBlocking()
 | 
			
		||||
                category.id = result.insertedId()?.toInt()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -257,7 +257,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
     * @param categories the categories to restore.
 | 
			
		||||
     */
 | 
			
		||||
    internal fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
 | 
			
		||||
        val dbCategories = databaseHelper.getCategories().executeAsBlocking()
 | 
			
		||||
        val dbCategories = db.getCategories().executeAsBlocking()
 | 
			
		||||
        val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
 | 
			
		||||
        categories.forEach { backupCategoryOrder ->
 | 
			
		||||
            backupCategories.firstOrNull {
 | 
			
		||||
@@ -273,8 +273,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
 | 
			
		||||
        // Update database
 | 
			
		||||
        if (mangaCategoriesToUpdate.isNotEmpty()) {
 | 
			
		||||
            databaseHelper.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
 | 
			
		||||
            databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
 | 
			
		||||
            db.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
 | 
			
		||||
            db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -287,7 +287,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        // List containing history to be updated
 | 
			
		||||
        val historyToBeUpdated = ArrayList<History>(history.size)
 | 
			
		||||
        for ((url, lastRead) in history) {
 | 
			
		||||
            val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
 | 
			
		||||
            val dbHistory = db.getHistoryByChapterUrl(url).executeAsBlocking()
 | 
			
		||||
            // Check if history already in database and update
 | 
			
		||||
            if (dbHistory != null) {
 | 
			
		||||
                dbHistory.apply {
 | 
			
		||||
@@ -296,7 +296,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
                historyToBeUpdated.add(dbHistory)
 | 
			
		||||
            } else {
 | 
			
		||||
                // If not in database create
 | 
			
		||||
                databaseHelper.getChapter(url).executeAsBlocking()?.let {
 | 
			
		||||
                db.getChapter(url).executeAsBlocking()?.let {
 | 
			
		||||
                    val historyToAdd = History.create(it).apply {
 | 
			
		||||
                        last_read = lastRead
 | 
			
		||||
                    }
 | 
			
		||||
@@ -304,7 +304,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking()
 | 
			
		||||
        db.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -318,7 +318,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        tracks.map { it.manga_id = manga.id!! }
 | 
			
		||||
 | 
			
		||||
        // Get tracks from database
 | 
			
		||||
        val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
 | 
			
		||||
        val dbTracks = db.getTracks(manga).executeAsBlocking()
 | 
			
		||||
        val trackToUpdate = mutableListOf<Track>()
 | 
			
		||||
 | 
			
		||||
        tracks.forEach { track ->
 | 
			
		||||
@@ -346,12 +346,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
 | 
			
		||||
        }
 | 
			
		||||
        // Update database
 | 
			
		||||
        if (trackToUpdate.isNotEmpty()) {
 | 
			
		||||
            databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
 | 
			
		||||
            db.insertTracks(trackToUpdate).executeAsBlocking()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
 | 
			
		||||
        val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
 | 
			
		||||
        val dbChapters = db.getChapters(manga).executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
        chapters.forEach { chapter ->
 | 
			
		||||
            val dbChapter = dbChapters.find { it.url == chapter.url }
 | 
			
		||||
 
 | 
			
		||||
@@ -34,8 +34,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Store source mapping for error messages
 | 
			
		||||
        var backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
 | 
			
		||||
        sourceMapping = backupMaps.map { it.sourceId to it.name }.toMap()
 | 
			
		||||
        val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
 | 
			
		||||
        sourceMapping = backupMaps.associate { it.sourceId to it.name }
 | 
			
		||||
 | 
			
		||||
        // Restore individual manga
 | 
			
		||||
        backup.backupManga.forEach {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,20 @@ 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.ValidatorParseException
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import okio.buffer
 | 
			
		||||
import okio.gzip
 | 
			
		||||
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()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks for critical backup file data.
 | 
			
		||||
     *
 | 
			
		||||
@@ -27,11 +33,11 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
 | 
			
		||||
                    .use { it.readByteArray() }
 | 
			
		||||
            backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            throw ValidatorParseException(e)
 | 
			
		||||
            throw IllegalStateException(e)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (backup.backupManga.isEmpty()) {
 | 
			
		||||
            throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
 | 
			
		||||
            throw IllegalStateException(context.getString(R.string.invalid_backup_file_missing_manga))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val sources = backup.backupSources.associate { it.sourceId to it.name }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,252 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.Companion.CURRENT_VERSION
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryImplTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterImplTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.HistoryTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaImplTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackImplTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackTypeSerializer
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Chapter
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.History
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.toSManga
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import kotlinx.serialization.modules.SerializersModule
 | 
			
		||||
import kotlinx.serialization.modules.contextual
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
 | 
			
		||||
class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) {
 | 
			
		||||
 | 
			
		||||
    val parser: Json = when (version) {
 | 
			
		||||
        2 -> Json {
 | 
			
		||||
            // Forks may have added items to backup
 | 
			
		||||
            ignoreUnknownKeys = true
 | 
			
		||||
 | 
			
		||||
            // Register custom serializers
 | 
			
		||||
            serializersModule = SerializersModule {
 | 
			
		||||
                contextual(MangaTypeSerializer)
 | 
			
		||||
                contextual(MangaImplTypeSerializer)
 | 
			
		||||
                contextual(ChapterTypeSerializer)
 | 
			
		||||
                contextual(ChapterImplTypeSerializer)
 | 
			
		||||
                contextual(CategoryTypeSerializer)
 | 
			
		||||
                contextual(CategoryImplTypeSerializer)
 | 
			
		||||
                contextual(TrackTypeSerializer)
 | 
			
		||||
                contextual(TrackImplTypeSerializer)
 | 
			
		||||
                contextual(HistoryTypeSerializer)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else -> throw Exception("Unknown backup version")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create backup Json file from database
 | 
			
		||||
     *
 | 
			
		||||
     * @param uri path of Uri
 | 
			
		||||
     * @param isAutoBackup backup called from scheduled backup job
 | 
			
		||||
     */
 | 
			
		||||
    override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean) =
 | 
			
		||||
        throw IllegalStateException("Legacy backup creation is not supported")
 | 
			
		||||
 | 
			
		||||
    fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
 | 
			
		||||
        manga.id = dbManga.id
 | 
			
		||||
        manga.copyFrom(dbManga)
 | 
			
		||||
        manga.favorite = true
 | 
			
		||||
        insertManga(manga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetches manga information
 | 
			
		||||
     *
 | 
			
		||||
     * @param source source of manga
 | 
			
		||||
     * @param manga manga that needs updating
 | 
			
		||||
     * @return Updated manga.
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun fetchManga(source: Source, manga: Manga): Manga {
 | 
			
		||||
        val networkManga = source.getMangaDetails(manga.toMangaInfo())
 | 
			
		||||
        return manga.also {
 | 
			
		||||
            it.copyFrom(networkManga.toSManga())
 | 
			
		||||
            it.favorite = true
 | 
			
		||||
            it.initialized = true
 | 
			
		||||
            it.id = insertManga(manga)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restore the categories from Json
 | 
			
		||||
     *
 | 
			
		||||
     * @param backupCategories array containing categories
 | 
			
		||||
     */
 | 
			
		||||
    internal fun restoreCategories(backupCategories: List<Category>) {
 | 
			
		||||
        // Get categories from file and from db
 | 
			
		||||
        val dbCategories = databaseHelper.getCategories().executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
        // Iterate over them
 | 
			
		||||
        backupCategories.forEach { category ->
 | 
			
		||||
            // Used to know if the category is already in the db
 | 
			
		||||
            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.id = dbCategory.id
 | 
			
		||||
                    found = true
 | 
			
		||||
                    break
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // If the category isn't in the db, remove the id and insert a new category
 | 
			
		||||
            // Store the inserted id in the category
 | 
			
		||||
            if (!found) {
 | 
			
		||||
                // Let the db assign the id
 | 
			
		||||
                category.id = null
 | 
			
		||||
                val result = databaseHelper.insertCategory(category).executeAsBlocking()
 | 
			
		||||
                category.id = result.insertedId()?.toInt()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restores the categories a manga is in.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga the manga whose categories have to be restored.
 | 
			
		||||
     * @param categories the categories to restore.
 | 
			
		||||
     */
 | 
			
		||||
    internal fun restoreCategoriesForManga(manga: Manga, categories: List<String>) {
 | 
			
		||||
        val dbCategories = databaseHelper.getCategories().executeAsBlocking()
 | 
			
		||||
        val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
 | 
			
		||||
        for (backupCategoryStr in categories) {
 | 
			
		||||
            for (dbCategory in dbCategories) {
 | 
			
		||||
                if (backupCategoryStr == dbCategory.name) {
 | 
			
		||||
                    mangaCategoriesToUpdate.add(MangaCategory.create(manga, dbCategory))
 | 
			
		||||
                    break
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update database
 | 
			
		||||
        if (mangaCategoriesToUpdate.isNotEmpty()) {
 | 
			
		||||
            databaseHelper.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
 | 
			
		||||
            databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restore history from Json
 | 
			
		||||
     *
 | 
			
		||||
     * @param history list containing history to be restored
 | 
			
		||||
     */
 | 
			
		||||
    internal fun restoreHistoryForManga(history: List<DHistory>) {
 | 
			
		||||
        // List containing history to be updated
 | 
			
		||||
        val historyToBeUpdated = ArrayList<History>(history.size)
 | 
			
		||||
        for ((url, lastRead) in history) {
 | 
			
		||||
            val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
 | 
			
		||||
            // Check if history already in database and update
 | 
			
		||||
            if (dbHistory != null) {
 | 
			
		||||
                dbHistory.apply {
 | 
			
		||||
                    last_read = max(lastRead, dbHistory.last_read)
 | 
			
		||||
                }
 | 
			
		||||
                historyToBeUpdated.add(dbHistory)
 | 
			
		||||
            } else {
 | 
			
		||||
                // If not in database create
 | 
			
		||||
                databaseHelper.getChapter(url).executeAsBlocking()?.let {
 | 
			
		||||
                    val historyToAdd = History.create(it).apply {
 | 
			
		||||
                        last_read = lastRead
 | 
			
		||||
                    }
 | 
			
		||||
                    historyToBeUpdated.add(historyToAdd)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restores the sync of a manga.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga the manga whose sync have to be restored.
 | 
			
		||||
     * @param tracks the track list to restore.
 | 
			
		||||
     */
 | 
			
		||||
    internal fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
 | 
			
		||||
        // Get tracks from database
 | 
			
		||||
        val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
 | 
			
		||||
        val trackToUpdate = ArrayList<Track>(tracks.size)
 | 
			
		||||
 | 
			
		||||
        tracks.forEach { track ->
 | 
			
		||||
            // Fix foreign keys with the current manga id
 | 
			
		||||
            track.manga_id = manga.id!!
 | 
			
		||||
 | 
			
		||||
            val service = trackManager.getService(track.sync_id)
 | 
			
		||||
            if (service != null && service.isLogged) {
 | 
			
		||||
                var isInDatabase = false
 | 
			
		||||
                for (dbTrack in dbTracks) {
 | 
			
		||||
                    if (track.sync_id == dbTrack.sync_id) {
 | 
			
		||||
                        // The sync is already in the db, only update its fields
 | 
			
		||||
                        if (track.media_id != dbTrack.media_id) {
 | 
			
		||||
                            dbTrack.media_id = track.media_id
 | 
			
		||||
                        }
 | 
			
		||||
                        if (track.library_id != dbTrack.library_id) {
 | 
			
		||||
                            dbTrack.library_id = track.library_id
 | 
			
		||||
                        }
 | 
			
		||||
                        dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
 | 
			
		||||
                        isInDatabase = true
 | 
			
		||||
                        trackToUpdate.add(dbTrack)
 | 
			
		||||
                        break
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!isInDatabase) {
 | 
			
		||||
                    // Insert new sync. Let the db assign the id
 | 
			
		||||
                    track.id = null
 | 
			
		||||
                    trackToUpdate.add(track)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Update database
 | 
			
		||||
        if (trackToUpdate.isNotEmpty()) {
 | 
			
		||||
            databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restore the chapters for manga if chapters already in database
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga manga of chapters
 | 
			
		||||
     * @param chapters list containing chapters that get restored
 | 
			
		||||
     * @return boolean answering if chapter fetch is not needed
 | 
			
		||||
     */
 | 
			
		||||
    internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>): Boolean {
 | 
			
		||||
        val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
        // Return if fetch is needed
 | 
			
		||||
        if (dbChapters.isEmpty() || dbChapters.size < chapters.size) {
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (chapter in chapters) {
 | 
			
		||||
            val pos = dbChapters.indexOf(chapter)
 | 
			
		||||
            if (pos != -1) {
 | 
			
		||||
                val dbChapter = dbChapters[pos]
 | 
			
		||||
                chapter.id = dbChapter.id
 | 
			
		||||
                chapter.copyFrom(dbChapter)
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            chapter.manga_id = manga.id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Filter the chapters that couldn't be found.
 | 
			
		||||
        updateChapters(chapters.filter { it.id != null })
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,184 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy
 | 
			
		||||
 | 
			
		||||
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.legacy.models.Backup
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.models.MangaObject
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Chapter
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import kotlinx.serialization.json.JsonObject
 | 
			
		||||
import kotlinx.serialization.json.decodeFromJsonElement
 | 
			
		||||
import kotlinx.serialization.json.decodeFromStream
 | 
			
		||||
import kotlinx.serialization.json.intOrNull
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<LegacyBackupManager>(context, notifier) {
 | 
			
		||||
 | 
			
		||||
    override suspend fun performRestore(uri: Uri): Boolean {
 | 
			
		||||
        // Read the json and create a Json Object,
 | 
			
		||||
        // cannot use the backupManager json deserializer one because its not initialized yet
 | 
			
		||||
        val backupObject = Json.decodeFromStream<JsonObject>(
 | 
			
		||||
            context.contentResolver.openInputStream(uri)!!,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        // Get parser version
 | 
			
		||||
        val version = backupObject["version"]?.jsonPrimitive?.intOrNull ?: 1
 | 
			
		||||
 | 
			
		||||
        // Initialize manager
 | 
			
		||||
        backupManager = LegacyBackupManager(context, version)
 | 
			
		||||
 | 
			
		||||
        // Decode the json object to a Backup object
 | 
			
		||||
        val backup = backupManager.parser.decodeFromJsonElement<Backup>(backupObject)
 | 
			
		||||
 | 
			
		||||
        restoreAmount = backup.mangas.size + 1 // +1 for categories
 | 
			
		||||
 | 
			
		||||
        // Restore categories
 | 
			
		||||
        backup.categories?.let { restoreCategories(it) }
 | 
			
		||||
 | 
			
		||||
        // Store source mapping for error messages
 | 
			
		||||
        sourceMapping = LegacyBackupRestoreValidator.getSourceMapping(backup.extensions ?: emptyList())
 | 
			
		||||
 | 
			
		||||
        // Restore individual manga
 | 
			
		||||
        backup.mangas.forEach {
 | 
			
		||||
            if (job?.isActive != true) {
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            restoreManga(it)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun restoreCategories(categoriesJson: List<Category>) {
 | 
			
		||||
        db.inTransaction {
 | 
			
		||||
            backupManager.restoreCategories(categoriesJson)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        restoreProgress += 1
 | 
			
		||||
        showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun restoreManga(mangaJson: MangaObject) {
 | 
			
		||||
        val manga = mangaJson.manga
 | 
			
		||||
        val chapters = mangaJson.chapters ?: emptyList()
 | 
			
		||||
        val categories = mangaJson.categories ?: emptyList()
 | 
			
		||||
        val history = mangaJson.history ?: emptyList()
 | 
			
		||||
        val tracks = mangaJson.track ?: emptyList()
 | 
			
		||||
 | 
			
		||||
        val source = backupManager.sourceManager.get(manga.source)
 | 
			
		||||
        val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            if (source != null) {
 | 
			
		||||
                restoreMangaData(manga, source, chapters, categories, history, tracks)
 | 
			
		||||
            } else {
 | 
			
		||||
                errors.add(Date() to "${manga.title} [$sourceName]: ${context.getString(R.string.source_not_found_name, sourceName)}")
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        restoreProgress += 1
 | 
			
		||||
        showRestoreProgress(restoreProgress, restoreAmount, manga.title)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a manga restore observable
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga manga data from json
 | 
			
		||||
     * @param source source to get manga data from
 | 
			
		||||
     * @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,
 | 
			
		||||
        source: Source,
 | 
			
		||||
        chapters: List<Chapter>,
 | 
			
		||||
        categories: List<String>,
 | 
			
		||||
        history: List<DHistory>,
 | 
			
		||||
        tracks: List<Track>,
 | 
			
		||||
    ) {
 | 
			
		||||
        val dbManga = backupManager.getMangaFromDatabase(manga)
 | 
			
		||||
 | 
			
		||||
        db.inTransaction {
 | 
			
		||||
            if (dbManga == null) {
 | 
			
		||||
                // Manga not in database
 | 
			
		||||
                restoreMangaFetch(source, manga, chapters, categories, history, tracks)
 | 
			
		||||
            } else { // Manga in database
 | 
			
		||||
                // Copy information from manga already in database
 | 
			
		||||
                backupManager.restoreMangaNoFetch(manga, dbManga)
 | 
			
		||||
                // Fetch rest of manga information
 | 
			
		||||
                restoreMangaNoFetch(source, manga, chapters, categories, history, tracks)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetches manga information.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga manga that needs updating
 | 
			
		||||
     * @param chapters chapters of manga that needs updating
 | 
			
		||||
     * @param categories categories that need updating
 | 
			
		||||
     */
 | 
			
		||||
    private suspend fun restoreMangaFetch(
 | 
			
		||||
        source: Source,
 | 
			
		||||
        manga: Manga,
 | 
			
		||||
        chapters: List<Chapter>,
 | 
			
		||||
        categories: List<String>,
 | 
			
		||||
        history: List<DHistory>,
 | 
			
		||||
        tracks: List<Track>,
 | 
			
		||||
    ) {
 | 
			
		||||
        try {
 | 
			
		||||
            val fetchedManga = backupManager.fetchManga(source, manga)
 | 
			
		||||
            fetchedManga.id ?: return
 | 
			
		||||
 | 
			
		||||
            updateChapters(source, fetchedManga, chapters)
 | 
			
		||||
 | 
			
		||||
            restoreExtraForManga(fetchedManga, categories, history, tracks)
 | 
			
		||||
 | 
			
		||||
            updateTracking(fetchedManga, tracks)
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            errors.add(Date() to "${manga.title} - ${e.message}")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun restoreMangaNoFetch(
 | 
			
		||||
        source: Source,
 | 
			
		||||
        backupManga: Manga,
 | 
			
		||||
        chapters: List<Chapter>,
 | 
			
		||||
        categories: List<String>,
 | 
			
		||||
        history: List<DHistory>,
 | 
			
		||||
        tracks: List<Track>,
 | 
			
		||||
    ) {
 | 
			
		||||
        if (!backupManager.restoreChaptersForManga(backupManga, chapters)) {
 | 
			
		||||
            updateChapters(source, backupManga, chapters)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        restoreExtraForManga(backupManga, categories, history, tracks)
 | 
			
		||||
 | 
			
		||||
        updateTracking(backupManga, tracks)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
 | 
			
		||||
        // Restore categories
 | 
			
		||||
        backupManager.restoreCategoriesForManga(manga, categories)
 | 
			
		||||
 | 
			
		||||
        // Restore history
 | 
			
		||||
        backupManager.restoreHistoryForManga(history)
 | 
			
		||||
 | 
			
		||||
        // Restore tracking
 | 
			
		||||
        backupManager.restoreTrackForManga(manga, tracks)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy
 | 
			
		||||
 | 
			
		||||
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.ValidatorParseException
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
 | 
			
		||||
import kotlinx.serialization.json.decodeFromStream
 | 
			
		||||
 | 
			
		||||
class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks for critical backup file data.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception if version or manga cannot be found.
 | 
			
		||||
     * @return List of missing sources or missing trackers.
 | 
			
		||||
     */
 | 
			
		||||
    override fun validate(context: Context, uri: Uri): Results {
 | 
			
		||||
        val backupManager = LegacyBackupManager(context)
 | 
			
		||||
 | 
			
		||||
        val backup = try {
 | 
			
		||||
            backupManager.parser.decodeFromStream<Backup>(
 | 
			
		||||
                context.contentResolver.openInputStream(uri)!!,
 | 
			
		||||
            )
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            throw ValidatorParseException(e)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (backup.version == null) {
 | 
			
		||||
            throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (backup.mangas.isEmpty()) {
 | 
			
		||||
            throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val sources = getSourceMapping(backup.extensions ?: emptyList())
 | 
			
		||||
        val missingSources = sources
 | 
			
		||||
            .filter { sourceManager.get(it.key) == null }
 | 
			
		||||
            .values
 | 
			
		||||
            .sorted()
 | 
			
		||||
 | 
			
		||||
        val trackers = backup.mangas
 | 
			
		||||
            .filterNot { it.track.isNullOrEmpty() }
 | 
			
		||||
            .flatMap { it.track ?: emptyList() }
 | 
			
		||||
            .map { it.sync_id }
 | 
			
		||||
            .distinct()
 | 
			
		||||
        val missingTrackers = trackers
 | 
			
		||||
            .mapNotNull { trackManager.getService(it) }
 | 
			
		||||
            .filter { !it.isLogged }
 | 
			
		||||
            .map { context.getString(it.nameRes()) }
 | 
			
		||||
            .sorted()
 | 
			
		||||
 | 
			
		||||
        return Results(missingSources, missingTrackers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun getSourceMapping(extensionsMapping: List<String>): Map<Long, String> {
 | 
			
		||||
            return extensionsMapping.associate {
 | 
			
		||||
                val items = it.split(":")
 | 
			
		||||
                items[0].toLong() to items[1]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,37 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy.models
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Chapter
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import kotlinx.serialization.Contextual
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Date
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Backup(
 | 
			
		||||
    val version: Int? = null,
 | 
			
		||||
    var mangas: MutableList<MangaObject> = mutableListOf(),
 | 
			
		||||
    var categories: List<@Contextual Category>? = null,
 | 
			
		||||
    var extensions: List<String>? = null,
 | 
			
		||||
) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val CURRENT_VERSION = 2
 | 
			
		||||
 | 
			
		||||
        fun getDefaultFilename(): String {
 | 
			
		||||
            val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
 | 
			
		||||
            return "tachiyomi_$date.json"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class MangaObject(
 | 
			
		||||
    var manga: @Contextual Manga,
 | 
			
		||||
    var chapters: List<@Contextual Chapter>? = null,
 | 
			
		||||
    var categories: List<String>? = null,
 | 
			
		||||
    var track: List<@Contextual Track>? = null,
 | 
			
		||||
    var history: List<@Contextual DHistory>? = null,
 | 
			
		||||
)
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy.models
 | 
			
		||||
 | 
			
		||||
data class DHistory(val url: String, val lastRead: Long)
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
			
		||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
import kotlinx.serialization.json.JsonDecoder
 | 
			
		||||
import kotlinx.serialization.json.JsonEncoder
 | 
			
		||||
import kotlinx.serialization.json.add
 | 
			
		||||
import kotlinx.serialization.json.buildJsonArray
 | 
			
		||||
import kotlinx.serialization.json.int
 | 
			
		||||
import kotlinx.serialization.json.jsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * JSON Serializer used to write / read [CategoryImpl] to / from json
 | 
			
		||||
 */
 | 
			
		||||
open class CategoryBaseSerializer<T : Category> : KSerializer<T> {
 | 
			
		||||
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Category")
 | 
			
		||||
 | 
			
		||||
    override fun serialize(encoder: Encoder, value: T) {
 | 
			
		||||
        encoder as JsonEncoder
 | 
			
		||||
        encoder.encodeJsonElement(
 | 
			
		||||
            buildJsonArray {
 | 
			
		||||
                add(value.name)
 | 
			
		||||
                add(value.order)
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Suppress("UNCHECKED_CAST")
 | 
			
		||||
    override fun deserialize(decoder: Decoder): T {
 | 
			
		||||
        // make a category impl and cast as T so that the serializer accepts it
 | 
			
		||||
        return CategoryImpl().apply {
 | 
			
		||||
            decoder as JsonDecoder
 | 
			
		||||
            val array = decoder.decodeJsonElement().jsonArray
 | 
			
		||||
            name = array[0].jsonPrimitive.content
 | 
			
		||||
            order = array[1].jsonPrimitive.int
 | 
			
		||||
        } as T
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Allow for serialization of a category and category impl
 | 
			
		||||
object CategoryTypeSerializer : CategoryBaseSerializer<Category>()
 | 
			
		||||
 | 
			
		||||
object CategoryImplTypeSerializer : CategoryBaseSerializer<CategoryImpl>()
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Chapter
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
import kotlinx.serialization.json.JsonDecoder
 | 
			
		||||
import kotlinx.serialization.json.JsonEncoder
 | 
			
		||||
import kotlinx.serialization.json.buildJsonObject
 | 
			
		||||
import kotlinx.serialization.json.intOrNull
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import kotlinx.serialization.json.put
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * JSON Serializer used to write / read [ChapterImpl] to / from json
 | 
			
		||||
 */
 | 
			
		||||
open class ChapterBaseSerializer<T : Chapter> : KSerializer<T> {
 | 
			
		||||
 | 
			
		||||
    override val descriptor = buildClassSerialDescriptor("Chapter")
 | 
			
		||||
 | 
			
		||||
    override fun serialize(encoder: Encoder, value: T) {
 | 
			
		||||
        encoder as JsonEncoder
 | 
			
		||||
        encoder.encodeJsonElement(
 | 
			
		||||
            buildJsonObject {
 | 
			
		||||
                put(URL, value.url)
 | 
			
		||||
                if (value.read) {
 | 
			
		||||
                    put(READ, 1)
 | 
			
		||||
                }
 | 
			
		||||
                if (value.bookmark) {
 | 
			
		||||
                    put(BOOKMARK, 1)
 | 
			
		||||
                }
 | 
			
		||||
                if (value.last_page_read != 0) {
 | 
			
		||||
                    put(LAST_READ, value.last_page_read)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Suppress("UNCHECKED_CAST")
 | 
			
		||||
    override fun deserialize(decoder: Decoder): T {
 | 
			
		||||
        // make a chapter impl and cast as T so that the serializer accepts it
 | 
			
		||||
        return ChapterImpl().apply {
 | 
			
		||||
            decoder as JsonDecoder
 | 
			
		||||
            val jsonObject = decoder.decodeJsonElement().jsonObject
 | 
			
		||||
            url = jsonObject[URL]!!.jsonPrimitive.content
 | 
			
		||||
            read = jsonObject[READ]?.jsonPrimitive?.intOrNull == 1
 | 
			
		||||
            bookmark = jsonObject[BOOKMARK]?.jsonPrimitive?.intOrNull == 1
 | 
			
		||||
            last_page_read = jsonObject[LAST_READ]?.jsonPrimitive?.intOrNull ?: last_page_read
 | 
			
		||||
        } as T
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val URL = "u"
 | 
			
		||||
        private const val READ = "r"
 | 
			
		||||
        private const val BOOKMARK = "b"
 | 
			
		||||
        private const val LAST_READ = "l"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Allow for serialization of a chapter and chapter impl
 | 
			
		||||
object ChapterTypeSerializer : ChapterBaseSerializer<Chapter>()
 | 
			
		||||
 | 
			
		||||
object ChapterImplTypeSerializer : ChapterBaseSerializer<ChapterImpl>()
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
			
		||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
import kotlinx.serialization.json.JsonDecoder
 | 
			
		||||
import kotlinx.serialization.json.JsonEncoder
 | 
			
		||||
import kotlinx.serialization.json.add
 | 
			
		||||
import kotlinx.serialization.json.buildJsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import kotlinx.serialization.json.long
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * JSON Serializer used to write / read [DHistory] to / from json
 | 
			
		||||
 */
 | 
			
		||||
object HistoryTypeSerializer : KSerializer<DHistory> {
 | 
			
		||||
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("History")
 | 
			
		||||
 | 
			
		||||
    override fun serialize(encoder: Encoder, value: DHistory) {
 | 
			
		||||
        encoder as JsonEncoder
 | 
			
		||||
        encoder.encodeJsonElement(
 | 
			
		||||
            buildJsonArray {
 | 
			
		||||
                add(value.url)
 | 
			
		||||
                add(value.lastRead)
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun deserialize(decoder: Decoder): DHistory {
 | 
			
		||||
        decoder as JsonDecoder
 | 
			
		||||
        val array = decoder.decodeJsonElement().jsonArray
 | 
			
		||||
        return DHistory(
 | 
			
		||||
            url = array[0].jsonPrimitive.content,
 | 
			
		||||
            lastRead = array[1].jsonPrimitive.long,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
			
		||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
import kotlinx.serialization.json.JsonDecoder
 | 
			
		||||
import kotlinx.serialization.json.JsonEncoder
 | 
			
		||||
import kotlinx.serialization.json.add
 | 
			
		||||
import kotlinx.serialization.json.buildJsonArray
 | 
			
		||||
import kotlinx.serialization.json.int
 | 
			
		||||
import kotlinx.serialization.json.jsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import kotlinx.serialization.json.long
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * JSON Serializer used to write / read [MangaImpl] to / from json
 | 
			
		||||
 */
 | 
			
		||||
open class MangaBaseSerializer<T : Manga> : KSerializer<T> {
 | 
			
		||||
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Manga")
 | 
			
		||||
 | 
			
		||||
    override fun serialize(encoder: Encoder, value: T) {
 | 
			
		||||
        encoder as JsonEncoder
 | 
			
		||||
        encoder.encodeJsonElement(
 | 
			
		||||
            buildJsonArray {
 | 
			
		||||
                add(value.url)
 | 
			
		||||
                add(value.title)
 | 
			
		||||
                add(value.source)
 | 
			
		||||
                add(value.viewer_flags)
 | 
			
		||||
                add(value.chapter_flags)
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Suppress("UNCHECKED_CAST")
 | 
			
		||||
    override fun deserialize(decoder: Decoder): T {
 | 
			
		||||
        // make a manga impl and cast as T so that the serializer accepts it
 | 
			
		||||
        return MangaImpl().apply {
 | 
			
		||||
            decoder as JsonDecoder
 | 
			
		||||
            val array = decoder.decodeJsonElement().jsonArray
 | 
			
		||||
            url = array[0].jsonPrimitive.content
 | 
			
		||||
            title = array[1].jsonPrimitive.content
 | 
			
		||||
            source = array[2].jsonPrimitive.long
 | 
			
		||||
            viewer_flags = array[3].jsonPrimitive.int
 | 
			
		||||
            chapter_flags = array[4].jsonPrimitive.int
 | 
			
		||||
        } as T
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Allow for serialization of a manga and manga impl
 | 
			
		||||
object MangaTypeSerializer : MangaBaseSerializer<Manga>()
 | 
			
		||||
 | 
			
		||||
object MangaImplTypeSerializer : MangaBaseSerializer<MangaImpl>()
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
			
		||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
import kotlinx.serialization.json.JsonDecoder
 | 
			
		||||
import kotlinx.serialization.json.JsonEncoder
 | 
			
		||||
import kotlinx.serialization.json.buildJsonObject
 | 
			
		||||
import kotlinx.serialization.json.float
 | 
			
		||||
import kotlinx.serialization.json.int
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import kotlinx.serialization.json.long
 | 
			
		||||
import kotlinx.serialization.json.put
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * JSON Serializer used to write / read [TrackImpl] to / from json
 | 
			
		||||
 */
 | 
			
		||||
open class TrackBaseSerializer<T : Track> : KSerializer<T> {
 | 
			
		||||
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Track")
 | 
			
		||||
 | 
			
		||||
    override fun serialize(encoder: Encoder, value: T) {
 | 
			
		||||
        encoder as JsonEncoder
 | 
			
		||||
        encoder.encodeJsonElement(
 | 
			
		||||
            buildJsonObject {
 | 
			
		||||
                put(TITLE, value.title)
 | 
			
		||||
                put(SYNC, value.sync_id)
 | 
			
		||||
                put(MEDIA, value.media_id)
 | 
			
		||||
                put(LIBRARY, value.library_id)
 | 
			
		||||
                put(LAST_READ, value.last_chapter_read)
 | 
			
		||||
                put(TRACKING_URL, value.tracking_url)
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Suppress("UNCHECKED_CAST")
 | 
			
		||||
    override fun deserialize(decoder: Decoder): T {
 | 
			
		||||
        // make a track impl and cast as T so that the serializer accepts it
 | 
			
		||||
        return TrackImpl().apply {
 | 
			
		||||
            decoder as JsonDecoder
 | 
			
		||||
            val jsonObject = decoder.decodeJsonElement().jsonObject
 | 
			
		||||
            title = jsonObject[TITLE]!!.jsonPrimitive.content
 | 
			
		||||
            sync_id = jsonObject[SYNC]!!.jsonPrimitive.int
 | 
			
		||||
            media_id = jsonObject[MEDIA]!!.jsonPrimitive.long
 | 
			
		||||
            library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long
 | 
			
		||||
            last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.float
 | 
			
		||||
            tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content
 | 
			
		||||
        } as T
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val SYNC = "s"
 | 
			
		||||
        private const val MEDIA = "r"
 | 
			
		||||
        private const val LIBRARY = "ml"
 | 
			
		||||
        private const val TITLE = "t"
 | 
			
		||||
        private const val LAST_READ = "l"
 | 
			
		||||
        private const val TRACKING_URL = "u"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Allow for serialization of a track and track impl
 | 
			
		||||
object TrackTypeSerializer : TrackBaseSerializer<Track>()
 | 
			
		||||
 | 
			
		||||
object TrackImplTypeSerializer : TrackBaseSerializer<TrackImpl>()
 | 
			
		||||
@@ -29,12 +29,4 @@ object TrackTable {
 | 
			
		||||
    const val COL_START_DATE = "start_date"
 | 
			
		||||
 | 
			
		||||
    const val COL_FINISH_DATE = "finish_date"
 | 
			
		||||
 | 
			
		||||
    val insertFromTempTable: String
 | 
			
		||||
        get() =
 | 
			
		||||
            """
 | 
			
		||||
            |INSERT INTO $TABLE($COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE)
 | 
			
		||||
            |SELECT $COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE
 | 
			
		||||
            |FROM ${TABLE}_tmp
 | 
			
		||||
            """.trimMargin()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,10 +22,8 @@ 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.BackupRestoreService
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestoreValidator
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestoreValidator
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.bindTo
 | 
			
		||||
@@ -272,19 +270,9 @@ class SettingsBackupController : SettingsController() {
 | 
			
		||||
            val uri: Uri = args.getParcelable(KEY_URI)!!
 | 
			
		||||
 | 
			
		||||
            return try {
 | 
			
		||||
                var type = BackupConst.BACKUP_TYPE_FULL
 | 
			
		||||
                val results = try {
 | 
			
		||||
                    FullBackupRestoreValidator().validate(activity, uri)
 | 
			
		||||
                } catch (_: ValidatorParseException) {
 | 
			
		||||
                    type = BackupConst.BACKUP_TYPE_LEGACY
 | 
			
		||||
                    LegacyBackupRestoreValidator().validate(activity, uri)
 | 
			
		||||
                }
 | 
			
		||||
                val results = FullBackupRestoreValidator().validate(activity, uri)
 | 
			
		||||
 | 
			
		||||
                var message = if (type == BackupConst.BACKUP_TYPE_FULL) {
 | 
			
		||||
                    activity.getString(R.string.backup_restore_content_full)
 | 
			
		||||
                } else {
 | 
			
		||||
                    activity.getString(R.string.backup_restore_content)
 | 
			
		||||
                }
 | 
			
		||||
                var message = activity.getString(R.string.backup_restore_content_full)
 | 
			
		||||
                if (results.missingSources.isNotEmpty()) {
 | 
			
		||||
                    message += "\n\n${activity.getString(R.string.backup_restore_missing_sources)}\n${results.missingSources.joinToString("\n") { "- $it" }}"
 | 
			
		||||
                }
 | 
			
		||||
@@ -296,7 +284,7 @@ class SettingsBackupController : SettingsController() {
 | 
			
		||||
                    .setTitle(R.string.pref_restore_backup)
 | 
			
		||||
                    .setMessage(message)
 | 
			
		||||
                    .setPositiveButton(R.string.action_restore) { _, _ ->
 | 
			
		||||
                        BackupRestoreService.start(activity, uri, type)
 | 
			
		||||
                        BackupRestoreService.start(activity, uri)
 | 
			
		||||
                    }
 | 
			
		||||
                    .create()
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user