// Copyright Epic Games, Inc. All Rights Reserved. #include "workspaces_cmd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zen { namespace { static void RemoveTrailingPathSeparator(std::filesystem::path& Path) { if (!Path.empty()) { std::u8string PathString = Path.u8string(); if (PathString.ends_with(std::filesystem::path::preferred_separator) || PathString.starts_with('/')) { PathString.pop_back(); Path = std::filesystem::path(PathString); } } } static void RemoveLeadingPathSeparator(std::filesystem::path& Path) { if (!Path.empty()) { std::u8string PathString = Path.u8string(); if (PathString.starts_with(std::filesystem::path::preferred_separator) || PathString.starts_with('/')) { PathString.erase(PathString.begin()); Path = std::filesystem::path(PathString); } } } void ShowShare(const Workspaces::WorkspaceShareConfiguration& Share, const Oid& WorkspaceId, std::string_view Prefix) { ZEN_CONSOLE("{}Id: {}", Prefix, Share.Id); ZEN_CONSOLE("{} Path: {}", Prefix, Share.SharePath); if (!Share.Alias.empty()) { ZEN_CONSOLE("{} Alias: {}", Prefix, Share.Alias); } if (WorkspaceId != Oid::Zero) { ZEN_CONSOLE("{} Workspace: {}", Prefix, WorkspaceId); } }; void ShowWorkspace(const Workspaces::WorkspaceConfiguration& Workspace, std::string_view Prefix) { ZEN_CONSOLE("{}Id: {}", Prefix, Workspace.Id); ZEN_CONSOLE("{} Root: {}", Prefix, Workspace.RootPath); ZEN_CONSOLE("{} AllowHttpShares: {}", Prefix, Workspace.AllowShareCreationFromHttp); std::string Error; std::vector Shares = Workspaces::ReadWorkspaceConfig(Log(), Workspace.RootPath, Error); if (!Error.empty()) { ZEN_CONSOLE("{}Failed to read shares from workspace {}. Reason: '{}'", Prefix, Workspace.Id, Error); } else { ZEN_CONSOLE("{} Shares: {}", Prefix, Shares.size()); for (const Workspaces::WorkspaceShareConfiguration& Share : Shares) { ShowShare(Share, Oid::Zero, fmt::format("{} ", Prefix)); } } }; std::vector ChunksToOidStrings(HttpClient& Http, std::string_view WorkspaceId, std::string_view ShareId, std::span ChunkIds) { using namespace std::literals; std::vector Oids; Oids.reserve(ChunkIds.size()); std::vector NeedsConvertIndexes; for (const std::string& StringChunkId : ChunkIds) { Oid ChunkId = Oid::TryFromHexString(StringChunkId); if (ChunkId == Oid::Zero) { NeedsConvertIndexes.push_back(Oids.size()); } Oids.push_back(ChunkId.ToString()); } if (!NeedsConvertIndexes.empty()) { if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId), {}, HttpClient::KeyValueMap{{"fieldnames", "id,clientpath"}})) { std::unordered_map PathToOid; CbObjectView ResultObj = Result.AsObject(); for (CbFieldView EntryView : ResultObj["files"sv]) { CbObjectView Entry = EntryView.AsObjectView(); PathToOid[std::string(Entry["clientpath"sv].AsString())] = Entry["id"sv].AsObjectId(); } for (size_t PathIndex : NeedsConvertIndexes) { if (auto It = PathToOid.find(ChunkIds[PathIndex]); It != PathToOid.end()) { Oids[PathIndex] = It->second.ToString(); ZEN_CONSOLE("Converted path '{}' to id '{}'", ChunkIds[PathIndex], Oids[PathIndex]); } else { Result.ThrowError( fmt::format("unable to resolve path {} workspace {}, share {}"sv, ChunkIds[PathIndex], WorkspaceId, ShareId)); } } } else { Result.ThrowError("failed to get workspace share file list to resolve paths"sv); } } return Oids; } } // namespace ////////////////////////////////////////////////////////////////////////// // WorkspaceCommand WorkspaceCommand::WorkspaceCommand() { m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("__hidden__", "", "subcommand", "", cxxopts::value(m_SubCommand)->default_value(""), ""); m_Options.parse_positional({"subcommand"}); AddSubCommand(m_CreateSubCmd); AddSubCommand(m_InfoSubCmd); AddSubCommand(m_RemoveSubCmd); } WorkspaceCommand::~WorkspaceCommand() = default; bool WorkspaceCommand::OnParentOptionsParsed(const ZenCliOptions& GlobalOptions) { ZEN_UNUSED(GlobalOptions); m_HostName = ResolveTargetHostSpec(m_HostName); if (m_SystemRootDir.empty()) { m_SystemRootDir = PickDefaultSystemRootDirectory(); if (m_SystemRootDir.empty()) { throw std::runtime_error("Unable to resolve default system root directory"); } } m_StatePath = m_SystemRootDir / "workspaces"; return true; } ////////////////////////////////////////////////////////////////////////// // WorkspaceCreateSubCmd WorkspaceCreateSubCmd::WorkspaceCreateSubCmd(WorkspaceCommand& Parent) : ZenSubCmdBase("create", "Create a workspace"), m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity(id)", cxxopts::value(m_Parent.m_Id), ""); SubOptions().add_option("", "r", "root-path", "Root file system folder for workspace", cxxopts::value(m_Parent.m_Path), ""); SubOptions().add_option("", "", "allow-share-create-from-http", "Allow create and delete inside this workspace using the http API. Defaults to false", cxxopts::value(m_Parent.m_AllowShareCreationFromHttp), ""); SubOptions().parse_positional({"root-path", "workspace"}); SubOptions().positional_help("root-path workspace"); } void WorkspaceCreateSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (m_Parent.m_Path.empty()) { throw OptionParseException("'--root-path' is required", SubOptions().help()); } std::filesystem::path Path = StringToPath(m_Parent.m_Path); if (m_Parent.m_Id.empty()) { m_Parent.m_Id = Workspaces::PathToId(Path).ToString(); ZEN_CONSOLE("Using generated workspace id '{}' from '--root-path' '{}'", m_Parent.m_Id, Path); } if (Oid::TryFromHexString(m_Parent.m_Id) == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_Id), SubOptions().help()); } if (Workspaces::AddWorkspace(Log(), m_Parent.m_StatePath, {.Id = Oid::FromHexString(m_Parent.m_Id), .RootPath = Path, .AllowShareCreationFromHttp = m_Parent.m_AllowShareCreationFromHttp})) { if (!m_Parent.m_HostName.empty()) { HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_Parent.m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Added/updated workspace {}", m_Parent.m_Id); } else { ZEN_CONSOLE_WARN("Workspace {} already exists", m_Parent.m_Id); } } ////////////////////////////////////////////////////////////////////////// // WorkspaceInfoSubCmd WorkspaceInfoSubCmd::WorkspaceInfoSubCmd(WorkspaceCommand& Parent) : ZenSubCmdBase("info", "Info about a workspace"), m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity(id)", cxxopts::value(m_Parent.m_Id), ""); SubOptions().parse_positional({"workspace"}); SubOptions().positional_help("workspace"); } void WorkspaceInfoSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (m_Parent.m_Id.empty()) { std::string Error; static std::vector Configs = Workspaces::ReadConfig(Log(), m_Parent.m_StatePath, Error); if (!Error.empty()) { throw std::runtime_error(fmt::format("Failed to read workspaces state from '{}'. Reason: '{}'", m_Parent.m_StatePath, Error)); } else { ZEN_CONSOLE("Workspaces: {}", Configs.size()); for (const Workspaces::WorkspaceConfiguration& Config : Configs) { ShowWorkspace(Config, " "sv); } } } else { if (Oid::TryFromHexString(m_Parent.m_Id) == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_Id), SubOptions().help()); } Workspaces::WorkspaceConfiguration Workspace = Workspaces::FindWorkspace(Log(), m_Parent.m_StatePath, Oid::FromHexString(m_Parent.m_Id)); if (Workspace.Id != Oid::Zero) { ShowWorkspace(Workspace, ""sv); } else { ZEN_CONSOLE_WARN("Workspace {} not found", m_Parent.m_Id); } } } ////////////////////////////////////////////////////////////////////////// // WorkspaceRemoveSubCmd WorkspaceRemoveSubCmd::WorkspaceRemoveSubCmd(WorkspaceCommand& Parent) : ZenSubCmdBase("remove", "Remove a workspace"), m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity(id)", cxxopts::value(m_Parent.m_Id), ""); SubOptions().parse_positional({"workspace"}); SubOptions().positional_help("workspace"); } void WorkspaceRemoveSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (m_Parent.m_Id.empty()) { throw OptionParseException("'--workspace' is required", SubOptions().help()); } if (Oid::TryFromHexString(m_Parent.m_Id) == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_Id), SubOptions().help()); } if (Workspaces::RemoveWorkspace(Log(), m_Parent.m_StatePath, Oid::FromHexString(m_Parent.m_Id))) { if (!m_Parent.m_HostName.empty()) { HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_Parent.m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Removed workspace {}", m_Parent.m_Id); } else { ZEN_CONSOLE_WARN("Workspace {} does not exist", m_Parent.m_Id); } } ////////////////////////////////////////////////////////////////////////// // WorkspaceShareCommand WorkspaceShareCommand::WorkspaceShareCommand() { m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("__hidden__", "", "subcommand", "", cxxopts::value(m_SubCommand)->default_value(""), ""); m_Options.parse_positional({"subcommand"}); AddSubCommand(m_CreateSubCmd); AddSubCommand(m_InfoSubCmd); AddSubCommand(m_RemoveSubCmd); AddSubCommand(m_FilesSubCmd); AddSubCommand(m_EntriesSubCmd); AddSubCommand(m_GetSubCmd); AddSubCommand(m_BatchSubCmd); } WorkspaceShareCommand::~WorkspaceShareCommand() = default; bool WorkspaceShareCommand::OnParentOptionsParsed(const ZenCliOptions& GlobalOptions) { ZEN_UNUSED(GlobalOptions); m_HostName = ResolveTargetHostSpec(m_HostName); if (m_SystemRootDir.empty()) { m_SystemRootDir = PickDefaultSystemRootDirectory(); if (m_SystemRootDir.empty()) { throw std::runtime_error("Unable to resolve default system root directory"); } } else { MakeSafeAbsolutePathInPlace(m_SystemRootDir); } m_StatePath = m_SystemRootDir / "workspaces"; return true; } std::string WorkspaceShareCommand::GetShareIdentityUrl() const { if (m_Alias.empty()) { return fmt::format("{}/{}", m_WorkspaceId, m_ShareId); } else { return fmt::format("share/{}", m_Alias); } } ////////////////////////////////////////////////////////////////////////// // WsShareCreateSubCmd WsShareCreateSubCmd::WsShareCreateSubCmd(WorkspaceShareCommand& Parent) : ZenSubCmdBase("create", "Create a workspace share") , m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_Parent.m_WorkspaceId), ""); SubOptions().add_option("", "r", "root-path", "Root path for workspace, replaces 'workspace' id", cxxopts::value(m_Parent.m_WorkspaceRoot), ""); SubOptions().add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_Parent.m_ShareId), ""); SubOptions().add_option("", "p", "share-path", "Folder path inside the workspace to share", cxxopts::value(m_Parent.m_SharePath), ""); SubOptions().add_option("", "a", "alias", "Named alias for this share", cxxopts::value(m_Parent.m_Alias), ""); SubOptions().parse_positional({"workspace", "share-path", "share"}); SubOptions().positional_help("workspace share-path share"); } void WsShareCreateSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (m_Parent.m_WorkspaceRoot.empty()) { if (m_Parent.m_WorkspaceId.empty()) { throw OptionParseException("'--workspace' or '--root-path' is required", SubOptions().help()); } Oid WorkspaceId = Oid::TryFromHexString(m_Parent.m_WorkspaceId); if (WorkspaceId == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_WorkspaceId), SubOptions().help()); } Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), m_Parent.m_StatePath, WorkspaceId); if (WorkspaceConfig.Id == Oid::Zero) { throw std::runtime_error(fmt::format("Workspace {} does not exist", m_Parent.m_WorkspaceId)); } m_Parent.m_WorkspaceRoot = WorkspaceConfig.RootPath; } else { RemoveTrailingPathSeparator(m_Parent.m_WorkspaceRoot); if (m_Parent.m_WorkspaceId.empty()) { m_Parent.m_WorkspaceId = Workspaces::PathToId(m_Parent.m_WorkspaceRoot).ToString(); ZEN_CONSOLE("Using generated workspace id {} from path '{}'", m_Parent.m_WorkspaceId, m_Parent.m_WorkspaceRoot); } else { if (Oid::TryFromHexString(m_Parent.m_WorkspaceId) == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_WorkspaceId), SubOptions().help()); } } if (Workspaces::AddWorkspace(Log(), m_Parent.m_StatePath, {.Id = Oid::FromHexString(m_Parent.m_WorkspaceId), .RootPath = m_Parent.m_WorkspaceRoot})) { ZEN_CONSOLE("Created workspace {} using root path '{}'", m_Parent.m_WorkspaceId, m_Parent.m_WorkspaceRoot); } else { ZEN_CONSOLE("Using existing workspace {} with root path '{}'", m_Parent.m_WorkspaceId, m_Parent.m_WorkspaceRoot); } } RemoveLeadingPathSeparator(m_Parent.m_SharePath); RemoveTrailingPathSeparator(m_Parent.m_SharePath); if (m_Parent.m_ShareId.empty()) { m_Parent.m_ShareId = Workspaces::PathToId(m_Parent.m_SharePath).ToString(); ZEN_CONSOLE("Using generated share id {}, for path '{}'", m_Parent.m_ShareId, m_Parent.m_SharePath); } if (Oid::TryFromHexString(m_Parent.m_ShareId) == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_ShareId), SubOptions().help()); } if (Workspaces::AddWorkspaceShare( Log(), m_Parent.m_WorkspaceRoot, {.Id = Oid::FromHexString(m_Parent.m_ShareId), .SharePath = m_Parent.m_SharePath, .Alias = m_Parent.m_Alias})) { if (!m_Parent.m_HostName.empty()) { HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_Parent.m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Created workspace share {}", m_Parent.m_ShareId); } else { ZEN_CONSOLE("Workspace share {} already exists", m_Parent.m_ShareId); } } ////////////////////////////////////////////////////////////////////////// // WsShareInfoSubCmd WsShareInfoSubCmd::WsShareInfoSubCmd(WorkspaceShareCommand& Parent) : ZenSubCmdBase("info", "Info about a workspace share") , m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_Parent.m_WorkspaceId), ""); SubOptions().add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_Parent.m_ShareId), ""); SubOptions().add_option("", "r", "refresh", "Refresh workspace share", cxxopts::value(m_Parent.m_Refresh), ""); SubOptions().add_option("", "a", "alias", "Named alias for this share", cxxopts::value(m_Parent.m_Alias), ""); SubOptions().parse_positional({"workspace", "share"}); SubOptions().positional_help("workspace share"); } void WsShareInfoSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (!m_Parent.m_Alias.empty()) { Workspaces::WorkspaceConfiguration WorkspaceConfig; Workspaces::WorkspaceShareConfiguration ShareConfig = Workspaces::FindWorkspaceShare(Log(), m_Parent.m_StatePath, m_Parent.m_Alias, WorkspaceConfig); if (ShareConfig.Id == Oid::Zero) { throw std::runtime_error(fmt::format("Workspace share with alias {} does not exist", m_Parent.m_Alias)); } ShowShare(ShareConfig, WorkspaceConfig.Id, ""sv); return; } if (m_Parent.m_WorkspaceId.empty()) { throw OptionParseException("'--workspace' or '--root-path' is required", SubOptions().help()); } if (Oid::TryFromHexString(m_Parent.m_WorkspaceId) == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_WorkspaceId), SubOptions().help()); } Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), m_Parent.m_StatePath, Oid::FromHexString(m_Parent.m_WorkspaceId)); if (WorkspaceConfig.Id == Oid::Zero) { throw std::runtime_error(fmt::format("Workspace {} does not exist", m_Parent.m_WorkspaceId)); } std::filesystem::path WorkspaceRoot = WorkspaceConfig.RootPath; if (m_Parent.m_ShareId.empty()) { throw OptionParseException("'--share' is required", SubOptions().help()); } if (Oid::TryFromHexString(m_Parent.m_ShareId) == Oid::Zero) { throw OptionParseException(fmt::format("'--share' ('{}') is malformed", m_Parent.m_ShareId), SubOptions().help()); } Workspaces::WorkspaceShareConfiguration Share = Workspaces::FindWorkspaceShare(Log(), WorkspaceRoot, Oid::FromHexString(m_Parent.m_ShareId)); if (Share.Id == Oid::Zero) { throw std::runtime_error(fmt::format("Workspace share {} does not exist", m_Parent.m_ShareId)); } ShowShare(Share, Oid::Zero, ""sv); } ////////////////////////////////////////////////////////////////////////// // WsShareRemoveSubCmd WsShareRemoveSubCmd::WsShareRemoveSubCmd(WorkspaceShareCommand& Parent) : ZenSubCmdBase("remove", "Remove a workspace share") , m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_Parent.m_WorkspaceId), ""); SubOptions().add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_Parent.m_ShareId), ""); SubOptions().add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Parent.m_Alias), ""); SubOptions().parse_positional({"workspace", "share"}); SubOptions().positional_help("workspace share"); } void WsShareRemoveSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); std::filesystem::path WorkspaceRoot; if (!m_Parent.m_Alias.empty()) { Workspaces::WorkspaceConfiguration WorkspaceConfig; Workspaces::WorkspaceShareConfiguration ShareConfig = Workspaces::FindWorkspaceShare(Log(), m_Parent.m_StatePath, m_Parent.m_Alias, WorkspaceConfig); if (ShareConfig.Id == Oid::Zero) { ZEN_CONSOLE("Workspace share with alias {} does not exist", m_Parent.m_Alias); return; } m_Parent.m_ShareId = ShareConfig.Id.ToString(); m_Parent.m_WorkspaceId = WorkspaceConfig.Id.ToString(); WorkspaceRoot = WorkspaceConfig.RootPath; } if (m_Parent.m_WorkspaceId.empty()) { throw OptionParseException("'--workspace' or '--root-path' is required", SubOptions().help()); } if (Oid::TryFromHexString(m_Parent.m_WorkspaceId) == Oid::Zero) { throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Parent.m_WorkspaceId), SubOptions().help()); } Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), m_Parent.m_StatePath, Oid::FromHexString(m_Parent.m_WorkspaceId)); if (WorkspaceConfig.Id == Oid::Zero) { ZEN_CONSOLE("Workspace {} does not exist", m_Parent.m_WorkspaceId); return; } WorkspaceRoot = WorkspaceConfig.RootPath; if (m_Parent.m_ShareId.empty()) { throw OptionParseException("'--share' is required", SubOptions().help()); } if (Oid::TryFromHexString(m_Parent.m_ShareId) == Oid::Zero) { throw OptionParseException(fmt::format("'--share' ('{}') is malformed", m_Parent.m_ShareId), SubOptions().help()); } if (Workspaces::RemoveWorkspaceShare(Log(), WorkspaceRoot, Oid::FromHexString(m_Parent.m_ShareId))) { if (!m_Parent.m_HostName.empty()) { HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_Parent.m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Removed workspace share {}", m_Parent.m_ShareId); } else { ZEN_CONSOLE("Removed workspace share {} does not exist", m_Parent.m_ShareId); } } ////////////////////////////////////////////////////////////////////////// // WsShareFilesSubCmd WsShareFilesSubCmd::WsShareFilesSubCmd(WorkspaceShareCommand& Parent) : ZenSubCmdBase("files", "List files in a workspace share") , m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_Parent.m_WorkspaceId), ""); SubOptions().add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_Parent.m_ShareId), ""); SubOptions().add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Parent.m_Alias), ""); SubOptions().add_option("", "", "filter", "A list of comma separated fields to include in the response - empty means all", cxxopts::value(m_Parent.m_FieldFilter), ""); SubOptions().add_option("", "r", "refresh", "Refresh workspace share", cxxopts::value(m_Parent.m_Refresh), ""); SubOptions().parse_positional({"workspace", "share"}); SubOptions().positional_help("workspace share"); } void WsShareFilesSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); HttpClient::KeyValueMap Params; if (!m_Parent.m_FieldFilter.empty()) { Params.Entries.insert_or_assign("fieldnames", m_Parent.m_FieldFilter); } if (m_Parent.m_Refresh) { Params.Entries.insert_or_assign("refresh", ToString(m_Parent.m_Refresh)); } if (m_Parent.m_HostName.empty()) { throw OptionParseException("Unable to resolve server specification", SubOptions().help()); } HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/files", m_Parent.GetShareIdentityUrl()), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); } else { Result.ThrowError("failed to get workspace share files"sv); } } ////////////////////////////////////////////////////////////////////////// // WsShareEntriesSubCmd WsShareEntriesSubCmd::WsShareEntriesSubCmd(WorkspaceShareCommand& Parent) : ZenSubCmdBase("entries", "List entries in a workspace shared folder") , m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_Parent.m_WorkspaceId), ""); SubOptions().add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_Parent.m_ShareId), ""); SubOptions().add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Parent.m_Alias), ""); SubOptions().add_option("", "", "filter", "A list of comma separated fields to include in the response - empty means all", cxxopts::value(m_Parent.m_FieldFilter), ""); SubOptions().add_option("", "", "opkey", "Filter the query to a particular key (id)", cxxopts::value(m_Parent.m_ChunkId), ""); SubOptions().add_option("", "r", "refresh", "Refresh workspace share", cxxopts::value(m_Parent.m_Refresh), ""); SubOptions().parse_positional({"workspace", "share", "opkey"}); SubOptions().positional_help("workspace share opkey"); } void WsShareEntriesSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); HttpClient::KeyValueMap Params; if (!m_Parent.m_ChunkId.empty()) { Params.Entries.insert_or_assign("opkey", m_Parent.m_ChunkId); } if (!m_Parent.m_FieldFilter.empty()) { Params.Entries.insert_or_assign("fieldfilter", m_Parent.m_FieldFilter); } if (m_Parent.m_Refresh) { Params.Entries.insert_or_assign("refresh", ToString(m_Parent.m_Refresh)); } if (m_Parent.m_HostName.empty()) { throw OptionParseException("Unable to resolve server specification", SubOptions().help()); } HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/entries", m_Parent.GetShareIdentityUrl()), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); } else { Result.ThrowError("failed to get workspace share entries"sv); } } ////////////////////////////////////////////////////////////////////////// // WsShareGetSubCmd WsShareGetSubCmd::WsShareGetSubCmd(WorkspaceShareCommand& Parent) : ZenSubCmdBase("get", "Get a chunk from a workspace shared folder") , m_Parent(Parent) { SubOptions().add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_Parent.m_WorkspaceId), ""); SubOptions().add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_Parent.m_ShareId), ""); SubOptions().add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Parent.m_Alias), ""); SubOptions().add_option("", "c", "chunk", "Chunk identity (id)", cxxopts::value(m_Parent.m_ChunkId), ""); SubOptions().add_option("", "", "offset", "Offset in chunk", cxxopts::value(m_Parent.m_Offset), ""); SubOptions().add_option("", "", "size", "Size of chunk", cxxopts::value(m_Parent.m_Size), ""); SubOptions().parse_positional({"workspace", "share", "chunk"}); SubOptions().positional_help("workspace share chunk"); } void WsShareGetSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (m_Parent.m_HostName.empty()) { throw OptionParseException("Unable to resolve server specification", SubOptions().help()); } if (m_Parent.m_ChunkId.empty()) { throw OptionParseException("'--chunk' is required", SubOptions().help()); } HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); m_Parent.m_ChunkId = ChunksToOidStrings(Http, m_Parent.m_WorkspaceId, m_Parent.m_ShareId, std::vector{m_Parent.m_ChunkId})[0]; HttpClient::KeyValueMap Params; if (m_Parent.m_Offset != 0) { Params.Entries.insert_or_assign("offset", fmt::format("{}", m_Parent.m_Offset)); } if (m_Parent.m_Size != ~uint64_t(0)) { Params.Entries.insert_or_assign("size", fmt::format("{}", m_Parent.m_Size)); } if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}", m_Parent.GetShareIdentityUrl(), m_Parent.m_ChunkId), {}, Params)) { ZEN_CONSOLE("{}: Bytes: {}", Result, NiceBytes(Result.ResponsePayload.GetSize())); } else { Result.ThrowError("failed to get workspace share chunk"sv); } } ////////////////////////////////////////////////////////////////////////// // WsShareBatchSubCmd WsShareBatchSubCmd::WsShareBatchSubCmd(WorkspaceShareCommand& Parent) : ZenSubCmdBase("batch", "Get a batch of chunks from a workspace shared folder") , m_Parent(Parent) { SubOptions().add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_Parent.m_ShareId), ""); SubOptions().add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_Parent.m_WorkspaceId), ""); SubOptions().add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Parent.m_Alias), ""); SubOptions().add_option("", "", "chunks", "A list of identities (id)", cxxopts::value(m_Parent.m_ChunkIds), ""); SubOptions().parse_positional({"workspace", "share", "chunks"}); SubOptions().positional_help("workspace share chunks"); } void WsShareBatchSubCmd::Run(const ZenCliOptions& GlobalOptions) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (m_Parent.m_HostName.empty()) { throw OptionParseException("Unable to resolve server specification", SubOptions().help()); } if (m_Parent.m_ShareId.empty()) { throw OptionParseException("'--share' is required", SubOptions().help()); } if (m_Parent.m_ChunkIds.empty()) { throw OptionParseException("'--chunks' is required", SubOptions().help()); } HttpClient Http = ZenCmdBase::CreateHttpClient(m_Parent.m_HostName); m_Parent.m_ChunkIds = ChunksToOidStrings(Http, m_Parent.m_WorkspaceId, m_Parent.m_ShareId, m_Parent.m_ChunkIds); std::vector ChunkRequests; ChunkRequests.resize(m_Parent.m_ChunkIds.size()); for (size_t Index = 0; Index < m_Parent.m_ChunkIds.size(); Index++) { ChunkRequests[Index] = RequestChunkEntry{.ChunkId = Oid::FromHexString(m_Parent.m_ChunkIds[Index]), .CorrelationId = gsl::narrow(Index), .Offset = 0, .RequestBytes = uint64_t(-1)}; } IoBuffer Payload = BuildChunkBatchRequest(ChunkRequests); if (HttpClient::Response Result = Http.Post(fmt::format("/ws/{}/batch", m_Parent.GetShareIdentityUrl()), Payload)) { ZEN_CONSOLE("{}: Bytes: {}", Result, NiceBytes(Result.ResponsePayload.GetSize())); std::vector Results = ParseChunkBatchResponse(Result.ResponsePayload); if (Results.size() != m_Parent.m_ChunkIds.size()) { throw std::runtime_error( fmt::format("failed to get workspace share batch - invalid result count received (expected: {}, received: {}", m_Parent.m_ChunkIds.size(), Results.size())); } for (size_t Index = 0; Index < m_Parent.m_ChunkIds.size(); Index++) { ZEN_CONSOLE("{}: Bytes: {}", m_Parent.m_ChunkIds[Index], NiceBytes(Results[Index].GetSize())); } } else { Result.ThrowError("failed to get workspace share batch"sv); } } } // namespace zen