fix: 修复密码管理全链路问题并简化 BinaryResolver
1. 修复 ConfigViewModel.save() 密码未保存到 PasswordManager 的 Bug - 当 save() 未接收到独立密码参数时,自动从 formConfig 提取密码 - 排除 'stored-in-keystore' 占位符误保存 2. 修复 importConfig 导入后密码占位符显示问题 - 跨设备导入后密码字段显示 'stored-in-keystore' 而非空值 - 空密码提示用户重新输入,避免混淆 3. 修复 ConfigScreen 密码字段同步问题 - LaunchedEffect 同步时过滤 'stored-in-keystore' 占位符 - 防止密码占位符在密码输入框中显示 4. 简化 BinaryResolver 缓存模式 - 移除复杂泛型缓存辅助函数 cacheOrResolve - 改用带 @Volatile 的内联空值检查 + also 缓存 - 代码更简洁、更易维护
This commit is contained in:
@@ -12,24 +12,29 @@ import java.io.File
|
||||
object BinaryResolver {
|
||||
private const val TAG = "BinaryResolver"
|
||||
|
||||
private var tarPath: String? = null
|
||||
private var zstdPath: String? = null
|
||||
@Volatile
|
||||
private var _tarPath: String? = null
|
||||
|
||||
fun tarPath(context: Context): String? = cacheOrResolve(context, "libtar_bin.so", "tar_bin", ::tarPath) { tarPath = it }
|
||||
fun zstdPath(context: Context): String? = cacheOrResolve(context, "libzstd_bin.so", "zstd_bin", ::zstdPath) { zstdPath = it }
|
||||
@Volatile
|
||||
private var _zstdPath: String? = null
|
||||
|
||||
private fun cacheOrResolve(
|
||||
context: Context, libName: String, destName: String,
|
||||
cache: () -> String?, setCache: (String?) -> Unit
|
||||
): String? {
|
||||
val cached = cache()
|
||||
if (cached != null) return cached
|
||||
val resolved = resolve(context, libName, destName)
|
||||
setCache(resolved)
|
||||
return resolved
|
||||
/** Resolve and cache the path to the bundled tar binary. */
|
||||
fun tarPath(context: Context): String? {
|
||||
_tarPath?.let { return it }
|
||||
return resolve(context, "libtar_bin.so", "tar_bin").also { _tarPath = it }
|
||||
}
|
||||
|
||||
private fun resolve(context: Context, libName: String, destName: String): String? {
|
||||
/** Resolve and cache the path to the bundled zstd binary. */
|
||||
fun zstdPath(context: Context): String? {
|
||||
_zstdPath?.let { return it }
|
||||
return resolve(context, "libzstd_bin.so", "zstd_bin").also { _zstdPath = it }
|
||||
}
|
||||
|
||||
private fun resolve(
|
||||
context: Context,
|
||||
libName: String,
|
||||
destName: String,
|
||||
): String? {
|
||||
val nativeLibDir = context.applicationInfo.nativeLibraryDir
|
||||
val source = File(nativeLibDir, libName)
|
||||
if (!source.isFile) {
|
||||
|
||||
@@ -70,11 +70,12 @@ fun ConfigScreen(
|
||||
backupUserId = config.backupUserId
|
||||
resticEnabled = config.resticEnabled == 1
|
||||
resticRepo = config.resticRepo
|
||||
resticPassword = config.resticPassword
|
||||
// 避免密码占位符显示在 UI 中
|
||||
resticPassword = config.resticPassword.takeIf { it != "stored-in-keystore" } ?: ""
|
||||
resticBackend = config.resticBackend
|
||||
resticBackendUrl = config.resticBackendUrl
|
||||
resticBackendUser = config.resticBackendUser
|
||||
resticBackendPass = config.resticBackendPass
|
||||
resticBackendPass = config.resticBackendPass.takeIf { it != "stored-in-keystore" } ?: ""
|
||||
resticBackendShare = config.resticBackendShare
|
||||
resticBackendDomain = config.resticBackendDomain
|
||||
streamingEnabled = config.useStreaming == 1
|
||||
|
||||
@@ -200,6 +200,9 @@ class ConfigViewModel(
|
||||
* Save config to file on IO and update status message.
|
||||
* The caller passes the current form values as a [BackupConfig] copy.
|
||||
* 密码单独通过 [PasswordManager] 安全存储,不入配置文件。
|
||||
*
|
||||
* 当 [resticPassword] / [backendPass] 为 null 时,自动从 [formConfig] 提取密码
|
||||
* 并保存到 [PasswordManager],确保 ConfigScreen 的调用也能正确持久化密码。
|
||||
*/
|
||||
fun save(
|
||||
formConfig: BackupConfig,
|
||||
@@ -208,11 +211,17 @@ class ConfigViewModel(
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
// 保存密码到加密存储
|
||||
if (resticPassword != null && resticPassword.isNotEmpty()) {
|
||||
PasswordManager.setResticPassword(resticPassword)
|
||||
val effectiveResticPassword =
|
||||
resticPassword
|
||||
?: formConfig.resticPassword.takeUnless { it.isNullOrEmpty() || it == "stored-in-keystore" }
|
||||
val effectiveBackendPass =
|
||||
backendPass
|
||||
?: formConfig.resticBackendPass.takeUnless { it.isNullOrEmpty() || it == "stored-in-keystore" }
|
||||
if (effectiveResticPassword != null && effectiveResticPassword.isNotEmpty()) {
|
||||
PasswordManager.setResticPassword(effectiveResticPassword)
|
||||
}
|
||||
if (backendPass != null && backendPass.isNotEmpty()) {
|
||||
PasswordManager.setBackendPass(backendPass)
|
||||
if (effectiveBackendPass != null && effectiveBackendPass.isNotEmpty()) {
|
||||
PasswordManager.setBackendPass(effectiveBackendPass)
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
BackupConfig.toFile(formConfig, configFile)
|
||||
@@ -304,10 +313,20 @@ class ConfigViewModel(
|
||||
// 需要从 PasswordManager 恢复真实密码,避免被覆盖
|
||||
val realResticPw = PasswordManager.getResticPassword()
|
||||
val realBackendPw = PasswordManager.getBackendPass()
|
||||
// 如果 PasswordManager 和配置文件中都没有真实密码(例如跨设备导入),
|
||||
// 置空密码字段,提示用户重新输入
|
||||
val restoredResticPw =
|
||||
realResticPw
|
||||
?: parsed.resticPassword.takeUnless { it == "stored-in-keystore" }
|
||||
?: ""
|
||||
val restoredBackendPw =
|
||||
realBackendPw
|
||||
?: parsed.resticBackendPass.takeUnless { it == "stored-in-keystore" }
|
||||
?: ""
|
||||
val restoredConfig =
|
||||
parsed.copy(
|
||||
resticPassword = realResticPw ?: parsed.resticPassword,
|
||||
resticBackendPass = realBackendPw ?: parsed.resticBackendPass,
|
||||
resticPassword = restoredResticPw,
|
||||
resticBackendPass = restoredBackendPw,
|
||||
)
|
||||
_uiState.update { it.copy(config = restoredConfig) }
|
||||
Log.i(TAG, "importConfig: loaded config from SAF")
|
||||
|
||||
Reference in New Issue
Block a user