// 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)); } } }; } // namespace WorkspaceCommand::WorkspaceCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("", "v", "verb", "Verb for workspace - create, remove, info", cxxopts::value(m_Verb), ""); m_Options.parse_positional({"verb"}); m_Options.positional_help("verb"); m_CreateOptions.add_options()("h,help", "Print help"); m_CreateOptions.add_option("", "w", "workspace", "Workspace identity(id)", cxxopts::value(m_Id), ""); m_CreateOptions.add_option("", "r", "root-path", "Root file system folder for workspace", cxxopts::value(m_Path), ""); m_CreateOptions.add_option("", "", "allow-share-create-from-http", "Allow create and delete inside this workspace using the http API. Defaults to false", cxxopts::value(m_AllowShareCreationFromHttp), ""); m_CreateOptions.parse_positional({"root-path", "workspace"}); m_CreateOptions.positional_help("root-path workspace"); m_InfoOptions.add_options()("h,help", "Print help"); m_InfoOptions.add_option("", "w", "workspace", "Workspace identity(id)", cxxopts::value(m_Id), ""); m_InfoOptions.parse_positional({"workspace"}); m_InfoOptions.positional_help("workspace"); m_RemoveOptions.add_options()("h,help", "Print help"); m_RemoveOptions.add_option("", "w", "workspace", "Workspace identity(id)", cxxopts::value(m_Id), ""); m_RemoveOptions.parse_positional({"workspace"}); m_InfoOptions.positional_help("workspace"); } WorkspaceCommand::~WorkspaceCommand() = default; int WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); using namespace std::literals; std::vector SubCommandArguments; cxxopts::Options* SubOption = nullptr; int ParentCommandArgCount = GetSubCommand(m_Options, argc, argv, m_SubCommands, SubOption, SubCommandArguments); if (!ParseOptions(ParentCommandArgCount, argv)) { return 0; } if (SubOption == nullptr) { throw zen::OptionParseException("command verb is missing"); } m_HostName = ResolveTargetHostSpec(m_HostName); if (m_SystemRootDir.empty()) { m_SystemRootDir = PickDefaultSystemRootDirectory(); if (m_SystemRootDir.empty()) { throw zen::OptionParseException("unable to resolve system root directory"); } } std::filesystem::path StatePath = m_SystemRootDir / "workspaces"; if (!ParseOptions(*SubOption, gsl::narrow(SubCommandArguments.size()), SubCommandArguments.data())) { return 0; } if (SubOption == &m_CreateOptions) { if (m_Path.empty()) { throw zen::OptionParseException(fmt::format("path is required\n{}", m_CreateOptions.help())); } std::filesystem::path Path = StringToPath(m_Path); if (m_Id.empty()) { m_Id = Workspaces::PathToId(Path).ToString(); ZEN_CONSOLE("Using generated workspace id {} from path '{}'", m_Id, Path); } if (Oid::TryFromHexString(m_Id) == Oid::Zero) { throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_Id)); } if (Workspaces::AddWorkspace( Log(), StatePath, {.Id = Oid::FromHexString(m_Id), .RootPath = Path, .AllowShareCreationFromHttp = m_AllowShareCreationFromHttp})) { if (!m_HostName.empty()) { HttpClient Http(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Added/updated workspace {}", m_Id); return 0; } else { ZEN_CONSOLE("Workspace {} already exists", m_Id); return 0; } } if (SubOption == &m_InfoOptions) { if (m_Id.empty()) { std::string Error; static std::vector Configs = Workspaces::ReadConfig(Log(), StatePath, Error); if (!Error.empty()) { ZEN_CONSOLE("Failed to read workspaces state from '{}'. Reason: '{}'", StatePath, Error); } else { ZEN_CONSOLE("Workspaces: {}", Configs.size()); for (const Workspaces::WorkspaceConfiguration& Config : Configs) { ShowWorkspace(Config, " "sv); } } return 0; } else { if (Oid::TryFromHexString(m_Id) == Oid::Zero) { throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_Id)); } Workspaces::WorkspaceConfiguration Workspace = Workspaces::FindWorkspace(Log(), StatePath, Oid::FromHexString(m_Id)); if (Workspace.Id != Oid::Zero) { ShowWorkspace(Workspace, ""sv); return 0; } else { ZEN_CONSOLE("Workspace {} not found", m_Id); } } } if (SubOption == &m_RemoveOptions) { if (m_Id.empty()) { throw zen::OptionParseException(fmt::format("id is required", m_RemoveOptions.help())); } if (Oid::TryFromHexString(m_Id) == Oid::Zero) { throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_Id)); } if (Workspaces::RemoveWorkspace(Log(), StatePath, Oid::FromHexString(m_Id))) { if (!m_HostName.empty()) { HttpClient Http(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Removed workspace {}", m_Id); } else { ZEN_CONSOLE("Workspace {} does not exist", m_Id); } return 0; } ZEN_ASSERT(false); } ///////////////////////////////////////////////////////////////////////// WorkspaceShareCommand::WorkspaceShareCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("", "v", "verb", "Verb for workspace - create, remove, info", cxxopts::value(m_Verb), ""); m_Options.parse_positional({"verb"}); m_Options.positional_help("verb"); m_CreateOptions.add_options()("h,help", "Print help"); m_CreateOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), ""); m_CreateOptions.add_option("", "r", "root-path", "Root path for workspace, replaces 'workspace' id", cxxopts::value(m_WorkspaceRoot), ""); m_CreateOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), ""); m_CreateOptions .add_option("", "p", "share-path", "Folder path inside the workspace to share", cxxopts::value(m_SharePath), ""); m_CreateOptions.add_option("", "a", "alias", "Named alias for this share", cxxopts::value(m_Alias), ""); m_CreateOptions.parse_positional({"workspace", "share-path", "share"}); m_CreateOptions.positional_help("workspace share-path share"); m_InfoOptions.add_options()("h,help", "Print help"); m_InfoOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), ""); m_InfoOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), ""); m_InfoOptions.add_option("", "r", "refresh", "Refresh workspace share", cxxopts::value(m_Refresh), ""); m_InfoOptions.add_option("", "a", "alias", "Named alias for this share", cxxopts::value(m_Alias), ""); m_InfoOptions.parse_positional({"workspace", "share"}); m_InfoOptions.positional_help("workspace share"); m_RemoveOptions.add_options()("h,help", "Print help"); m_RemoveOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), ""); m_RemoveOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), ""); m_RemoveOptions .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), ""); m_RemoveOptions.parse_positional({"workspace", "share"}); m_RemoveOptions.positional_help("workspace share"); m_FilesOptions.add_options()("h,help", "Print help"); m_FilesOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), ""); m_FilesOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), ""); m_FilesOptions .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), ""); m_FilesOptions.add_option("", "", "filter", "A list of comma separated fields to include in the response - empty means all", cxxopts::value(m_FieldFilter), ""); m_FilesOptions.add_option("", "r", "refresh", "Refresh workspace share", cxxopts::value(m_Refresh), ""); m_FilesOptions.parse_positional({"workspace", "share"}); m_FilesOptions.positional_help("workspace share"); m_EntriesOptions.add_options()("h,help", "Print help"); m_EntriesOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), ""); m_EntriesOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), ""); m_EntriesOptions .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), ""); m_EntriesOptions.add_option("", "", "filter", "A list of comma separated fields to include in the response - empty means all", cxxopts::value(m_FieldFilter), ""); m_EntriesOptions.add_option("", "", "opkey", "Filter the query to a particular key (id)", cxxopts::value(m_ChunkId), ""); m_EntriesOptions.add_option("", "r", "refresh", "Refresh workspace share", cxxopts::value(m_Refresh), ""); m_EntriesOptions.parse_positional({"workspace", "share", "opkey"}); m_EntriesOptions.positional_help("workspace share opkey"); m_GetChunkOptions.add_options()("h,help", "Print help"); m_GetChunkOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), ""); m_GetChunkOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), ""); m_GetChunkOptions .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), ""); m_GetChunkOptions.add_option("", "c", "chunk", "Chunk identity (id)", cxxopts::value(m_ChunkId), ""); m_GetChunkOptions.add_option("", "", "offset", "Offset in chunk", cxxopts::value(m_Offset), ""); m_GetChunkOptions.add_option("", "", "size", "Size of chunk", cxxopts::value(m_Size), ""); m_GetChunkOptions.parse_positional({"workspace", "share", "chunk"}); m_GetChunkOptions.positional_help("workspace share chunk"); m_GetChunkBatchOptions.add_options()("h,help", "Print help"); m_GetChunkBatchOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), ""); m_GetChunkBatchOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), ""); m_GetChunkBatchOptions .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), ""); m_GetChunkBatchOptions.add_option("", "", "chunks", "A list of identities (id)", cxxopts::value(m_ChunkIds), ""); m_GetChunkBatchOptions.parse_positional({"workspace", "share", "chunks"}); m_GetChunkBatchOptions.positional_help("workspace share chunks"); } WorkspaceShareCommand::~WorkspaceShareCommand() = default; int WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); using namespace std::literals; std::vector SubCommandArguments; cxxopts::Options* SubOption = nullptr; int ParentCommandArgCount = GetSubCommand(m_Options, argc, argv, m_SubCommands, SubOption, SubCommandArguments); if (!ParseOptions(ParentCommandArgCount, argv)) { return 0; } if (SubOption == nullptr) { throw zen::OptionParseException("command verb is missing"); } m_HostName = ResolveTargetHostSpec(m_HostName); if (m_SystemRootDir.empty()) { m_SystemRootDir = PickDefaultSystemRootDirectory(); if (m_SystemRootDir.empty()) { throw zen::OptionParseException("unable to resolve system root directory"); } } else { MakeSafeAbsolutePathÍnPlace(m_SystemRootDir); } std::filesystem::path StatePath = m_SystemRootDir / "workspaces"; if (!ParseOptions(*SubOption, gsl::narrow(SubCommandArguments.size()), SubCommandArguments.data())) { return 0; } if (SubOption == &m_CreateOptions) { if (m_WorkspaceRoot.empty()) { if (m_WorkspaceId.empty()) { throw zen::OptionParseException("workspace id or root path is required"); } Oid WorkspaceId = Oid::TryFromHexString(m_WorkspaceId); if (WorkspaceId == Oid::Zero) { throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_WorkspaceId)); } Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), StatePath, WorkspaceId); if (WorkspaceConfig.Id == Oid::Zero) { ZEN_CONSOLE("Workspace {} does not exist", m_WorkspaceId); return 0; } m_WorkspaceRoot = WorkspaceConfig.RootPath; } else { RemoveTrailingPathSeparator(m_WorkspaceRoot); if (m_WorkspaceId.empty()) { m_WorkspaceId = Workspaces::PathToId(m_WorkspaceRoot).ToString(); ZEN_CONSOLE("Using generated workspace id {} from path '{}'", m_WorkspaceId, m_WorkspaceRoot); } else { if (Oid::TryFromHexString(m_WorkspaceId) == Oid::Zero) { throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_WorkspaceId)); } } if (Workspaces::AddWorkspace(Log(), StatePath, {.Id = Oid::FromHexString(m_WorkspaceId), .RootPath = m_WorkspaceRoot})) { ZEN_CONSOLE("Created workspace {} using root path '{}'", m_WorkspaceId, m_WorkspaceRoot); } else { ZEN_CONSOLE("Using existing workspace {} with root path '{}'", m_WorkspaceId, m_WorkspaceRoot); } } RemoveLeadingPathSeparator(m_SharePath); RemoveTrailingPathSeparator(m_SharePath); if (m_ShareId.empty()) { m_ShareId = Workspaces::PathToId(m_SharePath).ToString(); ZEN_CONSOLE("Using generated share id {}, for path '{}'", m_ShareId, m_SharePath); } if (Oid::TryFromHexString(m_ShareId) == Oid::Zero) { throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_ShareId)); } if (Workspaces::AddWorkspaceShare(Log(), m_WorkspaceRoot, {.Id = Oid::FromHexString(m_ShareId), .SharePath = m_SharePath, .Alias = m_Alias})) { if (!m_HostName.empty()) { HttpClient Http(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Created workspace share {}", m_ShareId); return 0; } else { ZEN_CONSOLE("Workspace share {} already exists", m_ShareId); return 0; } } if (SubOption == &m_InfoOptions) { if (!m_Alias.empty()) { Workspaces::WorkspaceConfiguration WorkspaceConfig; Workspaces::WorkspaceShareConfiguration ShareConfig = Workspaces::FindWorkspaceShare(Log(), StatePath, m_Alias, WorkspaceConfig); if (ShareConfig.Id == Oid::Zero) { ZEN_CONSOLE("Workspace share with alias {} does not exist", m_Alias); return 0; } ShowShare(ShareConfig, WorkspaceConfig.Id, ""sv); return 0; } if (m_WorkspaceId.empty()) { throw zen::OptionParseException("workspace id or root path is required"); } if (Oid::TryFromHexString(m_WorkspaceId) == Oid::Zero) { throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_WorkspaceId)); } Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), StatePath, Oid::FromHexString(m_WorkspaceId)); if (WorkspaceConfig.Id == Oid::Zero) { ZEN_CONSOLE("Workspace {} does not exist", m_WorkspaceId); return 0; } std::filesystem::path WorkspaceRoot = WorkspaceConfig.RootPath; if (m_ShareId.empty()) { throw zen::OptionParseException("share id is required"); } if (Oid::TryFromHexString(m_ShareId) == Oid::Zero) { throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_ShareId)); } Workspaces::WorkspaceShareConfiguration Share = Workspaces::FindWorkspaceShare(Log(), WorkspaceRoot, Oid::FromHexString(m_ShareId)); if (Share.Id == Oid::Zero) { ZEN_CONSOLE("Workspace share {} does not exist", m_ShareId); return 0; } ShowShare(Share, Oid::Zero, ""sv); return 0; } if (SubOption == &m_RemoveOptions) { std::filesystem::path WorkspaceRoot; if (!m_Alias.empty()) { Workspaces::WorkspaceConfiguration WorkspaceConfig; Workspaces::WorkspaceShareConfiguration ShareConfig = Workspaces::FindWorkspaceShare(Log(), StatePath, m_Alias, WorkspaceConfig); if (ShareConfig.Id == Oid::Zero) { ZEN_CONSOLE("Workspace share with alias {} does not exist", m_Alias); return 0; } m_ShareId = ShareConfig.Id.ToString(); m_WorkspaceId = WorkspaceConfig.Id.ToString(); WorkspaceRoot = WorkspaceConfig.RootPath; } if (m_WorkspaceId.empty()) { throw zen::OptionParseException("workspace id or root path is required"); } if (Oid::TryFromHexString(m_WorkspaceId) == Oid::Zero) { throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_WorkspaceId)); } Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), StatePath, Oid::FromHexString(m_WorkspaceId)); if (WorkspaceConfig.Id == Oid::Zero) { ZEN_CONSOLE("Workspace {} does not exist", m_WorkspaceId); return 0; } WorkspaceRoot = WorkspaceConfig.RootPath; if (m_ShareId.empty()) { throw zen::OptionParseException("share id is required"); } if (Oid::TryFromHexString(m_ShareId) == Oid::Zero) { throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_ShareId)); } if (Workspaces::RemoveWorkspaceShare(Log(), WorkspaceRoot, Oid::FromHexString(m_ShareId))) { if (!m_HostName.empty()) { HttpClient Http(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); } } ZEN_CONSOLE("Removed workspace share {}", m_ShareId); return 0; } else { ZEN_CONSOLE("Removed workspace share {} does not exist", m_ShareId); return 0; } } auto GetShareIdentityUrl = [&](const cxxopts::Options& Opts) { if (m_Alias.empty()) { if (m_WorkspaceId.empty()) { throw zen::OptionParseException("workspace id is required"); } if (m_ShareId.empty()) { throw zen::OptionParseException(fmt::format("share id is required", Opts.help())); } return fmt::format("{}/{}", m_WorkspaceId, m_ShareId); } else { return fmt::format("share/{}", m_Alias); } }; if (SubOption == &m_FilesOptions) { HttpClient::KeyValueMap Params; if (!m_FieldFilter.empty()) { Params.Entries.insert_or_assign("fieldnames", m_FieldFilter); } if (m_Refresh) { Params.Entries.insert_or_assign("refresh", ToString(m_Refresh)); } if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } HttpClient Http(m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/files", GetShareIdentityUrl(m_FilesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); return 0; } else { Result.ThrowError("failed to get workspace share files"sv); return 1; } } if (SubOption == &m_EntriesOptions) { HttpClient::KeyValueMap Params; if (!m_ChunkId.empty()) { Params.Entries.insert_or_assign("opkey", m_ChunkId); } if (!m_FieldFilter.empty()) { Params.Entries.insert_or_assign("fieldfilter", m_FieldFilter); } if (m_Refresh) { Params.Entries.insert_or_assign("refresh", ToString(m_Refresh)); } if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } HttpClient Http(m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/entries", GetShareIdentityUrl(m_EntriesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); return 0; } else { Result.ThrowError("failed to get workspace share entries"sv); return 1; } } auto ChunksToOidStrings = [](HttpClient& Http, std::string_view WorkspaceId, std::string_view ShareId, std::span ChunkIds) -> std::vector { 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; for (CbFieldView EntryView : Result.AsObject()["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; }; if (SubOption == &m_GetChunkOptions) { if (m_ChunkId.empty()) { throw zen::OptionParseException("chunk id is required"); } if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } HttpClient Http(m_HostName); m_ChunkId = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, std::vector{m_ChunkId})[0]; HttpClient::KeyValueMap Params; if (m_Offset != 0) { Params.Entries.insert_or_assign("offset", fmt::format("{}", m_Offset)); } if (m_Size != ~uint64_t(0)) { Params.Entries.insert_or_assign("size", fmt::format("{}", m_Size)); } if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}", GetShareIdentityUrl(m_GetChunkOptions), m_ChunkId), {}, Params)) { ZEN_CONSOLE("{}: Bytes: {}", Result, NiceBytes(Result.ResponsePayload.GetSize())); return 0; } else { Result.ThrowError("failed to get workspace share chunk"sv); return 1; } } if (SubOption == &m_GetChunkBatchOptions) { if (m_ShareId.empty()) { throw zen::OptionParseException(fmt::format("share id is required", m_InfoOptions.help())); } if (m_ChunkIds.empty()) { throw zen::OptionParseException("share is is required"); } if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } HttpClient Http(m_HostName); m_ChunkIds = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, m_ChunkIds); std::vector ChunkRequests; ChunkRequests.resize(m_ChunkIds.size()); for (size_t Index = 0; Index < m_ChunkIds.size(); Index++) { ChunkRequests[Index] = RequestChunkEntry{.ChunkId = Oid::FromHexString(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", GetShareIdentityUrl(m_GetChunkBatchOptions)), Payload)) { ZEN_CONSOLE("{}: Bytes: {}", Result, NiceBytes(Result.ResponsePayload.GetSize())); std::vector Results = ParseChunkBatchResponse(Result.ResponsePayload); if (Results.size() != m_ChunkIds.size()) { throw std::runtime_error( fmt::format("failed to get workspace share batch - invalid result count recevied (expected: {}, received: {}", m_ChunkIds.size(), Results.size())); } for (size_t Index = 0; Index < m_ChunkIds.size(); Index++) { ZEN_CONSOLE("{}: Bytes: {}", m_ChunkIds[Index], NiceBytes(Results[Index].GetSize())); } return 0; } else { Result.ThrowError("failed to get workspace share batch"sv); return 1; } } ZEN_ASSERT(false); } } // namespace zen