diff options
| author | Dan Engelbrecht <[email protected]> | 2024-06-04 22:21:06 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2024-06-04 22:21:06 +0200 |
| commit | 3a82e1cb3b5a284d2c47e03d541a35b84589314b (patch) | |
| tree | d4b6c3d7c548b2d6f83b105fb0a5668ca2152889 /src | |
| parent | add batching of CacheStore requests for GetCacheValues/GetCacheChunks (#90) (diff) | |
| download | zen-3a82e1cb3b5a284d2c47e03d541a35b84589314b.tar.xz zen-3a82e1cb3b5a284d2c47e03d541a35b84589314b.zip | |
workspace share aliases (#91)
- Add `zen workspace-share` `--root-path` option - the root local file path of the workspace - if given it will automatically create the workspace before creating the share. If `--workspace` is omitted, an id will be generated from the `--root-path` parameter
- Add `/ws/share/{alias}/` endpoint - a shortcut to `/ws/{workspace_id}/{share_id}/` based endpoints using the alias for a workspace share
- Add `--alias` option to replace `--workspace` and `--share` options for `workspace-share` zen commands
- Rename `zen workspace create` `folder` option to `root-path`
- Rename `zen workspace create` `folder` option to `share-path`
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/workspaces_cmd.cpp | 129 | ||||
| -rw-r--r-- | src/zen/cmds/workspaces_cmd.h | 2 | ||||
| -rw-r--r-- | src/zenserver/workspaces/httpworkspaces.cpp | 623 | ||||
| -rw-r--r-- | src/zenserver/workspaces/httpworkspaces.h | 14 | ||||
| -rw-r--r-- | src/zenstore/include/zenstore/workspaces.h | 15 | ||||
| -rw-r--r-- | src/zenstore/workspaces.cpp | 125 |
6 files changed, 665 insertions, 243 deletions
diff --git a/src/zen/cmds/workspaces_cmd.cpp b/src/zen/cmds/workspaces_cmd.cpp index 503bc24cf..afdf5d7f5 100644 --- a/src/zen/cmds/workspaces_cmd.cpp +++ b/src/zen/cmds/workspaces_cmd.cpp @@ -28,9 +28,9 @@ WorkspaceCommand::WorkspaceCommand() m_CreateOptions.add_options()("h,help", "Print help"); m_CreateOptions.add_option("", "w", "workspace", "Workspace identity(id)", cxxopts::value(m_Id), "<workspaceid>"); - m_CreateOptions.add_option("", "r", "folder", "Root file system folder for workspace", cxxopts::value(m_Path), "<folder>"); - m_CreateOptions.parse_positional({"folder", "workspace"}); - m_CreateOptions.positional_help("folder workspace"); + m_CreateOptions.add_option("", "r", "root-path", "Root file system folder for workspace", cxxopts::value(m_Path), "<root-path>"); + 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), "<workspaceid>"); @@ -155,27 +155,40 @@ WorkspaceShareCommand::WorkspaceShareCommand() m_CreateOptions.add_options()("h,help", "Print help"); m_CreateOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), "<workspaceid>"); + m_CreateOptions.add_option("", + "r", + "root-path", + "Root path for workspace, replaces 'workspace' id", + cxxopts::value(m_WorkspaceRoot), + "<root-path>"); m_CreateOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), "<shareid>"); - m_CreateOptions.add_option("", "r", "folder", "Folder path inside the workspace to share", cxxopts::value(m_SharePath), "<folder>"); - m_CreateOptions.parse_positional({"workspace", "folder", "share"}); - m_CreateOptions.positional_help("workspace folder share"); + m_CreateOptions + .add_option("", "p", "share-path", "Folder path inside the workspace to share", cxxopts::value(m_SharePath), "<share-path>"); + m_CreateOptions.add_option("", "a", "alias", "Named alias for this share", cxxopts::value(m_Alias), "<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), "<workspaceid>"); m_InfoOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), "<shareid>"); m_InfoOptions.add_option("", "r", "refresh", "Refresh workspace share", cxxopts::value(m_Refresh), "<refresh>"); + m_InfoOptions.add_option("", "a", "alias", "Named alias for this share", cxxopts::value(m_Alias), "<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), "<workspaceid>"); m_RemoveOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), "<shareid>"); + m_RemoveOptions + .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), "<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), "<workspaceid>"); m_FilesOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), "<shareid>"); + m_FilesOptions + .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), "<alias>"); m_FilesOptions.add_option("", "", "filter", @@ -189,6 +202,8 @@ WorkspaceShareCommand::WorkspaceShareCommand() m_EntriesOptions.add_options()("h,help", "Print help"); m_EntriesOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), "<workspaceid>"); m_EntriesOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), "<shareid>"); + m_EntriesOptions + .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), "<alias>"); m_EntriesOptions.add_option("", "", "filter", @@ -203,6 +218,8 @@ WorkspaceShareCommand::WorkspaceShareCommand() m_GetChunkOptions.add_options()("h,help", "Print help"); m_GetChunkOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), "<workspaceid>"); m_GetChunkOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), "<shareid>"); + m_GetChunkOptions + .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), "<alias>"); m_GetChunkOptions.add_option("", "c", "chunk", "Chunk identity (id)", cxxopts::value(m_ChunkId), "<chunkid>"); m_GetChunkOptions.add_option("", "", "offset", "Offset in chunk", cxxopts::value(m_Offset), "<offset>"); m_GetChunkOptions.add_option("", "", "size", "Size of chunk", cxxopts::value(m_Size), "<size>"); @@ -212,6 +229,8 @@ WorkspaceShareCommand::WorkspaceShareCommand() m_GetChunkBatchOptions.add_options()("h,help", "Print help"); m_GetChunkBatchOptions.add_option("", "s", "share", "Workspace share identity(id)", cxxopts::value(m_ShareId), "<shareid>"); m_GetChunkBatchOptions.add_option("", "w", "workspace", "Workspace identity (id)", cxxopts::value(m_WorkspaceId), "<workspaceid>"); + m_GetChunkBatchOptions + .add_option("", "a", "alias", "Alias for the share, replaces 'workspace' and 'share' options", cxxopts::value(m_Alias), "<alias>"); m_GetChunkBatchOptions.add_option("", "", "chunks", "A list of identities (id)", cxxopts::value(m_ChunkIds), "<chunkids>"); m_GetChunkBatchOptions.parse_positional({"workspace", "share", "chunks"}); m_GetChunkBatchOptions.positional_help("workspace share chunks"); @@ -251,15 +270,45 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** return 0; } - if (m_WorkspaceId.empty()) - { - throw zen::OptionParseException("workspace id is required"); - } - HttpClient Http(m_HostName); if (SubOption == &m_CreateOptions) { + if (!m_WorkspaceRoot.empty()) + { + HttpClient::KeyValueMap Params{{"root_path", m_WorkspaceRoot}}; + if (HttpClient::Response Result = + Http.Put(fmt::format("/ws/{}", m_WorkspaceId.empty() ? Oid::Zero.ToString() : m_WorkspaceId), Params)) + { + if (Oid::Zero == Oid::TryFromHexString(Result.AsText())) + { + throw std::runtime_error(fmt::format("failed to create workspace {} with root path '{}'. Reason: {}", + m_WorkspaceId, + m_WorkspaceRoot, + Result.AsText())); + } + m_WorkspaceId = Result.AsText(); + if (Result.StatusCode == HttpResponseCode::Created) + { + ZEN_CONSOLE("Created workspace {} using root path '{}'", m_WorkspaceId, m_WorkspaceRoot); + } + else + { + ZEN_CONSOLE("Using existing workspace {} with root path '{}'", m_WorkspaceId, m_WorkspaceRoot); + } + } + else + { + Result.ThrowError(fmt::format("failed to create workspace {} with root path '{}'", m_WorkspaceId, m_WorkspaceRoot)); + return 1; + } + } + + if (m_WorkspaceId.empty()) + { + throw zen::OptionParseException("workspace id or root path is required"); + } + if (m_ShareId.empty()) { if (m_SharePath.ends_with(std::filesystem::path::preferred_separator)) @@ -272,6 +321,10 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** } HttpClient::KeyValueMap Params{{"share_path", m_SharePath}}; + if (!m_Alias.empty()) + { + Params.Entries.insert_or_assign("alias", m_Alias); + } if (HttpClient::Response Result = Http.Put(fmt::format("/ws/{}/{}", m_WorkspaceId, m_ShareId), Params)) { @@ -285,14 +338,29 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** } } - if (SubOption == &m_InfoOptions) - { - if (m_ShareId.empty()) + auto GetShareIdentityUrl = [&](const cxxopts::Options& Opts) { + if (m_Alias.empty()) { - throw zen::OptionParseException(fmt::format("share id is required", m_InfoOptions.help())); + 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 (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}", m_WorkspaceId, m_ShareId))) + if (SubOption == &m_InfoOptions) + { + if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}", GetShareIdentityUrl(m_InfoOptions)))) { ZEN_CONSOLE("{}", Result.ToText()); return 0; @@ -306,11 +374,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** if (SubOption == &m_RemoveOptions) { - if (m_ShareId.empty()) - { - throw zen::OptionParseException(fmt::format("share id is required", m_InfoOptions.help())); - } - if (HttpClient::Response Result = Http.Delete(fmt::format("/ws/{}/{}", m_WorkspaceId, m_ShareId))) + if (HttpClient::Response Result = Http.Delete(fmt::format("/ws/{}", GetShareIdentityUrl(m_RemoveOptions)))) { ZEN_CONSOLE("{}", Result); return 0; @@ -324,11 +388,6 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** if (SubOption == &m_FilesOptions) { - if (m_ShareId.empty()) - { - throw zen::OptionParseException(fmt::format("share id is required", m_InfoOptions.help())); - } - HttpClient::KeyValueMap Params; if (!m_FieldFilter.empty()) { @@ -339,7 +398,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** Params.Entries.insert_or_assign("refresh", ToString(m_Refresh)); } - if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}/files", m_WorkspaceId, m_ShareId), {}, Params)) + if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/files", GetShareIdentityUrl(m_FilesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); return 0; @@ -353,11 +412,6 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** if (SubOption == &m_EntriesOptions) { - if (m_ShareId.empty()) - { - throw zen::OptionParseException(fmt::format("share id is required", m_InfoOptions.help())); - } - HttpClient::KeyValueMap Params; if (!m_ChunkId.empty()) { @@ -372,7 +426,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** Params.Entries.insert_or_assign("refresh", ToString(m_Refresh)); } - if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}/entries", m_WorkspaceId, m_ShareId), {}, Params)) + if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/entries", GetShareIdentityUrl(m_EntriesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); return 0; @@ -434,11 +488,6 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** if (SubOption == &m_GetChunkOptions) { - if (m_ShareId.empty()) - { - throw zen::OptionParseException(fmt::format("share id is required", m_InfoOptions.help())); - } - if (m_ChunkId.empty()) { throw zen::OptionParseException("chunk id is required"); @@ -456,7 +505,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** Params.Entries.insert_or_assign("size", fmt::format("{}", m_Size)); } - if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}/{}", m_WorkspaceId, m_ShareId, m_ChunkId), {}, Params)) + 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; @@ -493,7 +542,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** } IoBuffer Payload = BuildChunkBatchRequest(ChunkRequests); - if (HttpClient::Response Result = Http.Post(fmt::format("/ws/{}/{}/batch", m_WorkspaceId, m_ShareId), Payload)) + if (HttpClient::Response Result = Http.Post(fmt::format("/ws/{}/batch", GetShareIdentityUrl(m_GetChunkBatchOptions)), Payload)) { ZEN_CONSOLE("{}: Bytes: {}", Result, NiceBytes(Result.ResponsePayload.GetSize())); std::vector<IoBuffer> Results = ParseChunkBatchResponse(Result.ResponsePayload); diff --git a/src/zen/cmds/workspaces_cmd.h b/src/zen/cmds/workspaces_cmd.h index a2df4b96e..cce3d0175 100644 --- a/src/zen/cmds/workspaces_cmd.h +++ b/src/zen/cmds/workspaces_cmd.h @@ -52,8 +52,10 @@ private: cxxopts::Options m_Options{Name, Description}; std::string m_HostName; std::string m_WorkspaceId; + std::string m_WorkspaceRoot; std::string m_Verb; // create, info, remove std::string m_ShareId; + std::string m_Alias; cxxopts::Options m_CreateOptions{"create", "Create a workspace share"}; std::string m_SharePath; diff --git a/src/zenserver/workspaces/httpworkspaces.cpp b/src/zenserver/workspaces/httpworkspaces.cpp index 121f40149..534b72bd5 100644 --- a/src/zenserver/workspaces/httpworkspaces.cpp +++ b/src/zenserver/workspaces/httpworkspaces.cpp @@ -37,6 +37,8 @@ namespace { return Oid::FromMemory(Hash.Hash); } + constinit AsciiSet ValidAliasCharactersSet{"abcdefghijklmnopqrstuvwxyz0123456789+-_.[]ABCDEFGHIJKLMNOPQRSTUVWXYZ"}; + } // namespace HttpWorkspacesService::HttpWorkspacesService(HttpStatsService& StatsService, const FileServeConfig& Cfg, Workspaces& Workspaces) @@ -126,6 +128,7 @@ HttpWorkspacesService::Initialize() m_Router.AddPattern("workspace", "([[:xdigit:]]{24})"); m_Router.AddPattern("share_id", "([[:xdigit:]]{24})"); m_Router.AddPattern("chunk", "([[:xdigit:]]{24})"); + m_Router.AddPattern("share_alias", "([[:alnum:]_.\\+\\-\\[\\]]+)"); m_Router.RegisterRoute( "{workspace_id}/{share_id}/files", @@ -153,6 +156,36 @@ HttpWorkspacesService::Initialize() HttpVerb::kGet | HttpVerb::kHead); m_Router.RegisterRoute( + "share/{share_alias}/files", + [this](HttpRouterRequest& Req) { ShareAliasFilesRequest(Req); }, + HttpVerb::kGet); + + m_Router.RegisterRoute( + "share/{share_alias}/{chunk}/info", + [this](HttpRouterRequest& Req) { ShareAliasChunkInfoRequest(Req); }, + HttpVerb::kGet); + + m_Router.RegisterRoute( + "share/{share_alias}/batch", + [this](HttpRouterRequest& Req) { ShareAliasBatchRequest(Req); }, + HttpVerb::kPost); + + m_Router.RegisterRoute( + "share/{share_alias}/entries", + [this](HttpRouterRequest& Req) { ShareAliasEntriesRequest(Req); }, + HttpVerb::kGet); + + m_Router.RegisterRoute( + "share/{share_alias}/{chunk}", + [this](HttpRouterRequest& Req) { ShareAliasChunkRequest(Req); }, + HttpVerb::kGet | HttpVerb::kHead); + + m_Router.RegisterRoute( + "share/{share_alias}", + [this](HttpRouterRequest& Req) { ShareAliasRequest(Req); }, + HttpVerb::kPut | HttpVerb::kGet | HttpVerb::kDelete); + + m_Router.RegisterRoute( "{workspace_id}/{share_id}", [this](HttpRouterRequest& Req) { ShareRequest(Req); }, HttpVerb::kPut | HttpVerb::kGet | HttpVerb::kDelete); @@ -209,6 +242,354 @@ HttpWorkspacesService::FilesRequest(HttpRouterRequest& Req) HttpContentType::kText, fmt::format("Invalid share id '{}'", Req.GetCapture(2))); } + FilesRequest(Req, WorkspaceId, ShareId); +} + +void +HttpWorkspacesService::ChunkInfoRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); + if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); + if (ShareId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid share id '{}'", Req.GetCapture(2))); + } + ChunkInfoRequest(Req, WorkspaceId, ShareId); +} + +void +HttpWorkspacesService::BatchRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); + if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); + if (ShareId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid share id '{}'", Req.GetCapture(2))); + } + BatchRequest(Req, WorkspaceId, ShareId); +} + +void +HttpWorkspacesService::EntriesRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); + if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); + if (ShareId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid share id '{}'", Req.GetCapture(2))); + } + EntriesRequest(Req, WorkspaceId, ShareId); +} + +void +HttpWorkspacesService::ChunkRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); + if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); + if (ShareId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid share id '{}'", Req.GetCapture(2))); + } + ChunkRequest(Req, WorkspaceId, ShareId); +} + +void +HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); + if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + Oid ShareId = Oid::Zero; + if (Req.GetCapture(2) != Oid::Zero.ToString()) + { + ShareId = Oid::TryFromHexString(Req.GetCapture(2)); + if (ShareId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid share id '{}'", Req.GetCapture(2))); + } + } + ShareRequest(Req, WorkspaceId, ShareId); +} + +void +HttpWorkspacesService::WorkspaceRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); + switch (ServerRequest.RequestVerb()) + { + case HttpVerb::kPut: + { + std::filesystem::path WorkspacePath = GetPathParameter(ServerRequest, "root_path"sv); + if (WorkspacePath.empty()) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + "Invalid 'root_path' parameter"); + } + if (Req.GetCapture(1) == Oid::Zero.ToString()) + { + // Synthesize Id + WorkspaceId = PathToChunkId(WorkspacePath); + ZEN_INFO("Generated workspace id from path '{}': {}", WorkspacePath, WorkspaceId); + } + else if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + m_WorkspacesStats.WorkspaceWriteCount++; + Workspaces::WorkspaceConfiguration NewConfig = {.Id = WorkspaceId, .RootPath = WorkspacePath}; + bool OK = m_Workspaces.AddWorkspace(NewConfig); + if (OK) + { + WriteState(); + return ServerRequest.WriteResponse(HttpResponseCode::Created, HttpContentType::kText, fmt::format("{}", WorkspaceId)); + } + else + { + Workspaces::WorkspaceConfiguration Config = m_Workspaces.GetWorkspaceConfiguration(WorkspaceId); + if (Config == NewConfig) + { + return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("{}", WorkspaceId)); + } + return ServerRequest.WriteResponse( + HttpResponseCode::Conflict, + HttpContentType::kText, + fmt::format("Workspace {} already exists with root path '{}'", WorkspaceId, Config.RootPath)); + } + } + case HttpVerb::kGet: + { + if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + m_WorkspacesStats.WorkspaceReadCount++; + Workspaces::WorkspaceInfo Info = m_Workspaces.GetWorkspaceInfo(WorkspaceId); + if (Info.Config.Id != Oid::Zero) + { + CbObjectWriter Response; + Response << "id" << Info.Config.Id; + Response << "root_path" << Info.Config.RootPath.string(); // utf8? + Response.BeginArray("shares"); + for (const Workspaces::WorkspaceShareConfiguration& ShareConfig : Info.Shares) + { + Response.BeginObject(); + { + Response << "id" << ShareConfig.Id; + Response << "share_path" << ShareConfig.SharePath.string(); // utf8? + if (!ShareConfig.Alias.empty()) + { + Response << "alias" << ShareConfig.Alias; + } + } + Response.EndObject(); + } + Response.EndArray(); + + return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save()); + } + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + case HttpVerb::kDelete: + { + if (WorkspaceId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); + } + m_WorkspacesStats.WorkspaceDeleteCount++; + bool Deleted = m_Workspaces.RemoveWorkspace(WorkspaceId); + if (Deleted) + { + WriteState(); + return ServerRequest.WriteResponse(HttpResponseCode::OK); + } + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + } +} + +void +HttpWorkspacesService::ShareAliasFilesRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + std::string Alias = Req.GetCapture(1); + if (Alias.empty()) + { + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid alias '{}'", Req.GetCapture(1))); + } + std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias); + if (!WorkspaceAndShareId.has_value()) + { + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + FilesRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId); +} + +void +HttpWorkspacesService::ShareAliasChunkInfoRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + std::string Alias = Req.GetCapture(1); + if (Alias.empty()) + { + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid alias '{}'", Req.GetCapture(1))); + } + std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias); + if (!WorkspaceAndShareId.has_value()) + { + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + ChunkInfoRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId); +} + +void +HttpWorkspacesService::ShareAliasBatchRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + std::string Alias = Req.GetCapture(1); + if (Alias.empty()) + { + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid alias '{}'", Req.GetCapture(1))); + } + std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias); + if (!WorkspaceAndShareId.has_value()) + { + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + BatchRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId); +} + +void +HttpWorkspacesService::ShareAliasEntriesRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + std::string Alias = Req.GetCapture(1); + if (Alias.empty()) + { + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid alias '{}'", Req.GetCapture(1))); + } + std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias); + if (!WorkspaceAndShareId.has_value()) + { + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + EntriesRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId); +} + +void +HttpWorkspacesService::ShareAliasChunkRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + std::string Alias = Req.GetCapture(1); + if (Alias.empty()) + { + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid alias '{}'", Req.GetCapture(1))); + } + std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias); + if (!WorkspaceAndShareId.has_value()) + { + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + ChunkRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId); +} + +void +HttpWorkspacesService::ShareAliasRequest(HttpRouterRequest& Req) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); + std::string Alias = Req.GetCapture(1); + if (Alias.empty()) + { + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid alias '{}'", Req.GetCapture(1))); + } + std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias); + if (!WorkspaceAndShareId.has_value()) + { + return ServerRequest.WriteResponse(HttpResponseCode::NotFound); + } + ShareRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId); +} + +void +HttpWorkspacesService::FilesRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId) +{ + HttpServerRequest& ServerRequest = Req.ServerRequest(); m_WorkspacesStats.WorkspaceShareFilesReadCount++; @@ -290,26 +671,10 @@ HttpWorkspacesService::FilesRequest(HttpRouterRequest& Req) } void -HttpWorkspacesService::ChunkInfoRequest(HttpRouterRequest& Req) +HttpWorkspacesService::ChunkInfoRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId) { HttpServerRequest& ServerRequest = Req.ServerRequest(); - const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); - if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); - if (ShareId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid share id '{}'", Req.GetCapture(2))); - } - const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(3)); + const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(3)); if (ChunkId == Oid::Zero) { m_WorkspacesStats.BadRequestCount++; @@ -330,25 +695,9 @@ HttpWorkspacesService::ChunkInfoRequest(HttpRouterRequest& Req) } void -HttpWorkspacesService::BatchRequest(HttpRouterRequest& Req) +HttpWorkspacesService::BatchRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId) { - HttpServerRequest& ServerRequest = Req.ServerRequest(); - const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); - if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); - if (ShareId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid share id '{}'", Req.GetCapture(2))); - } + HttpServerRequest& ServerRequest = Req.ServerRequest(); IoBuffer Payload = ServerRequest.ReadPayload(); std::optional<std::vector<RequestChunkEntry>> ChunkRequests = ParseChunkBatchRequest(Payload); if (!ChunkRequests.has_value()) @@ -392,7 +741,7 @@ HttpWorkspacesService::BatchRequest(HttpRouterRequest& Req) } void -HttpWorkspacesService::EntriesRequest(HttpRouterRequest& Req) +HttpWorkspacesService::EntriesRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId) { HttpServerRequest& ServerRequest = Req.ServerRequest(); std::string_view OpKey = ServerRequest.GetQueryParams().GetValue("opkey"sv); @@ -401,22 +750,6 @@ HttpWorkspacesService::EntriesRequest(HttpRouterRequest& Req) m_WorkspacesStats.BadRequestCount++; return ServerRequest.WriteResponse(HttpResponseCode::NotFound); } - const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); - if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); - if (ShareId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid share id '{}'", Req.GetCapture(2))); - } std::unordered_set<std::string> WantedFieldNames; if (auto FieldFilter = HttpServerRequest::Decode(ServerRequest.GetQueryParams().GetValue("fieldfilter")); !FieldFilter.empty()) { @@ -503,26 +836,10 @@ HttpWorkspacesService::EntriesRequest(HttpRouterRequest& Req) } void -HttpWorkspacesService::ChunkRequest(HttpRouterRequest& Req) +HttpWorkspacesService::ChunkRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId) { HttpServerRequest& ServerRequest = Req.ServerRequest(); - const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); - if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); - if (ShareId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid share id '{}'", Req.GetCapture(2))); - } - const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(3)); + const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(3)); if (ChunkId == Oid::Zero) { m_WorkspacesStats.BadRequestCount++; @@ -582,18 +899,11 @@ HttpWorkspacesService::ChunkRequest(HttpRouterRequest& Req) } void -HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) +HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& InShareId) { + Oid ShareId = InShareId; + HttpServerRequest& ServerRequest = Req.ServerRequest(); - const Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); - if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2)); switch (ServerRequest.RequestVerb()) { case HttpVerb::kPut: @@ -606,19 +916,20 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) HttpContentType::kText, "Invalid 'share_path' parameter"); } - if (Req.GetCapture(2) == Oid::Zero.ToString()) + + if (ShareId == Oid::Zero) { // Synthesize Id ShareId = PathToChunkId(SharePath); ZEN_INFO("Generated workspace id from path '{}': {}", SharePath, ShareId); } - else if (ShareId == Oid::Zero) + + std::string Alias = HttpServerRequest::Decode(ServerRequest.GetQueryParams().GetValue("alias"sv)); + if (!AsciiSet::HasOnly(Alias, ValidAliasCharactersSet)) { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid share id '{}'", Req.GetCapture(2))); + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid 'alias' parameter"); } + m_WorkspacesStats.WorkspaceShareWriteCount++; if (m_Workspaces.GetWorkspaceInfo(WorkspaceId).Config.Id != WorkspaceId) { @@ -626,7 +937,10 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) HttpContentType::kText, fmt::format("Workspace '{}' does not exist", WorkspaceId)); } - bool OK = m_Workspaces.AddWorkspaceShare(WorkspaceId, {ShareId, SharePath}, [](const std::filesystem::path& Path) { + const Workspaces::WorkspaceShareConfiguration NewConfig = {.Id = ShareId, + .SharePath = SharePath, + .Alias = std::string(Alias)}; + bool OK = m_Workspaces.AddWorkspaceShare(WorkspaceId, NewConfig, [](const std::filesystem::path& Path) { return PathToChunkId(Path); }); if (OK) @@ -637,17 +951,18 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) else { Workspaces::WorkspaceShareConfiguration Config = m_Workspaces.GetWorkspaceShareConfiguration(WorkspaceId, ShareId); - if (Config.Id == ShareId) + if (Config == NewConfig) { - if (Config.SharePath == SharePath) - { - return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("{}", ShareId)); - } + return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("{}", ShareId)); } return ServerRequest.WriteResponse( HttpResponseCode::Conflict, HttpContentType::kText, - fmt::format("Workspace share '{}' already exist in workspace '{}'", ShareId, WorkspaceId)); + fmt::format("Workspace share '{}' already exist in workspace '{}' with share path '{}' and alias '{}'", + ShareId, + WorkspaceId, + Config.SharePath, + Config.Alias)); } } case HttpVerb::kGet: @@ -659,6 +974,13 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) HttpContentType::kText, fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); } + if (ShareId == Oid::Zero) + { + m_WorkspacesStats.BadRequestCount++; + return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, + HttpContentType::kText, + fmt::format("Invalid share id '{}'", ShareId)); + } m_WorkspacesStats.WorkspaceShareReadCount++; Workspaces::WorkspaceShareConfiguration Config = m_Workspaces.GetWorkspaceShareConfiguration(WorkspaceId, ShareId); if (Config.Id != Oid::Zero) @@ -666,6 +988,10 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) CbObjectWriter Response; Response << "id" << Config.Id; Response << "share_path" << Config.SharePath.string(); // utf8? + if (!Config.Alias.empty()) + { + Response << "alias" << Config.Alias; + } return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save()); } return ServerRequest.WriteResponse(HttpResponseCode::NotFound); @@ -679,116 +1005,15 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req) HttpContentType::kText, fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); } - m_WorkspacesStats.WorkspaceShareDeleteCount++; - bool Deleted = m_Workspaces.RemoveWorkspaceShare(WorkspaceId, ShareId); - if (Deleted) - { - WriteState(); - return ServerRequest.WriteResponse(HttpResponseCode::OK); - } - return ServerRequest.WriteResponse(HttpResponseCode::NotFound); - } - } -} - -void -HttpWorkspacesService::WorkspaceRequest(HttpRouterRequest& Req) -{ - HttpServerRequest& ServerRequest = Req.ServerRequest(); - Oid WorkspaceId = Oid::TryFromHexString(Req.GetCapture(1)); - switch (ServerRequest.RequestVerb()) - { - case HttpVerb::kPut: - { - std::filesystem::path WorkspacePath = GetPathParameter(ServerRequest, "root_path"sv); - if (WorkspacePath.empty()) + if (ShareId == Oid::Zero) { m_WorkspacesStats.BadRequestCount++; return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, - "Invalid 'root_path' parameter"); - } - if (Req.GetCapture(1) == Oid::Zero.ToString()) - { - // Synthesize Id - WorkspaceId = PathToChunkId(WorkspacePath); - ZEN_INFO("Generated workspace id from path '{}': {}", WorkspacePath, WorkspaceId); + fmt::format("Invalid share id '{}'", ShareId)); } - else if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - m_WorkspacesStats.WorkspaceWriteCount++; - bool OK = m_Workspaces.AddWorkspace({WorkspaceId, WorkspacePath}); - if (OK) - { - WriteState(); - return ServerRequest.WriteResponse(HttpResponseCode::Created, HttpContentType::kText, fmt::format("{}", WorkspaceId)); - } - else - { - Workspaces::WorkspaceInfo Info = m_Workspaces.GetWorkspaceInfo(WorkspaceId); - if (Info.Config.Id == WorkspaceId) - { - if (Info.Config.RootPath == WorkspacePath) - { - return ServerRequest.WriteResponse(HttpResponseCode::OK, - HttpContentType::kText, - fmt::format("{}", WorkspaceId)); - } - } - return ServerRequest.WriteResponse( - HttpResponseCode::Conflict, - HttpContentType::kText, - fmt::format("Workspace {} already exists with root path '{}'", WorkspaceId, Info.Config.RootPath)); - } - } - case HttpVerb::kGet: - { - if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - m_WorkspacesStats.WorkspaceReadCount++; - Workspaces::WorkspaceInfo Info = m_Workspaces.GetWorkspaceInfo(WorkspaceId); - if (Info.Config.Id != Oid::Zero) - { - CbObjectWriter Response; - Response << "id" << Info.Config.Id; - Response << "root_path" << Info.Config.RootPath.string(); // utf8? - Response.BeginArray("shares"); - for (const Workspaces::WorkspaceShareConfiguration& ShareConfig : Info.Shares) - { - Response.BeginObject(); - { - Response << "id" << ShareConfig.Id; - Response << "share_path" << ShareConfig.SharePath.string(); // utf8? - } - Response.EndObject(); - } - Response.EndArray(); - - return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save()); - } - return ServerRequest.WriteResponse(HttpResponseCode::NotFound); - } - case HttpVerb::kDelete: - { - if (WorkspaceId == Oid::Zero) - { - m_WorkspacesStats.BadRequestCount++; - return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, - HttpContentType::kText, - fmt::format("Invalid workspace id '{}'", Req.GetCapture(1))); - } - m_WorkspacesStats.WorkspaceDeleteCount++; - bool Deleted = m_Workspaces.RemoveWorkspace(WorkspaceId); + m_WorkspacesStats.WorkspaceShareDeleteCount++; + bool Deleted = m_Workspaces.RemoveWorkspaceShare(WorkspaceId, ShareId); if (Deleted) { WriteState(); diff --git a/src/zenserver/workspaces/httpworkspaces.h b/src/zenserver/workspaces/httpworkspaces.h index cfd23e7ba..a61f7d7e3 100644 --- a/src/zenserver/workspaces/httpworkspaces.h +++ b/src/zenserver/workspaces/httpworkspaces.h @@ -61,6 +61,20 @@ private: void ShareRequest(HttpRouterRequest& Req); void WorkspaceRequest(HttpRouterRequest& Req); + void ShareAliasFilesRequest(HttpRouterRequest& Req); + void ShareAliasChunkInfoRequest(HttpRouterRequest& Req); + void ShareAliasBatchRequest(HttpRouterRequest& Req); + void ShareAliasEntriesRequest(HttpRouterRequest& Req); + void ShareAliasChunkRequest(HttpRouterRequest& Req); + void ShareAliasRequest(HttpRouterRequest& Req); + + void FilesRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId); + void ChunkInfoRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId); + void BatchRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId); + void EntriesRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId); + void ChunkRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId); + void ShareRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& InShareId); + HttpStatsService& m_StatsService; const FileServeConfig m_Config; HttpRequestRouter m_Router; diff --git a/src/zenstore/include/zenstore/workspaces.h b/src/zenstore/include/zenstore/workspaces.h index e1a024894..2eacf313d 100644 --- a/src/zenstore/include/zenstore/workspaces.h +++ b/src/zenstore/include/zenstore/workspaces.h @@ -41,12 +41,18 @@ public: { Oid Id; std::filesystem::path RootPath; + inline bool operator==(const WorkspaceConfiguration& Rhs) const { return Id == Rhs.Id && RootPath == Rhs.RootPath; } }; struct WorkspaceShareConfiguration { Oid Id; std::filesystem::path SharePath; + std::string Alias; + inline bool operator==(const WorkspaceShareConfiguration& Rhs) const + { + return Id == Rhs.Id && SharePath == Rhs.SharePath && Alias == Rhs.Alias; + } }; struct WorkspaceInfo @@ -84,6 +90,14 @@ public: void WriteState(const std::filesystem::path& WorkspaceStatePath); void ReadState(const std::filesystem::path& WorkspaceStatePath, std::function<Oid(const std::filesystem::path& Path)>&& PathToIdCB); + struct ShareAlias + { + Oid WorkspaceId; + Oid ShareId; + }; + + std::optional<ShareAlias> GetShareAlias(std::string_view Alias) const; + private: LoggerRef& Log() { return m_Log; } @@ -95,6 +109,7 @@ private: LoggerRef m_Log; mutable RwLock m_Lock; tsl::robin_map<Oid, Ref<Workspace>, Oid::Hasher> m_Workspaces; + tsl::robin_map<std::string, ShareAlias> m_ShareAliases; }; void workspaces_forcelink(); diff --git a/src/zenstore/workspaces.cpp b/src/zenstore/workspaces.cpp index 958d7b3f5..669d675dd 100644 --- a/src/zenstore/workspaces.cpp +++ b/src/zenstore/workspaces.cpp @@ -26,6 +26,10 @@ namespace { CbObjectWriter ShareWriter; ShareWriter.AddObjectId("id"sv, ShareConfig.Id); ShareWriter.AddString("share_path"sv, reinterpret_cast<const char*>(ShareConfig.SharePath.u8string().c_str())); + if (!ShareConfig.Alias.empty()) + { + ShareWriter.AddString("alias"sv, ShareConfig.Alias); + } ExtendableStringBuilder<256> Json; ShareWriter.Save().ToJson(Json); return Json.ToString(); @@ -43,9 +47,10 @@ namespace { { Oid ShareId = Object["id"sv].AsObjectId(); std::filesystem::path SharePath = Object["share_path"sv].AsU8String(); + std::string_view Alias = Object["alias"sv].AsString(); if (ShareId != Oid::Zero && !SharePath.empty()) { - return {.Id = ShareId, .SharePath = SharePath}; + return {.Id = ShareId, .SharePath = SharePath, .Alias = std::string(Alias)}; } } } @@ -436,8 +441,23 @@ Workspaces::RemoveWorkspace(const Oid& WorkspaceId) RwLock::ExclusiveLockScope Lock(m_Lock); if (auto It = m_Workspaces.find(WorkspaceId); It != m_Workspaces.end()) { + std::vector<std::string> Aliases; + for (const auto& AliasIt : m_ShareAliases) + { + if (AliasIt.second.WorkspaceId == WorkspaceId) + { + Aliases.push_back(AliasIt.first); + } + } + + for (const std::string& Alias : Aliases) + { + m_ShareAliases.erase(Alias); + } + m_Workspaces.erase(It); - ZEN_INFO("Removed workspace '{}'", WorkspaceId); + + ZEN_INFO("Removed workspace '{}' and {} aliases", WorkspaceId, Aliases.size()); return true; } return false; @@ -466,7 +486,12 @@ Workspaces::AddWorkspaceShare(const Oid& WorkspaceId, { RwLock::ExclusiveLockScope _(m_Lock); Workspace->SetShare(Configuration.Id, std::move(NewShare)); + if (!Configuration.Alias.empty()) + { + m_ShareAliases.insert_or_assign(Configuration.Alias, ShareAlias{.WorkspaceId = WorkspaceId, .ShareId = Configuration.Id}); + } } + ZEN_INFO("Added workspace share '{}' in workspace '{}' with path '{}'", Configuration.Id, WorkspaceId, Configuration.SharePath); return true; @@ -501,12 +526,20 @@ Workspaces::RemoveWorkspaceShare(const Oid& WorkspaceId, const Oid& ShareId) } } RwLock::ExclusiveLockScope _(m_Lock); - if (!Workspace->GetShare(ShareId)) + Ref<WorkspaceShare> ExistingShare = Workspace->GetShare(ShareId); + if (!ExistingShare) { return false; } + std::string Alias = ExistingShare->GetConfig().Alias; + if (!Alias.empty()) + { + m_ShareAliases.erase(Alias); + } + Workspace->SetShare(ShareId, {}); + ZEN_INFO("Removed workspace share '{}' in workspace '{}'", ShareId, WorkspaceId); return true; } @@ -622,12 +655,15 @@ Workspaces::WriteState(const std::filesystem::path& WorkspaceStatePath) ZEN_INFO("Writing workspaces state to {}", WorkspaceStatePath); + std::filesystem::path TempWritePath = WorkspaceStatePath.parent_path() / (WorkspaceStatePath.filename().string() + "_new"); + CreateDirectories(TempWritePath); + RwLock::SharedLockScope _(m_Lock); for (auto It : m_Workspaces) { const WorkspaceConfiguration& WorkspaceConfig = It.second->GetConfig(); ZEN_ASSERT(WorkspaceConfig.Id == It.first); - std::filesystem::path WorkspaceConfigDir = WorkspaceStatePath / WorkspaceConfig.Id.ToString(); + std::filesystem::path WorkspaceConfigDir = TempWritePath / WorkspaceConfig.Id.ToString(); CreateDirectories(WorkspaceConfigDir); std::string WorkspaceConfigJson = WorkspaceToJson(WorkspaceConfig); TemporaryFile::SafeWriteFile(WorkspaceConfigDir / "config.json"sv, @@ -643,6 +679,24 @@ Workspaces::WriteState(const std::filesystem::path& WorkspaceStatePath) TemporaryFile::SafeWriteFile(ShareConfigDir / "config.json"sv, MemoryView(ShareConfigJson.data(), ShareConfigJson.size())); } } + + // Overwrite with rename + std::filesystem::path OldStateDirectory = WorkspaceStatePath.parent_path() / (WorkspaceStatePath.filename().string() + "_old"); + if (std::filesystem::is_directory(WorkspaceStatePath)) + { + if (std::filesystem::is_directory(OldStateDirectory)) + { + std::filesystem::remove_all(OldStateDirectory); + } + std::filesystem::rename(WorkspaceStatePath, OldStateDirectory); + } + + std::filesystem::rename(TempWritePath, WorkspaceStatePath); + + if (std::filesystem::is_directory(OldStateDirectory)) + { + std::filesystem::remove_all(OldStateDirectory); + } } void @@ -705,6 +759,17 @@ Workspaces::ReadState(const std::filesystem::path& WorkspaceStatePath, std::func } } +std::optional<Workspaces::ShareAlias> +Workspaces::GetShareAlias(std::string_view Alias) const +{ + RwLock::SharedLockScope Lock(m_Lock); + if (auto It = m_ShareAliases.find(std::string(Alias)); It != m_ShareAliases.end()) + { + return It->second; + } + return {}; +} + std::pair<Ref<Workspace>, Ref<WorkspaceShare>> Workspaces::FindWorkspaceShare(const Oid& WorkspaceId, const Oid& ShareId, bool ForceRefresh, WorkerThreadPool& WorkerPool) { @@ -945,6 +1010,58 @@ TEST_CASE("workspace.share.basic") CHECK(!WS.RemoveWorkspace(PathToId(RootPath))); } +TEST_CASE("workspace.share.alias") +{ + using namespace std::literals; + + WorkerThreadPool WorkerPool(std::thread::hardware_concurrency()); + + ScopedTemporaryDirectory TempDir; + std::filesystem::path RootPath = TempDir.Path(); + std::vector<std::pair<std::filesystem::path, IoBuffer>> Content = GenerateFolderContent(RootPath); + + Workspaces WS; + CHECK(WS.AddWorkspace({PathToId(RootPath), RootPath})); + CHECK(WS.AddWorkspaceShare(PathToId(RootPath), + {PathToId("second_folder"), "second_folder", "my_share"}, + [](const std::filesystem::path& Path) { return PathToId(Path); })); + + std::optional<Workspaces::ShareAlias> Alias = WS.GetShareAlias("my_share"); + CHECK(Alias.has_value()); + CHECK(!WS.GetShareAlias("my_share2").has_value()); + + std::filesystem::path SharePath = RootPath / "second_folder"; + std::vector<std::filesystem::path> Paths = {{std::filesystem::relative(Content[4].first, SharePath)}, + {std::filesystem::relative(Content[6].first, SharePath)}, + {std::filesystem::relative(Content[7].first, SharePath)}, + {"the_file_that_is_not_there.txt"}}; + std::vector<IoBuffer> Chunks = WS.GetWorkspaceShareChunks(Alias->WorkspaceId, + Alias->ShareId, + std::vector<Workspaces::ChunkRequest>{{.ChunkId = PathToId(Paths[0])}, + {.ChunkId = PathToId(Paths[1])}, + {.ChunkId = PathToId(Paths[2])}, + {.ChunkId = PathToId(Paths[3])}}, + WorkerPool); + CHECK(Chunks.size() == 4); + CHECK(Chunks[0].GetView().EqualBytes(Content[4].second.GetView())); + CHECK(Chunks[1].GetView().EqualBytes(Content[6].second.GetView())); + CHECK(Chunks[2].GetView().EqualBytes(Content[7].second.GetView())); + CHECK(Chunks[3].GetSize() == 0); + + CHECK(WS.RemoveWorkspaceShare(Alias->WorkspaceId, Alias->ShareId)); + + CHECK(!WS.GetShareAlias("my_share").has_value()); + + CHECK(WS.AddWorkspaceShare(PathToId(RootPath), + {PathToId("second_folder"), "second_folder", "my_share"}, + [](const std::filesystem::path& Path) { return PathToId(Path); })); + CHECK(WS.GetShareAlias("my_share").has_value()); + + CHECK(WS.RemoveWorkspace(PathToId(RootPath))); + CHECK(!WS.RemoveWorkspace(PathToId(RootPath))); + + CHECK(!WS.GetShareAlias("my_share").has_value()); +} #endif void |