Migrate to XML Settings from Preferences (#722)
* Migrate to XML Settings from Preferences * Lint
This commit is contained in:
+62
-20
@@ -9,7 +9,8 @@ package xyz.nulldev.androidcompat.io.sharedprefs
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.russhwolf.settings.ExperimentalSettingsApi
|
||||
import com.russhwolf.settings.PreferencesSettings
|
||||
import com.russhwolf.settings.PropertiesSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.russhwolf.settings.serialization.decodeValue
|
||||
import com.russhwolf.settings.serialization.decodeValueOrNull
|
||||
import com.russhwolf.settings.serialization.encodeValue
|
||||
@@ -17,14 +18,52 @@ import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.builtins.SetSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import java.util.prefs.PreferenceChangeListener
|
||||
import java.util.prefs.Preferences
|
||||
import mu.KotlinLogging
|
||||
import xyz.nulldev.ts.config.ApplicationRootDir
|
||||
import java.util.Properties
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createParentDirectories
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.inputStream
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
|
||||
class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key")
|
||||
private val preferences = PreferencesSettings(javaPreferences)
|
||||
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, PreferenceChangeListener>()
|
||||
companion object {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
}
|
||||
|
||||
private val file = Path(ApplicationRootDir, "settings", "$key.xml")
|
||||
private val properties =
|
||||
Properties().also { properties ->
|
||||
try {
|
||||
if (file.exists()) {
|
||||
file.inputStream().use { properties.loadFromXML(it) }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Error loading settings from $key" }
|
||||
}
|
||||
}
|
||||
private val preferences =
|
||||
PropertiesSettings(
|
||||
properties,
|
||||
onModify = { properties ->
|
||||
try {
|
||||
if (properties.isEmpty) {
|
||||
file.deleteIfExists()
|
||||
} else {
|
||||
file.createParentDirectories()
|
||||
file.outputStream().use {
|
||||
properties.storeToXML(it, null)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Error saving settings in $key" }
|
||||
}
|
||||
},
|
||||
)
|
||||
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, (String) -> Unit>()
|
||||
|
||||
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
|
||||
override fun getAll(): MutableMap<String, *> {
|
||||
@@ -90,17 +129,21 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
}
|
||||
|
||||
override fun edit(): SharedPreferences.Editor {
|
||||
return Editor(preferences)
|
||||
return Editor(preferences) { key ->
|
||||
listeners.forEach { (_, listener) ->
|
||||
listener(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Editor(private val preferences: PreferencesSettings) : SharedPreferences.Editor {
|
||||
class Editor(private val preferences: Settings, private val notify: (String) -> Unit) : SharedPreferences.Editor {
|
||||
private val actions = mutableListOf<Action>()
|
||||
|
||||
private sealed class Action {
|
||||
data class Add(val key: String, val value: Any) : Action()
|
||||
|
||||
data class Remove(val key: String) : Action()
|
||||
object Clear : Action()
|
||||
data object Clear : Action()
|
||||
}
|
||||
|
||||
override fun putString(
|
||||
@@ -182,7 +225,7 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
actions.forEach {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (it) {
|
||||
is Action.Add ->
|
||||
is Action.Add -> {
|
||||
when (val value = it.value) {
|
||||
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set<String>)
|
||||
is String -> preferences.putString(it.key, value)
|
||||
@@ -192,6 +235,8 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
is Double -> preferences.putDouble(it.key, value)
|
||||
is Boolean -> preferences.putBoolean(it.key, value)
|
||||
}
|
||||
notify(it.key)
|
||||
}
|
||||
is Action.Remove -> {
|
||||
preferences.remove(it.key)
|
||||
/**
|
||||
@@ -205,6 +250,8 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
preferences.remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
notify(it.key)
|
||||
}
|
||||
Action.Clear -> preferences.clear()
|
||||
}
|
||||
@@ -213,23 +260,18 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
}
|
||||
|
||||
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||
val javaListener =
|
||||
PreferenceChangeListener {
|
||||
listener.onSharedPreferenceChanged(this, it.key)
|
||||
}
|
||||
val javaListener: (String) -> Unit = {
|
||||
listener.onSharedPreferenceChanged(this, it)
|
||||
}
|
||||
listeners[listener] = javaListener
|
||||
javaPreferences.addPreferenceChangeListener(javaListener)
|
||||
}
|
||||
|
||||
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||
val registeredListener = listeners.remove(listener)
|
||||
if (registeredListener != null) {
|
||||
javaPreferences.removePreferenceChangeListener(registeredListener)
|
||||
}
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
fun deleteAll(): Boolean {
|
||||
javaPreferences.removeNode()
|
||||
preferences.clear()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -7,6 +7,8 @@ package suwayomi.tachidesk.manga.impl.backup.proto
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import mu.KotlinLogging
|
||||
@@ -38,13 +40,14 @@ import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
import suwayomi.tachidesk.util.HAScheduler
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.prefs.Preferences
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
object ProtoBackupExport : ProtoBackupBase() {
|
||||
@@ -52,7 +55,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
private var backupSchedulerJobId: String = ""
|
||||
private const val LAST_AUTOMATED_BACKUP_KEY = "lastAutomatedBackupKey"
|
||||
private val preferences = Preferences.userNodeForPackage(ProtoBackupExport::class.java)
|
||||
private val preferences = Injekt.get<Application>().getSharedPreferences("manga/impl/backup/proto", Context.MODE_PRIVATE)
|
||||
|
||||
init {
|
||||
serverConfig.subscribeTo(
|
||||
@@ -77,7 +80,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
val task = {
|
||||
cleanupAutomatedBackups()
|
||||
createAutomatedBackup()
|
||||
preferences.putLong(LAST_AUTOMATED_BACKUP_KEY, System.currentTimeMillis())
|
||||
preferences.edit().putLong(LAST_AUTOMATED_BACKUP_KEY, System.currentTimeMillis()).apply()
|
||||
}
|
||||
|
||||
val (hour, minute) = serverConfig.backupTime.value.split(":").map { it.toInt() }
|
||||
|
||||
@@ -64,7 +64,7 @@ object DownloadManager {
|
||||
Injekt.get<Application>().getSharedPreferences(DownloadManager::class.jvmName, Context.MODE_PRIVATE)
|
||||
|
||||
private fun loadDownloadQueue(): List<Int> {
|
||||
return sharedPreferences.getStringSet(DOWNLOAD_QUEUE_KEY, emptySet())?.mapNotNull { it.toInt() } ?: emptyList()
|
||||
return sharedPreferences.getStringSet(DOWNLOAD_QUEUE_KEY, emptySet())?.mapNotNull { it.toInt() }.orEmpty()
|
||||
}
|
||||
|
||||
private fun saveDownloadQueue() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package suwayomi.tachidesk.manga.impl.update
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -27,9 +29,10 @@ import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
import suwayomi.tachidesk.util.HAScheduler
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.prefs.Preferences
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
@@ -48,7 +51,7 @@ class Updater : IUpdater {
|
||||
|
||||
private val lastUpdateKey = "lastUpdateKey"
|
||||
private val lastAutomatedUpdateKey = "lastAutomatedUpdateKey"
|
||||
private val preferences = Preferences.userNodeForPackage(Updater::class.java)
|
||||
private val preferences = Injekt.get<Application>().getSharedPreferences("manga/impl/update", Context.MODE_PRIVATE)
|
||||
|
||||
private var currentUpdateTaskId = ""
|
||||
|
||||
@@ -80,7 +83,7 @@ class Updater : IUpdater {
|
||||
|
||||
private fun autoUpdateTask() {
|
||||
val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0)
|
||||
preferences.putLong(lastAutomatedUpdateKey, System.currentTimeMillis())
|
||||
preferences.edit().putLong(lastAutomatedUpdateKey, System.currentTimeMillis()).apply()
|
||||
|
||||
if (status.value.running) {
|
||||
logger.debug { "Global update is already in progress" }
|
||||
@@ -178,7 +181,7 @@ class Updater : IUpdater {
|
||||
clear: Boolean?,
|
||||
forceAll: Boolean,
|
||||
) {
|
||||
preferences.putLong(lastUpdateKey, System.currentTimeMillis())
|
||||
preferences.edit().putLong(lastUpdateKey, System.currentTimeMillis()).apply()
|
||||
|
||||
if (clear == true) {
|
||||
reset()
|
||||
|
||||
@@ -7,6 +7,8 @@ package suwayomi.tachidesk.server
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import ch.qos.logback.classic.Level
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import eu.kanade.tachiyomi.App
|
||||
@@ -31,6 +33,8 @@ import suwayomi.tachidesk.server.database.databaseUp
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
import suwayomi.tachidesk.server.util.AppMutex.handleAppMutex
|
||||
import suwayomi.tachidesk.server.util.SystemTray
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import xyz.nulldev.androidcompat.AndroidCompat
|
||||
import xyz.nulldev.androidcompat.AndroidCompatInitializer
|
||||
import xyz.nulldev.ts.config.ApplicationRootDir
|
||||
@@ -42,6 +46,9 @@ import xyz.nulldev.ts.config.setLogLevelFor
|
||||
import java.io.File
|
||||
import java.security.Security
|
||||
import java.util.Locale
|
||||
import java.util.prefs.Preferences
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@@ -211,6 +218,10 @@ fun applicationSetup() {
|
||||
}
|
||||
}, ignoreInitialValue = false)
|
||||
|
||||
val preferences = Preferences.userRoot().node("suwayomi/tachidesk")
|
||||
migratePreferences(null, preferences)
|
||||
preferences.removeNode()
|
||||
|
||||
// Disable jetty's logging
|
||||
System.setProperty("org.eclipse.jetty.util.log.announce", "false")
|
||||
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog")
|
||||
@@ -250,3 +261,36 @@ fun applicationSetup() {
|
||||
// start DownloadManager and restore + resume downloads
|
||||
DownloadManager.restoreAndResumeDownloads()
|
||||
}
|
||||
|
||||
fun migratePreferences(
|
||||
parent: String?,
|
||||
rootNode: Preferences,
|
||||
) {
|
||||
val subNodes = rootNode.childrenNames()
|
||||
|
||||
for (subNodeName in subNodes) {
|
||||
val subNode = rootNode.node(subNodeName)
|
||||
val key =
|
||||
if (parent != null) {
|
||||
"$parent/$subNodeName"
|
||||
} else {
|
||||
subNodeName
|
||||
}
|
||||
val preferences = Injekt.get<Application>().getSharedPreferences(key, Context.MODE_PRIVATE)
|
||||
|
||||
val items: Map<String, String?> =
|
||||
subNode.keys().associateWith {
|
||||
subNode[it, null]?.ifBlank { null }
|
||||
}
|
||||
|
||||
preferences.edit().apply {
|
||||
items.forEach { (key, value) ->
|
||||
if (value != null) {
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
}.apply()
|
||||
|
||||
migratePreferences(key, subNode) // Recursively migrate sub-level nodes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ package suwayomi.tachidesk.server.util
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
@@ -45,6 +47,8 @@ import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
import suwayomi.tachidesk.util.HAScheduler
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
@@ -53,7 +57,6 @@ import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.util.Date
|
||||
import java.util.prefs.Preferences
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -124,7 +127,7 @@ object WebInterfaceManager {
|
||||
|
||||
private const val LAST_WEBUI_UPDATE_CHECK_KEY = "lastWebUIUpdateCheckKey"
|
||||
|
||||
private val preferences = Preferences.userNodeForPackage(WebInterfaceManager::class.java)
|
||||
private val preferences = Injekt.get<Application>().getSharedPreferences("server/util", Context.MODE_PRIVATE)
|
||||
private var currentUpdateTaskId: String = ""
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
@@ -326,7 +329,7 @@ object WebInterfaceManager {
|
||||
}
|
||||
|
||||
private suspend fun checkForUpdate() {
|
||||
preferences.putLong(LAST_WEBUI_UPDATE_CHECK_KEY, System.currentTimeMillis())
|
||||
preferences.edit().putLong(LAST_WEBUI_UPDATE_CHECK_KEY, System.currentTimeMillis()).apply()
|
||||
val localVersion = getLocalVersion()
|
||||
|
||||
if (!isUpdateAvailable(localVersion).second) {
|
||||
|
||||
Reference in New Issue
Block a user