Add ability to create manual backups with private preferences too
This commit is contained in:
parent
8735836498
commit
ccec5c3efe
9 changed files with 65 additions and 77 deletions
|
@ -154,14 +154,6 @@ private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val options: BackupOptions = BackupOptions(
|
val options: BackupOptions = BackupOptions(),
|
||||||
libraryEntries = true,
|
|
||||||
categories = true,
|
|
||||||
chapters = true,
|
|
||||||
tracking = true,
|
|
||||||
history = true,
|
|
||||||
appSettings = false,
|
|
||||||
sourceSettings = false,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.util.lang.asBooleanArray
|
||||||
|
import eu.kanade.tachiyomi.util.lang.asDataClass
|
||||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
import eu.kanade.tachiyomi.util.system.isRunning
|
import eu.kanade.tachiyomi.util.system.isRunning
|
||||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||||
|
@ -47,9 +49,8 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
||||||
|
|
||||||
setForegroundSafely()
|
setForegroundSafely()
|
||||||
|
|
||||||
val options = inputData.getBooleanArray(OPTIONS_KEY)
|
val options: BackupOptions = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()
|
||||||
?.let { BackupOptions.fromBooleanArray(it) }
|
?: BackupOptions()
|
||||||
?: BackupOptions.AutomaticDefaults
|
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val location = BackupCreator(context, isAutoBackup).backup(uri, options)
|
val location = BackupCreator(context, isAutoBackup).backup(uri, options)
|
||||||
|
@ -118,7 +119,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
||||||
val inputData = workDataOf(
|
val inputData = workDataOf(
|
||||||
IS_AUTO_BACKUP_KEY to false,
|
IS_AUTO_BACKUP_KEY to false,
|
||||||
LOCATION_URI_KEY to uri.toString(),
|
LOCATION_URI_KEY to uri.toString(),
|
||||||
OPTIONS_KEY to options.toBooleanArray(),
|
OPTIONS_KEY to options.asBooleanArray(),
|
||||||
)
|
)
|
||||||
val request = OneTimeWorkRequestBuilder<BackupCreateJob>()
|
val request = OneTimeWorkRequestBuilder<BackupCreateJob>()
|
||||||
.addTag(TAG_MANUAL)
|
.addTag(TAG_MANUAL)
|
||||||
|
|
|
@ -131,13 +131,13 @@ class BackupCreator(
|
||||||
private fun backupAppPreferences(options: BackupOptions): List<BackupPreference> {
|
private fun backupAppPreferences(options: BackupOptions): List<BackupPreference> {
|
||||||
if (!options.appSettings) return emptyList()
|
if (!options.appSettings) return emptyList()
|
||||||
|
|
||||||
return preferenceBackupCreator.backupAppPreferences()
|
return preferenceBackupCreator.backupAppPreferences(includePrivatePreferences = options.privateSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun backupSourcePreferences(options: BackupOptions): List<BackupSourcePreferences> {
|
private fun backupSourcePreferences(options: BackupOptions): List<BackupSourcePreferences> {
|
||||||
if (!options.sourceSettings) return emptyList()
|
if (!options.sourceSettings) return emptyList()
|
||||||
|
|
||||||
return preferenceBackupCreator.backupSourcePreferences()
|
return preferenceBackupCreator.backupSourcePreferences(includePrivatePreferences = options.privateSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -12,75 +12,52 @@ data class BackupOptions(
|
||||||
val history: Boolean = true,
|
val history: Boolean = true,
|
||||||
val appSettings: Boolean = true,
|
val appSettings: Boolean = true,
|
||||||
val sourceSettings: Boolean = true,
|
val sourceSettings: Boolean = true,
|
||||||
|
val privateSettings: Boolean = false,
|
||||||
) {
|
) {
|
||||||
fun toBooleanArray() = booleanArrayOf(
|
|
||||||
libraryEntries,
|
|
||||||
categories,
|
|
||||||
chapters,
|
|
||||||
tracking,
|
|
||||||
history,
|
|
||||||
appSettings,
|
|
||||||
sourceSettings,
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val AutomaticDefaults = BackupOptions(
|
val entries = persistentListOf(
|
||||||
libraryEntries = true,
|
Entry(
|
||||||
categories = true,
|
|
||||||
chapters = true,
|
|
||||||
tracking = true,
|
|
||||||
history = true,
|
|
||||||
appSettings = true,
|
|
||||||
sourceSettings = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun fromBooleanArray(booleanArray: BooleanArray) = BackupOptions(
|
|
||||||
libraryEntries = booleanArray[0],
|
|
||||||
categories = booleanArray[1],
|
|
||||||
chapters = booleanArray[2],
|
|
||||||
tracking = booleanArray[3],
|
|
||||||
history = booleanArray[4],
|
|
||||||
appSettings = booleanArray[5],
|
|
||||||
sourceSettings = booleanArray[6],
|
|
||||||
)
|
|
||||||
|
|
||||||
val entries = persistentListOf<BackupOptionEntry>(
|
|
||||||
BackupOptionEntry(
|
|
||||||
label = MR.strings.categories,
|
label = MR.strings.categories,
|
||||||
getter = BackupOptions::categories,
|
getter = BackupOptions::categories,
|
||||||
setter = { options, enabled -> options.copy(categories = enabled) },
|
setter = { options, enabled -> options.copy(categories = enabled) },
|
||||||
),
|
),
|
||||||
BackupOptionEntry(
|
Entry(
|
||||||
label = MR.strings.chapters,
|
label = MR.strings.chapters,
|
||||||
getter = BackupOptions::chapters,
|
getter = BackupOptions::chapters,
|
||||||
setter = { options, enabled -> options.copy(chapters = enabled) },
|
setter = { options, enabled -> options.copy(chapters = enabled) },
|
||||||
),
|
),
|
||||||
BackupOptionEntry(
|
Entry(
|
||||||
label = MR.strings.track,
|
label = MR.strings.track,
|
||||||
getter = BackupOptions::tracking,
|
getter = BackupOptions::tracking,
|
||||||
setter = { options, enabled -> options.copy(tracking = enabled) },
|
setter = { options, enabled -> options.copy(tracking = enabled) },
|
||||||
),
|
),
|
||||||
BackupOptionEntry(
|
Entry(
|
||||||
label = MR.strings.history,
|
label = MR.strings.history,
|
||||||
getter = BackupOptions::history,
|
getter = BackupOptions::history,
|
||||||
setter = { options, enabled -> options.copy(history = enabled) },
|
setter = { options, enabled -> options.copy(history = enabled) },
|
||||||
),
|
),
|
||||||
BackupOptionEntry(
|
Entry(
|
||||||
label = MR.strings.app_settings,
|
label = MR.strings.app_settings,
|
||||||
getter = BackupOptions::appSettings,
|
getter = BackupOptions::appSettings,
|
||||||
setter = { options, enabled -> options.copy(appSettings = enabled) },
|
setter = { options, enabled -> options.copy(appSettings = enabled) },
|
||||||
),
|
),
|
||||||
BackupOptionEntry(
|
Entry(
|
||||||
label = MR.strings.source_settings,
|
label = MR.strings.source_settings,
|
||||||
getter = BackupOptions::sourceSettings,
|
getter = BackupOptions::sourceSettings,
|
||||||
setter = { options, enabled -> options.copy(sourceSettings = enabled) },
|
setter = { options, enabled -> options.copy(sourceSettings = enabled) },
|
||||||
),
|
),
|
||||||
|
Entry(
|
||||||
|
label = MR.strings.private_settings,
|
||||||
|
getter = BackupOptions::privateSettings,
|
||||||
|
setter = { options, enabled -> options.copy(privateSettings = enabled) },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data class BackupOptionEntry(
|
data class Entry(
|
||||||
val label: StringResource,
|
val label: StringResource,
|
||||||
val getter: (BackupOptions) -> Boolean,
|
val getter: (BackupOptions) -> Boolean,
|
||||||
val setter: (BackupOptions, Boolean) -> BackupOptions,
|
val setter: (BackupOptions, Boolean) -> BackupOptions,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -22,26 +22,27 @@ class PreferenceBackupCreator(
|
||||||
private val preferenceStore: PreferenceStore = Injekt.get(),
|
private val preferenceStore: PreferenceStore = Injekt.get(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun backupAppPreferences(): List<BackupPreference> {
|
fun backupAppPreferences(includePrivatePreferences: Boolean): List<BackupPreference> {
|
||||||
return preferenceStore.getAll().toBackupPreferences()
|
return preferenceStore.getAll().toBackupPreferences()
|
||||||
|
.withPrivatePreferences(includePrivatePreferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backupSourcePreferences(): List<BackupSourcePreferences> {
|
fun backupSourcePreferences(includePrivatePreferences: Boolean): List<BackupSourcePreferences> {
|
||||||
return sourceManager.getCatalogueSources()
|
return sourceManager.getCatalogueSources()
|
||||||
.filterIsInstance<ConfigurableSource>()
|
.filterIsInstance<ConfigurableSource>()
|
||||||
.map {
|
.map {
|
||||||
BackupSourcePreferences(
|
BackupSourcePreferences(
|
||||||
it.preferenceKey(),
|
it.preferenceKey(),
|
||||||
it.sourcePreferences().all.toBackupPreferences(),
|
it.sourcePreferences().all.toBackupPreferences()
|
||||||
|
.withPrivatePreferences(includePrivatePreferences),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
|
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
|
||||||
return this.filterKeys {
|
return this
|
||||||
!Preference.isAppState(it) && !Preference.isPrivate(it)
|
.filterKeys { !Preference.isAppState(it) }
|
||||||
}
|
|
||||||
.mapNotNull { (key, value) ->
|
.mapNotNull { (key, value) ->
|
||||||
when (value) {
|
when (value) {
|
||||||
is Int -> BackupPreference(key, IntPreferenceValue(value))
|
is Int -> BackupPreference(key, IntPreferenceValue(value))
|
||||||
|
@ -56,4 +57,11 @@ class PreferenceBackupCreator(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<BackupPreference>.withPrivatePreferences(include: Boolean) =
|
||||||
|
if (include) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
this.filter { !Preference.isPrivate(it.key) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ import androidx.work.WorkerParameters
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.util.lang.asBooleanArray
|
||||||
|
import eu.kanade.tachiyomi.util.lang.asDataClass
|
||||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
import eu.kanade.tachiyomi.util.system.isRunning
|
import eu.kanade.tachiyomi.util.system.isRunning
|
||||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||||
|
@ -30,8 +32,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
|
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
|
||||||
val options = inputData.getBooleanArray(OPTIONS_KEY)
|
val options: RestoreOptions? = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()
|
||||||
?.let { RestoreOptions.fromBooleanArray(it) }
|
|
||||||
|
|
||||||
if (uri == null || options == null) {
|
if (uri == null || options == null) {
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
|
@ -84,7 +85,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
||||||
val inputData = workDataOf(
|
val inputData = workDataOf(
|
||||||
LOCATION_URI_KEY to uri.toString(),
|
LOCATION_URI_KEY to uri.toString(),
|
||||||
SYNC_KEY to sync,
|
SYNC_KEY to sync,
|
||||||
OPTIONS_KEY to options.toBooleanArray(),
|
OPTIONS_KEY to options.asBooleanArray(),
|
||||||
)
|
)
|
||||||
val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
|
val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
|
|
|
@ -4,14 +4,4 @@ data class RestoreOptions(
|
||||||
val appSettings: Boolean = true,
|
val appSettings: Boolean = true,
|
||||||
val sourceSettings: Boolean = true,
|
val sourceSettings: Boolean = true,
|
||||||
val library: Boolean = true,
|
val library: Boolean = true,
|
||||||
) {
|
)
|
||||||
fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromBooleanArray(booleanArray: BooleanArray) = RestoreOptions(
|
|
||||||
appSettings = booleanArray[0],
|
|
||||||
sourceSettings = booleanArray[1],
|
|
||||||
library = booleanArray[2],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package eu.kanade.tachiyomi.util.lang
|
||||||
|
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
|
||||||
|
fun <T : Any> T.asBooleanArray(): BooleanArray {
|
||||||
|
return this::class.declaredMemberProperties
|
||||||
|
.filterIsInstance<KProperty1<T, Boolean>>()
|
||||||
|
.map { it.get(this) }
|
||||||
|
.toBooleanArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> BooleanArray.asDataClass(): T {
|
||||||
|
val properties = T::class.declaredMemberProperties.filterIsInstance<KProperty1<T, Boolean>>()
|
||||||
|
require(properties.size == this.size) { "Boolean array size does not match data class property count" }
|
||||||
|
return T::class.primaryConstructor!!.call(*this.toTypedArray())
|
||||||
|
}
|
|
@ -504,6 +504,7 @@
|
||||||
<string name="backup_choice">What do you want to backup?</string>
|
<string name="backup_choice">What do you want to backup?</string>
|
||||||
<string name="app_settings">App settings</string>
|
<string name="app_settings">App settings</string>
|
||||||
<string name="source_settings">Source settings</string>
|
<string name="source_settings">Source settings</string>
|
||||||
|
<string name="private_settings">Include sensitive settings (e.g., tracker login tokens)</string>
|
||||||
<string name="creating_backup">Creating backup</string>
|
<string name="creating_backup">Creating backup</string>
|
||||||
<string name="creating_backup_error">Backup failed</string>
|
<string name="creating_backup_error">Backup failed</string>
|
||||||
<string name="missing_storage_permission">Storage permissions not granted</string>
|
<string name="missing_storage_permission">Storage permissions not granted</string>
|
||||||
|
|
Reference in a new issue