CLI client: more robust
This commit is contained in:
@@ -80,9 +80,10 @@ object Client {
|
|||||||
val res = route.call(client, body)
|
val res = route.call(client, body)
|
||||||
res
|
res
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
|
println("Coroutine calling ${route.verb} ${route.pattern} was cancelled.")
|
||||||
ErrorResponse("Coroutine calling ${route.verb} ${route.pattern} was cancelled.").error()
|
ErrorResponse("Coroutine calling ${route.verb} ${route.pattern} was cancelled.").error()
|
||||||
} catch (e: Exception) {
|
} 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()
|
ErrorResponse(e.message ?: "Unknown error.").error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.jaytux.phoebench.clients.cli
|
package com.jaytux.phoebench.clients.cli
|
||||||
|
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
Client.forceGloballyInitialized()
|
Client.forceGloballyInitialized()
|
||||||
CLI.main(args)
|
CLI.main(args)
|
||||||
Client.shutdown()
|
Client.shutdown()
|
||||||
|
exitProcess(0) // because D-Bus threads...
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,63 @@
|
|||||||
package com.jaytux.phoebench.clients.cli
|
package com.jaytux.phoebench.clients.cli
|
||||||
|
|
||||||
|
import com.github.javakeyring.BackendNotSupportedException
|
||||||
import com.github.javakeyring.Keyring
|
import com.github.javakeyring.Keyring
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
|
import java.util.prefs.Preferences
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
object PersistentStorage {
|
object PersistentStorage {
|
||||||
private val json = Json
|
private val json = Json
|
||||||
const val SERVICE = "com.jaytux.phoebench.cli"
|
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<T>(
|
class StoredProperty<T>(
|
||||||
private val _key: String,
|
private val _key: String,
|
||||||
@@ -20,21 +69,15 @@ object PersistentStorage {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun load(): T? = runCatching {
|
fun load(): T? = runCatching {
|
||||||
Keyring.create().use {
|
_backend.read(_key)
|
||||||
it.getPassword(SERVICE, _key)
|
|
||||||
}
|
|
||||||
}.getOrNull()?.let { _fromString(it) }
|
}.getOrNull()?.let { _fromString(it) }
|
||||||
|
|
||||||
fun save(value: T) = runCatching {
|
fun save(value: T) = runCatching {
|
||||||
val keyring = Keyring.create().use {
|
_backend.write(_key, _toString(value))
|
||||||
it.setPassword(SERVICE, _key, _toString(value))
|
|
||||||
}
|
|
||||||
}.onFailure { println("Failed to write to OS keyring: ${it.message}") }.ignore()
|
}.onFailure { println("Failed to write to OS keyring: ${it.message}") }.ignore()
|
||||||
|
|
||||||
fun erase() = runCatching {
|
fun erase() = runCatching {
|
||||||
val keyring = Keyring.create().use {
|
_backend.erase(_key)
|
||||||
it.deletePassword(SERVICE, _key)
|
|
||||||
}
|
|
||||||
}.ignore()
|
}.ignore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,15 +67,15 @@ object ProjectHandlers {
|
|||||||
System.err.println("Failed to get project details: ${it.msg}")
|
System.err.println("Failed to get project details: ${it.msg}")
|
||||||
}) {
|
}) {
|
||||||
println("Project ${it.owner.name}/${it.name} [${it.id}]:")
|
println("Project ${it.owner.name}/${it.name} [${it.id}]:")
|
||||||
println("${it.usedLabels.size} labels:")
|
println("${it.usedLabels.size} versions:")
|
||||||
it.usedLabels.forEach { l ->
|
it.usedLabels.forEach { v ->
|
||||||
println(" - [${l.id}] ${l.name} (with color ${l.color})")
|
println(" - [${v.id}] VERSION ${v.name} (with color ${v.color}); metadata: ${v.meta}")
|
||||||
}
|
}
|
||||||
val map = it.usedLabels.associateBy { l -> l.id }
|
val map = it.usedLabels.associateBy { l -> l.id }
|
||||||
|
|
||||||
println("\n${it.entries.size} entries:")
|
println("\n${it.entries.size} entries:")
|
||||||
it.entries.forEach { e ->
|
it.entries.forEach { e ->
|
||||||
println(" - [${e.id}] Entry labeled ${map[e.labelId]?.name ?: "<invalid label>"} at ${e.timestamp.fmt()} " +
|
println(" - [${e.id}] ENTRY versioned ${map[e.labelId]?.name ?: "<invalid version>"} at ${e.timestamp.fmt()} " +
|
||||||
"(${e.warmups.size} warmup data points, ${e.measurements.size} measurement data points; in ${e.unit.disp})")
|
"(${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?) {
|
fun newVersion(name: String?, color: String?, project: CLI.Commands.Project.IProjectIdentification?, version: String?) {
|
||||||
val id = ensureProjectIdentification(project)
|
val id = ensureProjectIdentification(project)
|
||||||
val useName = name.maybePrompt("label name") { it }
|
val useName = name.maybePrompt("version name") { it }
|
||||||
val useColor = color.maybePrompt("color") {
|
val useColor = color.maybePrompt("color") {
|
||||||
val check = it.length == 7 && it[0] == '#' && it.substring(1, it.length).all { c -> c.isDigit() || c in "ABCDEF" }
|
val check = it.length == 7 && it[0] == '#' && it.substring(1, it.length).all { c -> c.isDigit() || c in "ABCDEF" }
|
||||||
if(check) it
|
if(check) it
|
||||||
@@ -110,7 +110,7 @@ object ProjectHandlers {
|
|||||||
Client.callRoute(Routes.Version.new, VersionRequest(useName, useColor, useVersion, projectId))
|
Client.callRoute(Routes.Version.new, VersionRequest(useName, useColor, useVersion, projectId))
|
||||||
}
|
}
|
||||||
}.fold({
|
}.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.")
|
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.ID -> versionId.id.value()
|
||||||
is CLI.Commands.Project.VersionName -> Client.callRoute(Routes.Project.get, pId).bind {
|
is CLI.Commands.Project.VersionName -> Client.callRoute(Routes.Project.get, pId).bind {
|
||||||
it.usedLabels.firstOrNull { l -> l.name == versionId.name }?.id?.value()
|
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 }
|
}.map { pId to it }
|
||||||
}.bind { (pId, vId) ->
|
}.bind { (pId, vId) ->
|
||||||
|
|||||||
Reference in New Issue
Block a user