diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index 3c12cecfe..fa06f55bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -6,11 +6,13 @@ import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator +import eu.kanade.tachiyomi.data.backup.create.creators.ExtensionRepoBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupSource @@ -45,6 +47,7 @@ class BackupCreator( private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(), private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(), private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(), + private val extensionRepoBackupCreator: ExtensionRepoBackupCreator = ExtensionRepoBackupCreator(), private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(), ) { @@ -78,6 +81,7 @@ class BackupCreator( backupCategories = backupCategories(options), backupSources = backupSources(backupManga), backupPreferences = backupAppPreferences(options), + backupExtensionRepo = backupExtensionRepos(options), backupSourcePreferences = backupSourcePreferences(options), ) @@ -133,6 +137,12 @@ class BackupCreator( return preferenceBackupCreator.createApp(includePrivatePreferences = options.privateSettings) } + private suspend fun backupExtensionRepos(options: BackupOptions): List { + if (!options.extensionRepoSettings) return emptyList() + + return extensionRepoBackupCreator() + } + private fun backupSourcePreferences(options: BackupOptions): List { if (!options.sourceSettings) return emptyList() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupOptions.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupOptions.kt index 7130c25cf..daaecff8e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupOptions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupOptions.kt @@ -11,6 +11,7 @@ data class BackupOptions( val tracking: Boolean = true, val history: Boolean = true, val appSettings: Boolean = true, + val extensionRepoSettings: Boolean = true, val sourceSettings: Boolean = true, val privateSettings: Boolean = false, ) { @@ -22,11 +23,12 @@ data class BackupOptions( tracking, history, appSettings, + extensionRepoSettings, sourceSettings, privateSettings, ) - fun canCreate() = libraryEntries || categories || appSettings || sourceSettings + fun canCreate() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings companion object { val libraryOptions = persistentListOf( @@ -66,6 +68,11 @@ data class BackupOptions( getter = BackupOptions::appSettings, setter = { options, enabled -> options.copy(appSettings = enabled) }, ), + Entry( + label = MR.strings.extensionRepo_settings, + getter = BackupOptions::extensionRepoSettings, + setter = { options, enabled -> options.copy(extensionRepoSettings = enabled) }, + ), Entry( label = MR.strings.source_settings, getter = BackupOptions::sourceSettings, @@ -86,8 +93,9 @@ data class BackupOptions( tracking = array[3], history = array[4], appSettings = array[5], - sourceSettings = array[6], - privateSettings = array[7], + extensionRepoSettings = array[6], + sourceSettings = array[7], + privateSettings = array[8], ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/ExtensionRepoBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/ExtensionRepoBackupCreator.kt new file mode 100644 index 000000000..2db6a6f06 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/ExtensionRepoBackupCreator.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos +import eu.kanade.tachiyomi.data.backup.models.backupExtensionReposMapper +import mihon.domain.extensionrepo.interactor.GetExtensionRepo +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class ExtensionRepoBackupCreator( + private val getExtensionRepos: GetExtensionRepo = Injekt.get(), +) { + + suspend operator fun invoke(): List { + return getExtensionRepos.getAll() + .map(backupExtensionReposMapper) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt index dcf3d1174..75c75b6b5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber +@Suppress("MagicNumber") @Serializable data class Backup( @ProtoNumber(1) val backupManga: List, @@ -11,4 +12,5 @@ data class Backup( @ProtoNumber(101) var backupSources: List = emptyList(), @ProtoNumber(104) var backupPreferences: List = emptyList(), @ProtoNumber(105) var backupSourcePreferences: List = emptyList(), + @ProtoNumber(106) var backupExtensionRepo: List = emptyList(), ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupExtensionRepos.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupExtensionRepos.kt new file mode 100644 index 000000000..fa9968667 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupExtensionRepos.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.data.backup.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import mihon.domain.extensionrepo.model.ExtensionRepo + +@Suppress("MagicNumber") +@Serializable +class BackupExtensionRepos( + @ProtoNumber(1) var baseUrl: String, + @ProtoNumber(2) var name: String, + @ProtoNumber(3) var shortName: String?, + @ProtoNumber(4) var website: String, + @ProtoNumber(5) var signingKeyFingerprint: String, +) + +val backupExtensionReposMapper = { repo: ExtensionRepo -> + BackupExtensionRepos( + baseUrl = repo.baseUrl, + name = repo.name, + shortName = repo.shortName, + website = repo.website, + signingKeyFingerprint = repo.signingKeyFingerprint, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index 29dc04533..c4275f96f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -5,10 +5,12 @@ import android.net.Uri import eu.kanade.tachiyomi.data.backup.BackupDecoder import eu.kanade.tachiyomi.data.backup.BackupNotifier import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesRestorer +import eu.kanade.tachiyomi.data.backup.restore.restorers.ExtensionRepoRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer import eu.kanade.tachiyomi.util.system.createFileInCacheDir @@ -30,6 +32,7 @@ class BackupRestorer( private val categoriesRestorer: CategoriesRestorer = CategoriesRestorer(), private val preferenceRestorer: PreferenceRestorer = PreferenceRestorer(context), + private val extensionRepoRestorer: ExtensionRepoRestorer = ExtensionRepoRestorer(), private val mangaRestorer: MangaRestorer = MangaRestorer(), ) { @@ -76,6 +79,9 @@ class BackupRestorer( if (options.appSettings) { restoreAmount += 1 } + if (options.extensionRepoSettings) { + restoreAmount += backup.backupExtensionRepo.size + } if (options.sourceSettings) { restoreAmount += 1 } @@ -93,6 +99,9 @@ class BackupRestorer( if (options.libraryEntries) { restoreManga(backup.backupManga, if (options.categories) backup.backupCategories else emptyList()) } + if (options.extensionRepoSettings) { + restoreExtensionRepos(backup.backupExtensionRepo) + } // TODO: optionally trigger online library + tracker update } @@ -157,6 +166,29 @@ class BackupRestorer( ) } + private fun CoroutineScope.restoreExtensionRepos( + backupExtensionRepo: List + ) = launch { + backupExtensionRepo + .forEach { + ensureActive() + + try { + extensionRepoRestorer(it) + } catch (e: Exception) { + errors.add(Date() to "Error Adding Repo: ${it.name} : ${e.message}") + } + + restoreProgress += 1 + notifier.showRestoreProgress( + context.stringResource(MR.strings.extensionRepo_settings), + restoreProgress, + restoreAmount, + isSync, + ) + } + } + private fun writeErrorLog(): File { try { if (errors.isNotEmpty()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt index 30c34200d..933bf0568 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt @@ -8,17 +8,19 @@ data class RestoreOptions( val libraryEntries: Boolean = true, val categories: Boolean = true, val appSettings: Boolean = true, - val sourceSettings: Boolean = true, + val extensionRepoSettings: Boolean = true, + val sourceSettings: Boolean = true ) { fun asBooleanArray() = booleanArrayOf( libraryEntries, categories, appSettings, - sourceSettings, + extensionRepoSettings, + sourceSettings ) - fun canRestore() = libraryEntries || categories || appSettings || sourceSettings + fun canRestore() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings companion object { val options = persistentListOf( @@ -37,6 +39,11 @@ data class RestoreOptions( getter = RestoreOptions::appSettings, setter = { options, enabled -> options.copy(appSettings = enabled) }, ), + Entry( + label = MR.strings.extensionRepo_settings, + getter = RestoreOptions::extensionRepoSettings, + setter = { options, enabled -> options.copy(extensionRepoSettings = enabled) }, + ), Entry( label = MR.strings.source_settings, getter = RestoreOptions::sourceSettings, @@ -48,7 +55,8 @@ data class RestoreOptions( libraryEntries = array[0], categories = array[1], appSettings = array[2], - sourceSettings = array[3], + extensionRepoSettings = array[3], + sourceSettings = array[4], ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt new file mode 100644 index 000000000..1dd0da491 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.data.backup.restore.restorers + +import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos +import mihon.domain.extensionrepo.interactor.GetExtensionRepo +import tachiyomi.data.DatabaseHandler +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class ExtensionRepoRestorer( + private val handler: DatabaseHandler = Injekt.get(), + private val getExtensionRepos: GetExtensionRepo = Injekt.get() +) { + + suspend operator fun invoke( + backupRepo: BackupExtensionRepos, + ) { + val dbRepos = getExtensionRepos.getAll() + val existingReposBySHA = dbRepos.associateBy { it.signingKeyFingerprint } + val existingReposByUrl = dbRepos.associateBy { it.baseUrl } + + val urlExists = existingReposByUrl[backupRepo.baseUrl] + val shaExists = existingReposBySHA[backupRepo.signingKeyFingerprint] + + if (urlExists != null && urlExists.signingKeyFingerprint != backupRepo.signingKeyFingerprint) { + error("Already Exists with different signing key fingerprint") + } else if (shaExists != null) { + error("${shaExists.name} has the same signing key fingerprint") + } else { + handler.await { + extension_reposQueries.insert( + backupRepo.baseUrl, + backupRepo.name, + backupRepo.shortName, + backupRepo.website, + backupRepo.signingKeyFingerprint + ) + } + } + } +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 73f7d4a6e..90719fb92 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -529,6 +529,7 @@ What do you want to backup? App settings Source settings + Extension Repos Include sensitive settings (e.g., tracker login tokens) Creating backup Backup failed