Apply developer settings only after accepted persistence completes
This commit is contained in:
1 parent
a9495de90f
commit
e8aeaaf365
1 file changed
+71
-40
+71
-40
@@ -92,6 +92,7 @@ fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = {
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) { loadFailed = true; LogManager.addLog("ERROR", "Settings", "Settings load failed: $e") }
|
||||
}
|
||||
BackHandler(enabled = !loaded, onBack = onBack)
|
||||
if (!loaded) {
|
||||
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
TextButton(onClick = onBack) { Text(stringResource(R.string.back)) }
|
||||
@@ -338,10 +339,13 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
|
||||
fun saveUdpLogSettings() {
|
||||
val settings = UdpLogSettings(logUdpEnabled, logUdpIp, logUdpPort.toIntOrNull() ?: 0)
|
||||
provider.saveUdpLogSettings(settings)
|
||||
NativeLib.setUdpLogTarget(if (settings.active) settings.ip else "", if (settings.active) settings.port else 0)
|
||||
LogManager.addLog(if (settings.enabled && !settings.active) "WARN" else "INFO", "UDPLog",
|
||||
"Network logging enabled=${settings.enabled} active=${settings.active} target=${settings.ip}:${settings.port}")
|
||||
val request = provider.saveUdpLogSettings(settings)
|
||||
provider.changes.submit("network logging") {
|
||||
request.await()
|
||||
NativeLib.setUdpLogTarget(if (settings.active) settings.ip else "", if (settings.active) settings.port else 0)
|
||||
LogManager.addLog(if (settings.enabled && !settings.active) "WARN" else "INFO", "UDPLog",
|
||||
"Network logging enabled=${settings.enabled} active=${settings.active} target=${settings.ip}:${settings.port}")
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -354,22 +358,29 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
logFile = provider.getString("debug.log_file") ?: "utun.log"
|
||||
val udpLog = try { provider.getUdpLogSettings() }
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) { LogManager.addLog("ERROR", "UDPLog", "Settings load failed; network logging disabled: $e"); UdpLogSettings() }
|
||||
logUdpEnabled = udpLog.enabled; logUdpIp = udpLog.ip; logUdpPort = udpLog.port.toString()
|
||||
logUdpLoaded = true
|
||||
LogManager.addLog("INFO", "UDPLog", "Settings loaded enabled=${udpLog.enabled} active=${udpLog.active}")
|
||||
catch (e: Exception) { provider.changes.reportFailure("load network logging", e); null }
|
||||
if (udpLog != null) {
|
||||
logUdpEnabled = udpLog.enabled; logUdpIp = udpLog.ip; logUdpPort = udpLog.port.toString()
|
||||
logUdpLoaded = true
|
||||
LogManager.addLog("INFO", "UDPLog", "Settings loaded enabled=${udpLog.enabled} active=${udpLog.active}")
|
||||
}
|
||||
standbyActiveSec = provider.getInt("chat.standby_active_sec").toString()
|
||||
standbySleepSec = provider.getInt("chat.standby_sleep_sec").toString()
|
||||
standbyMinSleepSec = provider.getInt("chat.standby_min_sleep_sec").toString()
|
||||
standbyKeepaliveMs = provider.getInt("chat.standby_keepalive_ms").toString()
|
||||
val stored = provider.getString("debug.categories") ?: ""
|
||||
catLevels = if (stored.isNotEmpty()) stored.split(",").associate { it.split("=", limit = 2).let { (k, v) -> k to v } } else emptyMap()
|
||||
catLevels = stored.split(',').mapNotNull { entry ->
|
||||
val pair = entry.trim().split('=', limit = 2)
|
||||
if (pair.size == 2 && pair[0].isNotBlank() && pair[1].uppercase() in LEVELS) pair[0].trim() to pair[1].uppercase()
|
||||
else { if (entry.isNotBlank()) LogManager.addLog("WARN", "CFG", "Malformed debug category preference ignored"); null }
|
||||
}.toMap()
|
||||
try { servers = provider.loadServersFromConfig().await()
|
||||
serversLoaded = true
|
||||
} catch (e: CancellationException) { throw e
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "CFG", "load network settings failed: ${e.javaClass.simpleName}: ${e.message}")
|
||||
configFailure = true
|
||||
} finally { serversLoaded = true }
|
||||
}
|
||||
availableIfaces = allIface.drop(1)
|
||||
connectionMode = provider.getConnectionMode()
|
||||
autoSocketProtocols = provider.getAutoSocketProtocols()
|
||||
@@ -381,40 +392,46 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
servers = s
|
||||
val revision = ++serverSaveRevision
|
||||
val request = provider.saveServersToConfig(s)
|
||||
scope.launch {
|
||||
provider.changes.submit("manual sockets") {
|
||||
try { request.await()
|
||||
} catch (e: CancellationException) { throw e
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "CFG", "save network settings failed: ${e.javaClass.simpleName}: ${e.message}")
|
||||
if (revision == serverSaveRevision) configFailure = true
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
val switchMode = { mode: String ->
|
||||
connectionMode = mode; onDirty(true)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
onDirty(true)
|
||||
provider.changes.submit("connection mode") {
|
||||
try {
|
||||
provider.setConnectionMode(mode)
|
||||
connectionMode = mode
|
||||
if (NativeLib.isReady()) check(NativeLib.restart(provider.getString("db.path") ?: "",
|
||||
provider.getInt("control.port"), provider.buildConfigText().await())) { "core restart rejected connection mode" }
|
||||
} catch (e: CancellationException) { throw e
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "CFG", "connection mode change failed: ${e.javaClass.simpleName}: ${e.message}")
|
||||
withContext(Dispatchers.Main) { configFailure = true }
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
val setDebug = { level: String ->
|
||||
NativeLib.setDebugLevel("", level)
|
||||
scope.launch { provider.setValue("debug.console_level", level) }; debugLevel = level
|
||||
provider.changes.submit("debug level") {
|
||||
provider.setValue("debug.console_level", level)
|
||||
NativeLib.setDebugLevel("", level); debugLevel = level
|
||||
}
|
||||
}
|
||||
val setCatLevel = { cat: String, lvl: String ->
|
||||
val updated = catLevels.toMutableMap()
|
||||
if (lvl == "") updated.remove(cat) else updated[cat] = lvl
|
||||
catLevels = updated
|
||||
val csv = updated.entries.joinToString(",") { "${it.key}=${it.value}" }
|
||||
scope.launch { provider.setValue("debug.categories", csv) }
|
||||
if (lvl.isEmpty()) NativeLib.setDebugLevel(cat, "NONE") else NativeLib.setDebugLevel(cat, lvl)
|
||||
provider.changes.submit("debug category") {
|
||||
provider.setValue("debug.categories", csv); catLevels = updated
|
||||
NativeLib.setDebugLevel(cat, lvl.ifEmpty { "NONE" })
|
||||
}
|
||||
}
|
||||
|
||||
var ctrlExpanded by remember { mutableStateOf(false) }
|
||||
@@ -437,7 +454,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
/* Node */
|
||||
item(key = "node-title") { Text(stringResource(R.string.node), style = MaterialTheme.typography.titleMedium) }
|
||||
item(key = "listen-port") {
|
||||
OutlinedTextField(listenPort, { v -> onDirty(true); listenPort = v; scope.launch { provider.setValue("node.listen_port", v) } },
|
||||
OutlinedTextField(listenPort, { v -> onDirty(true); listenPort = v; provider.changes.submit("listen port") { provider.setValue("node.listen_port", v) } },
|
||||
label = { Text(stringResource(R.string.listen_port)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true, modifier = Modifier.heightIn(min = 40.dp))
|
||||
}
|
||||
@@ -454,13 +471,13 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
ctrlIfaces.forEach { (n, ip) ->
|
||||
DropdownMenuItem(text = { Text("$n ($ip)") }, onClick = {
|
||||
controlBindIp = ip; ctrlBindLabel = "$n ($ip)"; onDirty(true)
|
||||
scope.launch { provider.setValue("control.bind_ip", ip) }; ctrlExpanded = false
|
||||
provider.changes.submit("control address") { provider.setValue("control.bind_ip", ip) }; ctrlExpanded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
OutlinedTextField(controlPort, { v -> onDirty(true); controlPort = v; scope.launch { provider.setValue("control.port", v) } },
|
||||
OutlinedTextField(controlPort, { v -> onDirty(true); controlPort = v; provider.changes.submit("control port") { provider.setValue("control.port", v) } },
|
||||
label = { Text(stringResource(R.string.port)) }, modifier = Modifier.width(80.dp).heightIn(min = 40.dp),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true)
|
||||
}
|
||||
@@ -505,7 +522,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
onClick = {
|
||||
if (autoSocketProtocols != protocols) {
|
||||
savingSocketProtocols = true
|
||||
scope.launch {
|
||||
provider.changes.submit("socket protocols") {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
provider.setAutoSocketProtocols(protocols)
|
||||
@@ -519,6 +536,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "CFG", "socket protocol change failed: ${e.message}")
|
||||
configFailure = true
|
||||
throw e
|
||||
} finally { savingSocketProtocols = false }
|
||||
}
|
||||
}
|
||||
@@ -625,12 +643,14 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(standbyActiveSec,
|
||||
{ v -> standbyActiveSec = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1..3600) {
|
||||
scope.launch { provider.setValue("chat.standby_active_sec", v) }; NativeLib.setChatSetting("standby_active_sec", v) } },
|
||||
provider.changes.submit("standby active") { provider.setValue("chat.standby_active_sec", v)
|
||||
if (NativeLib.isReady()) NativeLib.setChatSetting("standby_active_sec", v) } } },
|
||||
label = { Text(stringResource(R.string.standby_active_seconds)) }, modifier = Modifier.weight(1f).heightIn(min = 40.dp),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true)
|
||||
OutlinedTextField(standbySleepSec,
|
||||
{ v -> standbySleepSec = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1..86400) {
|
||||
scope.launch { provider.setValue("chat.standby_sleep_sec", v) }; NativeLib.setChatSetting("standby_sleep_sec", v) } },
|
||||
provider.changes.submit("standby sleep") { provider.setValue("chat.standby_sleep_sec", v)
|
||||
if (NativeLib.isReady()) NativeLib.setChatSetting("standby_sleep_sec", v) } } },
|
||||
label = { Text(stringResource(R.string.standby_sleep_seconds)) }, modifier = Modifier.weight(1f).heightIn(min = 40.dp),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true)
|
||||
}
|
||||
@@ -638,14 +658,16 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
item(key = "standby-min-sleep") {
|
||||
OutlinedTextField(standbyMinSleepSec,
|
||||
{ v -> standbyMinSleepSec = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1..86400) {
|
||||
scope.launch { provider.setValue("chat.standby_min_sleep_sec", v) }; NativeLib.setChatSetting("standby_min_sleep_sec", v) } },
|
||||
provider.changes.submit("standby minimum sleep") { provider.setValue("chat.standby_min_sleep_sec", v)
|
||||
if (NativeLib.isReady()) NativeLib.setChatSetting("standby_min_sleep_sec", v) } } },
|
||||
label = { Text(stringResource(R.string.standby_min_sleep_seconds)) }, modifier = Modifier.heightIn(min = 40.dp),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true)
|
||||
}
|
||||
item(key = "standby-keepalive") {
|
||||
OutlinedTextField(standbyKeepaliveMs,
|
||||
{ v -> standbyKeepaliveMs = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1000..600000) {
|
||||
scope.launch { provider.setValue("chat.standby_keepalive_ms", v) }; NativeLib.setChatSetting("standby_keepalive_ms", v) } },
|
||||
provider.changes.submit("standby keepalive") { provider.setValue("chat.standby_keepalive_ms", v)
|
||||
if (NativeLib.isReady()) NativeLib.setChatSetting("standby_keepalive_ms", v) } } },
|
||||
label = { Text(stringResource(R.string.standby_keepalive_ms)) }, modifier = Modifier.heightIn(min = 40.dp),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true)
|
||||
}
|
||||
@@ -658,10 +680,11 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(stringResource(R.string.group_autoconnect), modifier = Modifier.weight(1f))
|
||||
Switch(checked = autoConnect, onCheckedChange = {
|
||||
autoConnect = it
|
||||
val s = if (it) "1" else "0"
|
||||
scope.launch { provider.setValue("chat.group_autoconnect", s) }
|
||||
NativeLib.setChatSetting("group_autoconnect", s)
|
||||
provider.changes.submit("group autoconnect") {
|
||||
provider.setValue("chat.group_autoconnect", s); autoConnect = s == "1"
|
||||
if (NativeLib.isReady()) NativeLib.setChatSetting("group_autoconnect", s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -672,7 +695,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
item(key = "debug-title") { Text(stringResource(R.string.debug), style = MaterialTheme.typography.titleMedium) }
|
||||
item(key = "debug-level") { DebugLevelDropdown(label = stringResource(R.string.global_level), current = debugLevel.uppercase(), onSelect = setDebug) }
|
||||
item(key = "log-file") {
|
||||
OutlinedTextField(logFile, { v -> logFile = v; scope.launch { provider.setValue("debug.log_file", v) } },
|
||||
OutlinedTextField(logFile, { v -> logFile = v; provider.changes.submit("log file") { provider.setValue("debug.log_file", v) } },
|
||||
label = { Text(stringResource(R.string.log_file)) }, singleLine = true)
|
||||
}
|
||||
item(key = "log-udp-enabled") {
|
||||
@@ -727,7 +750,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
Switch(checked = startOnBoot, enabled = !savingStartOnBoot, onCheckedChange = { enabled ->
|
||||
savingStartOnBoot = true
|
||||
startOnBootError = false
|
||||
scope.launch {
|
||||
provider.changes.submit("start on boot") {
|
||||
try {
|
||||
provider.setStartOnBoot(enabled)
|
||||
} catch (e: CancellationException) {
|
||||
@@ -735,6 +758,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "CONFIG", "Start on boot setting save failed: enabled=$enabled error=$e")
|
||||
startOnBootError = true
|
||||
throw e
|
||||
} finally {
|
||||
savingStartOnBoot = false
|
||||
}
|
||||
@@ -751,13 +775,17 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
color = if (serviceEnabled) Color(0xFF81C784) else MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodyMedium)
|
||||
Button(onClick = {
|
||||
runBlocking { provider.setServiceEnabled(true) }
|
||||
ContextCompat.startForegroundService(ChatApplication.instance,
|
||||
Intent(ChatApplication.instance, HeadlessService::class.java))
|
||||
provider.changes.submit("start core service") {
|
||||
provider.setServiceEnabled(true)
|
||||
checkNotNull(ContextCompat.startForegroundService(ChatApplication.instance,
|
||||
Intent(ChatApplication.instance, HeadlessService::class.java))) { "Core service component unavailable" }
|
||||
}
|
||||
}, enabled = !serviceEnabled) { Text(stringResource(R.string.service_start)) }
|
||||
Button(onClick = {
|
||||
runBlocking { provider.setServiceEnabled(false) }
|
||||
ChatApplication.instance.stopService(Intent(ChatApplication.instance, HeadlessService::class.java))
|
||||
provider.changes.submit("stop core service") {
|
||||
provider.setServiceEnabled(false)
|
||||
ChatApplication.instance.stopService(Intent(ChatApplication.instance, HeadlessService::class.java))
|
||||
}
|
||||
}, enabled = serviceEnabled,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)) { Text(stringResource(R.string.service_stop)) }
|
||||
}
|
||||
@@ -767,7 +795,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
|
||||
item(key = "restart") {
|
||||
Button(onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
provider.changes.submit("restart core") {
|
||||
try {
|
||||
if (NativeLib.isReady()) {
|
||||
val path = provider.getString("db.path") ?: ""; val port = provider.getInt("control.port")
|
||||
@@ -777,6 +805,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "CFG", "restart failed: ${e.javaClass.simpleName}: ${e.message}")
|
||||
withContext(Dispatchers.Main) { configFailure = true }
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary)) {
|
||||
@@ -786,9 +815,11 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
item(key = "exit-spacer") { Spacer(Modifier.height(8.dp)) }
|
||||
item(key = "exit") {
|
||||
Button(onClick = {
|
||||
runBlocking { provider.setServiceEnabled(false) }
|
||||
ChatApplication.instance.stopService(Intent(ChatApplication.instance, HeadlessService::class.java))
|
||||
onExit()
|
||||
provider.changes.submit("stop service and exit") {
|
||||
provider.setServiceEnabled(false)
|
||||
ChatApplication.instance.stopService(Intent(ChatApplication.instance, HeadlessService::class.java))
|
||||
onExit()
|
||||
}
|
||||
}, modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)) {
|
||||
Icon(Icons.Default.PowerSettingsNew, null, modifier = Modifier.size(18.dp)); Spacer(Modifier.width(8.dp)); Text(stringResource(R.string.stop_all_exit))
|
||||
|
||||
Reference in new issue
Block a user