From 3d59b5d7036c35fe484d052ff32dbdc9d0a75cf7 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Mon, 13 Apr 2026 19:17:09 +0200 Subject: fix utf characters in source code (#953) --- src/zen/cmds/workspaces_cmd.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/zen/cmds/workspaces_cmd.cpp') diff --git a/src/zen/cmds/workspaces_cmd.cpp b/src/zen/cmds/workspaces_cmd.cpp index 9e49b464e..1ae2d58fa 100644 --- a/src/zen/cmds/workspaces_cmd.cpp +++ b/src/zen/cmds/workspaces_cmd.cpp @@ -137,7 +137,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("'verb' option is required", m_Options.help()); } - // Parse subcommand permissively — forward unrecognised options to the parent parser. + // Parse subcommand permissively - forward unrecognised options to the parent parser. std::vector SubUnmatched; if (!ParseOptionsPermissive(*SubOption, gsl::narrow(SubCommandArguments.size()), SubCommandArguments.data(), SubUnmatched)) { @@ -403,7 +403,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("'verb' option is required", m_Options.help()); } - // Parse subcommand permissively — forward unrecognised options to the parent parser. + // Parse subcommand permissively - forward unrecognised options to the parent parser. std::vector SubUnmatched; if (!ParseOptionsPermissive(*SubOption, gsl::narrow(SubCommandArguments.size()), SubCommandArguments.data(), SubUnmatched)) { -- cgit v1.2.3 From 27d72af24a8de9a81500e68a0874f1430297b3bc Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Apr 2026 23:52:38 +0200 Subject: Zen CLI common server interface (#920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a common `ZenServiceClient` RAII wrapper for zen CLI commands that interact with a zenserver instance. CLI operations (admin, builds, cache, exec, hub, info, projectstore, trace, ui, version, vfs, workspaces) automatically register sessions so they become visible in the server's session list, and forward log output to the server's session log endpoint. All session HTTP I/O (announce, remove, log batches) runs on a single background worker thread, so CLI startup and shutdown never block on server availability. ### Key changes - **`ZenServiceClient`** — new RAII class that wraps host resolution, HTTP client creation, and session lifecycle (register on connect, remove on exit). Replaces ad-hoc boilerplate across all command files that talk to a server, including the new `trace` subcommands (`start`, `stop`, `status`). - **Async session I/O** — `SessionsServiceClient` now owns a single worker thread and command queue. `Announce()`, `Remove()`, and `UpdateMetadata()` enqueue commands and return immediately. The worker creates one `HttpClient` with a 5-second total timeout, bounding any individual request. Eliminates main-thread stalls when the server is unreachable. - **Session log forwarding** — `SessionLogSink` is a thin enqueuer that posts log messages to the same worker queue (no separate thread or HTTP client). Log levels are serialized as integers; the server-side ingest handles both string and integer formats for backwards compatibility, with bounds checking on integer values. - **Build & projectstore session registration** — Long-running `builds` and projectstore cache (oplog-download) connections register sessions too, making them visible alongside regular CLI command sessions. ### Cleanup - Extract `SetupCacheSession` helper on `StorageInstance` to reduce duplication. - Remove unused `HttpClient` reference in ui command. --- src/zen/cmds/workspaces_cmd.cpp | 54 +++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) (limited to 'src/zen/cmds/workspaces_cmd.cpp') diff --git a/src/zen/cmds/workspaces_cmd.cpp b/src/zen/cmds/workspaces_cmd.cpp index 1ae2d58fa..3ab3b9e04 100644 --- a/src/zen/cmds/workspaces_cmd.cpp +++ b/src/zen/cmds/workspaces_cmd.cpp @@ -2,6 +2,8 @@ #include "workspaces_cmd.h" +#include "zenserviceclient.h" + #include #include #include @@ -159,8 +161,6 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) return; } - m_HostName = ResolveTargetHostSpec(m_HostName); - if (m_SystemRootDir.empty()) { m_SystemRootDir = PickDefaultSystemRootDirectory(); @@ -199,7 +199,8 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if (!m_HostName.empty()) { - HttpClient Http = CreateHttpClient(m_HostName); + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -271,7 +272,8 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if (!m_HostName.empty()) { - HttpClient Http = CreateHttpClient(m_HostName); + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -425,8 +427,6 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** return; } - m_HostName = ResolveTargetHostSpec(m_HostName); - if (m_SystemRootDir.empty()) { m_SystemRootDir = PickDefaultSystemRootDirectory(); @@ -509,7 +509,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** { if (!m_HostName.empty()) { - HttpClient Http = CreateHttpClient(m_HostName); + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -626,7 +627,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** { if (!m_HostName.empty()) { - HttpClient Http = CreateHttpClient(m_HostName); + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -674,12 +676,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** Params.Entries.insert_or_assign("refresh", ToString(m_Refresh)); } - if (m_HostName.empty()) - { - throw OptionParseException("Unable to resolve server specification", SubOption->help()); - } - - HttpClient Http = CreateHttpClient(m_HostName); + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/files", GetShareIdentityUrl(m_FilesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); @@ -707,12 +705,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** Params.Entries.insert_or_assign("refresh", ToString(m_Refresh)); } - if (m_HostName.empty()) - { - throw OptionParseException("Unable to resolve server specification", SubOption->help()); - } - - HttpClient Http = CreateHttpClient(m_HostName); + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/entries", GetShareIdentityUrl(m_EntriesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); @@ -777,18 +771,14 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** if (SubOption == &m_GetChunkOptions) { - if (m_HostName.empty()) - { - throw OptionParseException("Unable to resolve server specification", SubOption->help()); - } - if (m_ChunkId.empty()) { throw OptionParseException("'--chunk' is required", SubOption->help()); } - HttpClient Http = CreateHttpClient(m_HostName); - m_ChunkId = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, std::vector{m_ChunkId})[0]; + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); + m_ChunkId = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, std::vector{m_ChunkId})[0]; HttpClient::KeyValueMap Params; if (m_Offset != 0) @@ -813,11 +803,6 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** if (SubOption == &m_GetChunkBatchOptions) { - if (m_HostName.empty()) - { - throw OptionParseException("Unable to resolve server specification", SubOption->help()); - } - if (m_ShareId.empty()) { throw OptionParseException("'--share' is required", SubOption->help()); @@ -828,8 +813,9 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("'--chunks' is required", SubOption->help()); } - HttpClient Http = CreateHttpClient(m_HostName); - m_ChunkIds = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, m_ChunkIds); + ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = Name}); + HttpClient& Http = Service.Http(); + m_ChunkIds = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, m_ChunkIds); std::vector ChunkRequests; ChunkRequests.resize(m_ChunkIds.size()); -- cgit v1.2.3