Initial commit

This commit is contained in:
weishaupt
2026-09-01 12:32:15 -04:00
commit defdefdb28
33 changed files with 3540 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
package data
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
/**
* Desktop equivalent of the Android EncryptedSharedPreferences-backed repository.
* Persists the server list — including each server's "on join perform" commands
* (NickServ/ChanServ identify, etc.) — as a single JSON file (rootDir/servers.json).
* Loaded once at app start and replayed automatically on every connect, so you only
* ever type your identify commands in once.
*
* NOTE ON SECRECY: the Android version encrypts this at rest via EncryptedSharedPreferences
* backed by an Android Keystore master key, so identify passwords never sit in plaintext.
* There is no OS-backed secret store used here — desktop has no direct equivalent that
* this project depends on. This file is plaintext JSON; the best this does is restrict
* its OS file permissions to the current user (best-effort via File.setReadable/setWritable,
* since Windows has no POSIX permission bits to set directly). That is meaningfully weaker
* than the Android Keystore-backed encryption - treat it accordingly.
*/
class ServerRepository(rootDir: File) {
private val file = File(rootDir, "servers.json")
fun loadAll(): List<ServerConfig> {
if (!file.exists()) return emptyList()
return try {
val arr = JSONArray(file.readText())
(0 until arr.length()).map { i -> fromJson(arr.getJSONObject(i)) }
} catch (e: Exception) {
emptyList()
}
}
fun saveAll(servers: List<ServerConfig>) {
val arr = JSONArray()
servers.forEach { arr.put(toJson(it)) }
file.parentFile?.mkdirs()
file.writeText(arr.toString())
restrictToOwner(file)
}
fun upsert(server: ServerConfig) {
// TEMP DEBUG (see task: on-connect identify command investigation) - confirms the
// Edit Server dialog's Save actually persists onConnectCommands, by writing then
// immediately re-reading the file back rather than trusting the in-memory value.
println("[ServerRepository] upsert(${server.id}): saving onConnectCommands=${server.onConnectCommands}")
val current = loadAll().toMutableList()
val idx = current.indexOfFirst { it.id == server.id }
if (idx >= 0) current[idx] = server else current.add(server)
saveAll(current)
val reloaded = loadAll().firstOrNull { it.id == server.id }
println("[ServerRepository] upsert(${server.id}): reloaded from disk onConnectCommands=${reloaded?.onConnectCommands}")
}
fun remove(serverId: String) {
saveAll(loadAll().filterNot { it.id == serverId })
}
// Best-effort "only the current user can read this" - Windows has no POSIX mode
// bits, so File.setReadable/setWritable(_, ownerOnly = true) is the closest plain
// java.io.File gets; it maps to denying the "Everyone" ACL entry rather than a
// real per-user ACL, which is weaker than the Android Keystore-backed version.
private fun restrictToOwner(file: File) {
file.setReadable(false, false)
file.setReadable(true, true)
file.setWritable(false, false)
file.setWritable(true, true)
}
private fun toJson(s: ServerConfig): JSONObject = JSONObject().apply {
put("id", s.id)
put("label", s.label)
put("host", s.host)
put("port", s.port)
put("useTls", s.useTls)
put("nick", s.nick)
put("autoJoinChannels", JSONArray(s.autoJoinChannels))
put("saslUser", s.saslUser ?: JSONObject.NULL)
put("saslPass", s.saslPass ?: JSONObject.NULL)
put("onConnectCommands", JSONArray(s.onConnectCommands))
}
private fun fromJson(o: JSONObject): ServerConfig {
fun strArray(key: String): List<String> {
val arr = o.optJSONArray(key) ?: return emptyList()
return (0 until arr.length()).map { arr.getString(it) }
}
return ServerConfig(
id = o.getString("id"),
label = o.getString("label"),
host = o.getString("host"),
port = o.optInt("port", 6667),
useTls = o.optBoolean("useTls", false),
nick = o.getString("nick"),
autoJoinChannels = strArray("autoJoinChannels"),
saslUser = o.optString("saslUser", null).takeIf { it != "null" },
saslPass = o.optString("saslPass", null).takeIf { it != "null" },
onConnectCommands = strArray("onConnectCommands")
)
}
}