aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2024-06-04 22:21:06 +0200
committerGitHub Enterprise <[email protected]>2024-06-04 22:21:06 +0200
commit3a82e1cb3b5a284d2c47e03d541a35b84589314b (patch)
treed4b6c3d7c548b2d6f83b105fb0a5668ca2152889 /src
parentadd batching of CacheStore requests for GetCacheValues/GetCacheChunks (#90) (diff)
downloadzen-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.cpp129
-rw-r--r--src/zen/cmds/workspaces_cmd.h2
-rw-r--r--src/zenserver/workspaces/httpworkspaces.cpp623
-rw-r--r--src/zenserver/workspaces/httpworkspaces.h14
-rw-r--r--src/zenstore/include/zenstore/workspaces.h15
-rw-r--r--src/zenstore/workspaces.cpp125
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