Skip to content

Commit

Permalink
Add ability to create manual backups with private preferences too
Browse files Browse the repository at this point in the history
  • Loading branch information
arkon committed Dec 28, 2023
1 parent 8735836 commit ccec5c3
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,6 @@ private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel

@Immutable
data class State(
val options: BackupOptions = BackupOptions(
libraryEntries = true,
categories = true,
chapters = true,
tracking = true,
history = true,
appSettings = false,
sourceSettings = false,
),
val options: BackupOptions = BackupOptions(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
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.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely
Expand Down Expand Up @@ -47,9 +49,8 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete

setForegroundSafely()

val options = inputData.getBooleanArray(OPTIONS_KEY)
?.let { BackupOptions.fromBooleanArray(it) }
?: BackupOptions.AutomaticDefaults
val options: BackupOptions = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()
?: BackupOptions()

return try {
val location = BackupCreator(context, isAutoBackup).backup(uri, options)
Expand Down Expand Up @@ -118,7 +119,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
val inputData = workDataOf(
IS_AUTO_BACKUP_KEY to false,
LOCATION_URI_KEY to uri.toString(),
OPTIONS_KEY to options.toBooleanArray(),
OPTIONS_KEY to options.asBooleanArray(),
)
val request = OneTimeWorkRequestBuilder<BackupCreateJob>()
.addTag(TAG_MANUAL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ class BackupCreator(
private fun backupAppPreferences(options: BackupOptions): List<BackupPreference> {
if (!options.appSettings) return emptyList()

return preferenceBackupCreator.backupAppPreferences()
return preferenceBackupCreator.backupAppPreferences(includePrivatePreferences = options.privateSettings)
}

private fun backupSourcePreferences(options: BackupOptions): List<BackupSourcePreferences> {
if (!options.sourceSettings) return emptyList()

return preferenceBackupCreator.backupSourcePreferences()
return preferenceBackupCreator.backupSourcePreferences(includePrivatePreferences = options.privateSettings)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,75 +12,52 @@ data class BackupOptions(
val history: Boolean = true,
val appSettings: Boolean = true,
val sourceSettings: Boolean = true,
val privateSettings: Boolean = false,
) {
fun toBooleanArray() = booleanArrayOf(
libraryEntries,
categories,
chapters,
tracking,
history,
appSettings,
sourceSettings,
)

companion object {
val AutomaticDefaults = BackupOptions(
libraryEntries = true,
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(
val entries = persistentListOf(
Entry(
label = MR.strings.categories,
getter = BackupOptions::categories,
setter = { options, enabled -> options.copy(categories = enabled) },
),
BackupOptionEntry(
Entry(
label = MR.strings.chapters,
getter = BackupOptions::chapters,
setter = { options, enabled -> options.copy(chapters = enabled) },
),
BackupOptionEntry(
Entry(
label = MR.strings.track,
getter = BackupOptions::tracking,
setter = { options, enabled -> options.copy(tracking = enabled) },
),
BackupOptionEntry(
Entry(
label = MR.strings.history,
getter = BackupOptions::history,
setter = { options, enabled -> options.copy(history = enabled) },
),
BackupOptionEntry(
Entry(
label = MR.strings.app_settings,
getter = BackupOptions::appSettings,
setter = { options, enabled -> options.copy(appSettings = enabled) },
),
BackupOptionEntry(
Entry(
label = MR.strings.source_settings,
getter = BackupOptions::sourceSettings,
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(
val label: StringResource,
val getter: (BackupOptions) -> Boolean,
val setter: (BackupOptions, Boolean) -> BackupOptions,
)
data class Entry(
val label: StringResource,
val getter: (BackupOptions) -> Boolean,
val setter: (BackupOptions, Boolean) -> BackupOptions,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,27 @@ class PreferenceBackupCreator(
private val preferenceStore: PreferenceStore = Injekt.get(),
) {

fun backupAppPreferences(): List<BackupPreference> {
fun backupAppPreferences(includePrivatePreferences: Boolean): List<BackupPreference> {
return preferenceStore.getAll().toBackupPreferences()
.withPrivatePreferences(includePrivatePreferences)
}

fun backupSourcePreferences(): List<BackupSourcePreferences> {
fun backupSourcePreferences(includePrivatePreferences: Boolean): List<BackupSourcePreferences> {
return sourceManager.getCatalogueSources()
.filterIsInstance<ConfigurableSource>()
.map {
BackupSourcePreferences(
it.preferenceKey(),
it.sourcePreferences().all.toBackupPreferences(),
it.sourcePreferences().all.toBackupPreferences()
.withPrivatePreferences(includePrivatePreferences),
)
}
}

@Suppress("UNCHECKED_CAST")
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
return this.filterKeys {
!Preference.isAppState(it) && !Preference.isPrivate(it)
}
return this
.filterKeys { !Preference.isAppState(it) }
.mapNotNull { (key, value) ->
when (value) {
is Int -> BackupPreference(key, IntPreferenceValue(value))
Expand All @@ -56,4 +57,11 @@ class PreferenceBackupCreator(
}
}
}

private fun List<BackupPreference>.withPrivatePreferences(include: Boolean) =
if (include) {
this
} else {
this.filter { !Preference.isPrivate(it.key) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import eu.kanade.tachiyomi.data.backup.BackupNotifier
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.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely
Expand All @@ -30,8 +32,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet

override suspend fun doWork(): Result {
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
val options = inputData.getBooleanArray(OPTIONS_KEY)
?.let { RestoreOptions.fromBooleanArray(it) }
val options: RestoreOptions? = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()

if (uri == null || options == null) {
return Result.failure()
Expand Down Expand Up @@ -84,7 +85,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
val inputData = workDataOf(
LOCATION_URI_KEY to uri.toString(),
SYNC_KEY to sync,
OPTIONS_KEY to options.toBooleanArray(),
OPTIONS_KEY to options.asBooleanArray(),
)
val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
.addTag(TAG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,4 @@ data class RestoreOptions(
val appSettings: Boolean = true,
val sourceSettings: 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],
)
}
}
)
Original file line number Diff line number Diff line change
@@ -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())
}
1 change: 1 addition & 0 deletions i18n/src/commonMain/resources/MR/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@
<string name="backup_choice">What do you want to backup?</string>
<string name="app_settings">App 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_error">Backup failed</string>
<string name="missing_storage_permission">Storage permissions not granted</string>
Expand Down

0 comments on commit ccec5c3

Please sign in to comment.