From 462c6da9657f4b13cd5933259bfa57344e793aba Mon Sep 17 00:00:00 2001 From: jay-tux Date: Thu, 13 Aug 2026 15:22:53 +0200 Subject: [PATCH] CLI client: more robust --- .../jaytux/phoebench/clients/cli/Client.kt | 3 +- .../com/jaytux/phoebench/clients/cli/Main.kt | 3 + .../clients/cli/PersistentStorage.kt | 61 ++++++++++++++++--- .../phoebench/clients/cli/ProjectHandlers.kt | 14 ++--- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Client.kt b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Client.kt index 49e78f5..5aa34c3 100644 --- a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Client.kt +++ b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Client.kt @@ -80,9 +80,10 @@ object Client { val res = route.call(client, body) res } catch (e: CancellationException) { + println("Coroutine calling ${route.verb} ${route.pattern} was cancelled.") ErrorResponse("Coroutine calling ${route.verb} ${route.pattern} was cancelled.").error() } catch (e: Exception) { -// println("Call to ${route.pattern} ran into an exception") + println("Call to ${route.pattern} ran into an exception: ${e.message} (${e::class.simpleName})") ErrorResponse(e.message ?: "Unknown error.").error() } } diff --git a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Main.kt b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Main.kt index 3470989..76df801 100644 --- a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Main.kt +++ b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/Main.kt @@ -1,7 +1,10 @@ package com.jaytux.phoebench.clients.cli +import kotlin.system.exitProcess + fun main(args: Array) { Client.forceGloballyInitialized() CLI.main(args) Client.shutdown() + exitProcess(0) // because D-Bus threads... } \ No newline at end of file diff --git a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/PersistentStorage.kt b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/PersistentStorage.kt index 7b7a274..df149e3 100644 --- a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/PersistentStorage.kt +++ b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/PersistentStorage.kt @@ -1,14 +1,63 @@ package com.jaytux.phoebench.clients.cli +import com.github.javakeyring.BackendNotSupportedException import com.github.javakeyring.Keyring import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json import kotlinx.serialization.serializer +import java.util.prefs.Preferences import kotlin.uuid.Uuid object PersistentStorage { private val json = Json const val SERVICE = "com.jaytux.phoebench.cli" + private var _backend: IBackend = KeyringBackend + + interface IBackend { + fun read(key: String): String? + fun write(key: String, value: String) + fun erase(key: String) + } + + private object KeyringBackend : IBackend { + override fun read(key: String): String? = try { + Keyring.create().use { it.getPassword(SERVICE, key) } + } catch(e: Exception) { + _backend = PreferencesBackend + _backend.read(key) + } + + override fun write(key: String, value: String) { + try { + Keyring.create().use { it.setPassword(SERVICE, key, value) } + } catch(e: Exception) { + _backend = PreferencesBackend + _backend.write(key, value) + } + } + + override fun erase(key: String) { + try { + Keyring.create().use { it.deletePassword(SERVICE, key) } + } catch (e: Exception) { + _backend = PreferencesBackend + _backend.erase(key) + } + } + } + + private object PreferencesBackend : IBackend { + private val prefs = Preferences.userNodeForPackage(this::class.java) + override fun read(key: String): String? = prefs.get(key, null) + + override fun write(key: String, value: String) { + prefs.put(key, value) + } + + override fun erase(key: String) { + prefs.remove(key) + } + } class StoredProperty( private val _key: String, @@ -20,21 +69,15 @@ object PersistentStorage { ) fun load(): T? = runCatching { - Keyring.create().use { - it.getPassword(SERVICE, _key) - } + _backend.read(_key) }.getOrNull()?.let { _fromString(it) } fun save(value: T) = runCatching { - val keyring = Keyring.create().use { - it.setPassword(SERVICE, _key, _toString(value)) - } + _backend.write(_key, _toString(value)) }.onFailure { println("Failed to write to OS keyring: ${it.message}") }.ignore() fun erase() = runCatching { - val keyring = Keyring.create().use { - it.deletePassword(SERVICE, _key) - } + _backend.erase(_key) }.ignore() } diff --git a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/ProjectHandlers.kt b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/ProjectHandlers.kt index 5006adc..299fcb6 100644 --- a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/ProjectHandlers.kt +++ b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/ProjectHandlers.kt @@ -67,15 +67,15 @@ object ProjectHandlers { System.err.println("Failed to get project details: ${it.msg}") }) { println("Project ${it.owner.name}/${it.name} [${it.id}]:") - println("${it.usedLabels.size} labels:") - it.usedLabels.forEach { l -> - println(" - [${l.id}] ${l.name} (with color ${l.color})") + println("${it.usedLabels.size} versions:") + it.usedLabels.forEach { v -> + println(" - [${v.id}] VERSION ${v.name} (with color ${v.color}); metadata: ${v.meta}") } val map = it.usedLabels.associateBy { l -> l.id } println("\n${it.entries.size} entries:") it.entries.forEach { e -> - println(" - [${e.id}] Entry labeled ${map[e.labelId]?.name ?: ""} at ${e.timestamp.fmt()} " + + println(" - [${e.id}] ENTRY versioned ${map[e.labelId]?.name ?: ""} at ${e.timestamp.fmt()} " + "(${e.warmups.size} warmup data points, ${e.measurements.size} measurement data points; in ${e.unit.disp})") } } @@ -94,7 +94,7 @@ object ProjectHandlers { fun newVersion(name: String?, color: String?, project: CLI.Commands.Project.IProjectIdentification?, version: String?) { val id = ensureProjectIdentification(project) - val useName = name.maybePrompt("label name") { it } + val useName = name.maybePrompt("version name") { it } val useColor = color.maybePrompt("color") { val check = it.length == 7 && it[0] == '#' && it.substring(1, it.length).all { c -> c.isDigit() || c in "ABCDEF" } if(check) it @@ -110,7 +110,7 @@ object ProjectHandlers { Client.callRoute(Routes.Version.new, VersionRequest(useName, useColor, useVersion, projectId)) } }.fold({ - System.err.println("Could not create label: ${it.msg}") + System.err.println("Could not create version: ${it.msg}") }) { println("Label ${it.name} (${it.color}) [${it.id}] created.") } @@ -164,7 +164,7 @@ object ProjectHandlers { is CLI.Commands.Project.ID -> versionId.id.value() is CLI.Commands.Project.VersionName -> Client.callRoute(Routes.Project.get, pId).bind { it.usedLabels.firstOrNull { l -> l.name == versionId.name }?.id?.value() - ?: ErrorResponse("Label ${versionId.name} does not exist in this project.").error() + ?: ErrorResponse("Version ${versionId.name} does not exist in this project.").error() } }.map { pId to it } }.bind { (pId, vId) ->