fix: 不再使用脆弱的反射进行备份与恢复
This commit is contained in:
@@ -25,7 +25,6 @@ import io.legado.app.utils.createFolderIfNotExist
|
||||
import io.legado.app.utils.defaultSharedPreferences
|
||||
import io.legado.app.utils.externalFiles
|
||||
import io.legado.app.utils.getFile
|
||||
import io.legado.app.utils.getSharedPreferences
|
||||
import io.legado.app.utils.isContentScheme
|
||||
import io.legado.app.utils.normalizeFileName
|
||||
import io.legado.app.utils.openOutputStream
|
||||
@@ -178,29 +177,29 @@ object Backup {
|
||||
.writeText(GSON.toJson(it))
|
||||
}
|
||||
currentCoroutineContext().ensureActive()
|
||||
appCtx.getSharedPreferences(backupPath, "config")?.let { sp ->
|
||||
sp.edit(commit = true) {
|
||||
appCtx.defaultSharedPreferences.all.forEach { (key, value) ->
|
||||
if (BackupConfig.keyIsNotIgnore(key)) {
|
||||
when (key) {
|
||||
PreferKey.webDavPassword -> {
|
||||
putString(key, aes.runCatching {
|
||||
encryptBase64(value.toString())
|
||||
}.getOrDefault(value.toString()))
|
||||
}
|
||||
val configMap = appCtx.defaultSharedPreferences.all
|
||||
val xmlBuilder = StringBuilder()
|
||||
xmlBuilder.append("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n")
|
||||
xmlBuilder.append("<map>\n")
|
||||
configMap.forEach { (key, value) ->
|
||||
if (BackupConfig.keyIsNotIgnore(key, true)) {
|
||||
val finalValue = if (key == PreferKey.webDavPassword) {
|
||||
aes.runCatching { encryptBase64(value.toString()) }.getOrDefault(value.toString())
|
||||
} else value
|
||||
|
||||
else -> when (value) {
|
||||
is Int -> putInt(key, value)
|
||||
is Boolean -> putBoolean(key, value)
|
||||
is Long -> putLong(key, value)
|
||||
is Float -> putFloat(key, value)
|
||||
is String -> putString(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
when (finalValue) {
|
||||
is String -> xmlBuilder.append(" <string name=\"$key\">${finalValue.replace("&", "&").replace("<", "<")}</string>\n")
|
||||
is Int -> xmlBuilder.append(" <int name=\"$key\" value=\"$finalValue\" />\n")
|
||||
is Long -> xmlBuilder.append(" <long name=\"$key\" value=\"$finalValue\" />\n")
|
||||
is Float -> xmlBuilder.append(" <float name=\"$key\" value=\"$finalValue\" />\n")
|
||||
is Boolean -> xmlBuilder.append(" <boolean name=\"$key\" value=\"$finalValue\" />\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlBuilder.append("</map>")
|
||||
FileUtils.createFileIfNotExist(backupPath + File.separator + "config.xml")
|
||||
.writeText(xmlBuilder.toString())
|
||||
|
||||
currentCoroutineContext().ensureActive()
|
||||
val zipFileName = getNowZipFileName()
|
||||
val paths = arrayListOf(*backupFileNames)
|
||||
|
||||
@@ -88,7 +88,35 @@ object BackupConfig {
|
||||
PreferKey.bgImage,
|
||||
PreferKey.bgImageBlurring,
|
||||
PreferKey.bgImageN,
|
||||
PreferKey.bgImageNBlurring
|
||||
PreferKey.bgImageNBlurring,
|
||||
PreferKey.themeColor,
|
||||
PreferKey.secondaryThemeColor,
|
||||
PreferKey.paletteStyle,
|
||||
PreferKey.materialVersion,
|
||||
PreferKey.composeEngine,
|
||||
PreferKey.customContrast,
|
||||
PreferKey.customMode,
|
||||
PreferKey.useMiuixMonet,
|
||||
PreferKey.containerOpacity,
|
||||
PreferKey.topBarOpacity,
|
||||
PreferKey.bottomBarOpacity,
|
||||
PreferKey.enableBlur,
|
||||
PreferKey.enableProgressiveBlur,
|
||||
PreferKey.topBarBlurRadius,
|
||||
PreferKey.bottomBarBlurRadius,
|
||||
PreferKey.topBarBlurAlpha,
|
||||
PreferKey.bottomBarBlurAlpha,
|
||||
PreferKey.bottomBarLensRadius,
|
||||
PreferKey.useFlexibleTopAppBar,
|
||||
PreferKey.cBackground,
|
||||
PreferKey.cBBackground,
|
||||
PreferKey.cNBackground,
|
||||
PreferKey.cNBBackground,
|
||||
PreferKey.enableDeepPersonalization,
|
||||
PreferKey.primaryTextColor,
|
||||
PreferKey.secondaryTextColor,
|
||||
PreferKey.themeBackgroundColor,
|
||||
PreferKey.labelContainerColor
|
||||
)
|
||||
|
||||
private val coverPrefKeys = arrayOf(
|
||||
@@ -100,9 +128,10 @@ object BackupConfig {
|
||||
PreferKey.coverShowAuthorN
|
||||
)
|
||||
|
||||
fun keyIsNotIgnore(key: String): Boolean {
|
||||
fun keyIsNotIgnore(key: String, isBackup: Boolean = false): Boolean {
|
||||
if (ignorePrefKeys.contains(key)) return false
|
||||
if (isBackup) return true
|
||||
return when {
|
||||
ignorePrefKeys.contains(key) -> false
|
||||
ignoreReadConfig && readPrefKeys.contains(key) -> false
|
||||
ignoreThemeConfig && themePrefKeys.contains(key) -> false
|
||||
ignoreCoverConfig && coverPrefKeys.contains(key) -> false
|
||||
@@ -116,17 +145,17 @@ object BackupConfig {
|
||||
|
||||
val ignoreReadConfig: Boolean
|
||||
get() = ignoreConfig[readConfigKey] == true
|
||||
private val ignoreThemeMode: Boolean
|
||||
val ignoreThemeMode: Boolean
|
||||
get() = ignoreConfig[PreferKey.themeMode] == true
|
||||
private val ignoreThemeConfig: Boolean
|
||||
val ignoreThemeConfig: Boolean
|
||||
get() = ignoreConfig[themeConfigKey] == true
|
||||
private val ignoreCoverConfig: Boolean
|
||||
val ignoreCoverConfig: Boolean
|
||||
get() = ignoreConfig[coverConfigKey] == true
|
||||
private val ignoreBookshelfLayout: Boolean
|
||||
val ignoreBookshelfLayout: Boolean
|
||||
get() = ignoreConfig[PreferKey.bookshelfLayout] == true
|
||||
private val ignoreShowRss: Boolean
|
||||
val ignoreShowRss: Boolean
|
||||
get() = ignoreConfig[PreferKey.showRss] == true
|
||||
private val ignoreThreadCount: Boolean
|
||||
val ignoreThreadCount: Boolean
|
||||
get() = ignoreConfig[PreferKey.threadCount] == true
|
||||
val ignoreLocalBook: Boolean
|
||||
get() = ignoreConfig[localBookKey] == true
|
||||
|
||||
@@ -48,7 +48,6 @@ import io.legado.app.utils.fromJsonArray
|
||||
import io.legado.app.utils.getPrefBoolean
|
||||
import io.legado.app.utils.getPrefInt
|
||||
import io.legado.app.utils.getPrefString
|
||||
import io.legado.app.utils.getSharedPreferences
|
||||
import io.legado.app.utils.isContentScheme
|
||||
import io.legado.app.utils.isJsonArray
|
||||
import io.legado.app.utils.openInputStream
|
||||
@@ -58,6 +57,8 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
@@ -273,17 +274,19 @@ object Restore : KoinComponent {
|
||||
AppLog.put("恢复直链上传出错\n${it.localizedMessage}", it)
|
||||
}
|
||||
//恢复主题配置
|
||||
File(path, OldThemeConfig.configFileName).takeIf {
|
||||
it.exists()
|
||||
}?.runCatching {
|
||||
FileUtils.delete(OldThemeConfig.configFilePath)
|
||||
copyTo(File(OldThemeConfig.configFilePath))
|
||||
OldThemeConfig.upConfig()
|
||||
}?.onFailure {
|
||||
AppLog.put("恢复主题出错\n${it.localizedMessage}", it)
|
||||
if (!BackupConfig.ignoreThemeConfig) {
|
||||
File(path, OldThemeConfig.configFileName).takeIf {
|
||||
it.exists()
|
||||
}?.runCatching {
|
||||
FileUtils.delete(OldThemeConfig.configFilePath)
|
||||
copyTo(File(OldThemeConfig.configFilePath))
|
||||
OldThemeConfig.upConfig()
|
||||
}?.onFailure {
|
||||
AppLog.put("恢复主题出错\n${it.localizedMessage}", it)
|
||||
}
|
||||
}
|
||||
File(path, BookCover.configFileName).takeIf {
|
||||
it.exists()
|
||||
it.exists() && !BackupConfig.ignoreCoverConfig
|
||||
}?.runCatching {
|
||||
val json = readText()
|
||||
BookCover.saveCoverRule(json)
|
||||
@@ -311,43 +314,19 @@ object Restore : KoinComponent {
|
||||
AppLog.put("恢复阅读界面出错\n${it.localizedMessage}", it)
|
||||
}
|
||||
}
|
||||
//AppWebDav.downBgs()
|
||||
appCtx.getSharedPreferences(path, "config")?.all?.let { map ->
|
||||
val finalMap = mutableMapOf<String, Any>()
|
||||
appCtx.defaultSharedPreferences.edit {
|
||||
map.forEach { (key, value) ->
|
||||
if (BackupConfig.keyIsNotIgnore(key)) {
|
||||
when (key) {
|
||||
PreferKey.webDavPassword -> {
|
||||
val password = kotlin.runCatching {
|
||||
aes.decryptStr(value.toString())
|
||||
}.getOrNull() ?: let {
|
||||
if (appCtx.getPrefString(PreferKey.webDavPassword).isNullOrBlank()) {
|
||||
value.toString()
|
||||
} else null
|
||||
}
|
||||
password?.let {
|
||||
putString(key, it)
|
||||
finalMap[key] = it
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
when (value) {
|
||||
is Int -> { putInt(key, value); finalMap[key] = value }
|
||||
is Boolean -> { putBoolean(key, value); finalMap[key] = value }
|
||||
is Long -> { putLong(key, value); finalMap[key] = value }
|
||||
is Float -> { putFloat(key, value); finalMap[key] = value }
|
||||
is String -> { putString(key, value); finalMap[key] = value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 恢复配置文件 (手动解析 XML,替代反射逻辑)
|
||||
val configFile = File(path, "config.xml")
|
||||
if (configFile.exists()) {
|
||||
try {
|
||||
val map = readXmlToMap(configFile)
|
||||
if (map.isNotEmpty()) {
|
||||
applyConfigMap(map, aes)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("恢复配置 XML 出错\n${e.localizedMessage}", e)
|
||||
}
|
||||
// 同步恢复到 DataStore
|
||||
settingsRepository.batchPutFromMap(finalMap)
|
||||
}
|
||||
|
||||
ReadBookConfig.apply {
|
||||
comicStyleSelect = appCtx.getPrefInt(PreferKey.comicStyleSelect)
|
||||
readStyleSelect = appCtx.getPrefInt(PreferKey.readStyleSelect)
|
||||
@@ -366,6 +345,81 @@ object Restore : KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun applyConfigMap(map: Map<String, Any?>, aes: BackupAES) {
|
||||
val finalMap = mutableMapOf<String, Any>()
|
||||
appCtx.defaultSharedPreferences.edit {
|
||||
map.forEach { (key, value) ->
|
||||
if (BackupConfig.keyIsNotIgnore(key)) {
|
||||
when (key) {
|
||||
PreferKey.webDavPassword -> {
|
||||
val password = kotlin.runCatching {
|
||||
aes.decryptStr(value.toString())
|
||||
}.getOrNull() ?: let {
|
||||
if (appCtx.getPrefString(PreferKey.webDavPassword).isNullOrBlank()) {
|
||||
value.toString()
|
||||
} else null
|
||||
}
|
||||
password?.let {
|
||||
putString(key, it)
|
||||
finalMap[key] = it
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (value != null) {
|
||||
when (value) {
|
||||
is Int -> { putInt(key, value); finalMap[key] = value }
|
||||
is Boolean -> { putBoolean(key, value); finalMap[key] = value }
|
||||
is Long -> { putLong(key, value); finalMap[key] = value }
|
||||
is Double -> { // JSON 数字会被解析为 Double
|
||||
val floatValue = value.toFloat()
|
||||
putFloat(key, floatValue)
|
||||
finalMap[key] = floatValue
|
||||
}
|
||||
is Float -> { putFloat(key, value); finalMap[key] = value }
|
||||
is String -> { putString(key, value); finalMap[key] = value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 同步恢复到 DataStore
|
||||
settingsRepository.batchPutFromMap(finalMap)
|
||||
}
|
||||
|
||||
private fun readXmlToMap(file: File): Map<String, Any?> {
|
||||
val map = mutableMapOf<String, Any?>()
|
||||
try {
|
||||
val factory = XmlPullParserFactory.newInstance()
|
||||
val parser = factory.newPullParser()
|
||||
FileInputStream(file).use { fis ->
|
||||
parser.setInput(fis, "UTF-8")
|
||||
var eventType = parser.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
val tagName = parser.name
|
||||
val name = parser.getAttributeValue(null, "name")
|
||||
if (name != null) {
|
||||
when (tagName) {
|
||||
"string" -> map[name] = parser.nextText()
|
||||
"int" -> map[name] = parser.getAttributeValue(null, "value").toInt()
|
||||
"long" -> map[name] = parser.getAttributeValue(null, "value").toLong()
|
||||
"float" -> map[name] = parser.getAttributeValue(null, "value").toFloat()
|
||||
"boolean" -> map[name] = parser.getAttributeValue(null, "value").toBoolean()
|
||||
}
|
||||
}
|
||||
}
|
||||
eventType = parser.next()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? {
|
||||
try {
|
||||
val file = File(path, fileName)
|
||||
|
||||
@@ -374,7 +374,7 @@ fun BackupConfigScreen(
|
||||
AppTextField(
|
||||
value = tempPassword,
|
||||
onValueChange = { tempPassword = it },
|
||||
backgroundColor = MiuixTheme.colorScheme.surface,
|
||||
backgroundColor = LegadoTheme.colorScheme.surface,
|
||||
label = stringResource(R.string.web_dav_pw),
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
|
||||
Reference in New Issue
Block a user