aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2023-08-18 11:21:12 +0200
committerGitHub <[email protected]>2023-08-18 11:21:12 +0200
commit3975db7eb6ab9457c90a32c2744843d9e757b370 (patch)
treebfcebcf91f5c86fd80d0f48c52b123ed8eabfca8 /src
parentcheck oplog op attachments when gathering references for GC (#363) (diff)
downloadzen-3975db7eb6ab9457c90a32c2744843d9e757b370.tar.xz
zen-3975db7eb6ab9457c90a32c2744843d9e757b370.zip
add update/delete endpoint for project and oplog (#353)
* add update endpoint for project store project * add update endpoint for oplog * changelog * Zen command line tool `project-update` Zen command line tool `project-delete` Zen command line tool `oplog-update` Zen command line tool `oplog-delete` * add --force-update option to project/oplog create remove project/oplog update commnad
Diffstat (limited to 'src')
-rw-r--r--src/zen/cmds/projectstore.cpp139
-rw-r--r--src/zen/cmds/projectstore.h37
-rw-r--r--src/zen/zen.cpp4
-rw-r--r--src/zenserver/projectstore/httpprojectstore.cpp91
-rw-r--r--src/zenserver/projectstore/projectstore.cpp40
-rw-r--r--src/zenserver/projectstore/projectstore.h6
6 files changed, 307 insertions, 10 deletions
diff --git a/src/zen/cmds/projectstore.cpp b/src/zen/cmds/projectstore.cpp
index f7dc82569..b2f1cda5d 100644
--- a/src/zen/cmds/projectstore.cpp
+++ b/src/zen/cmds/projectstore.cpp
@@ -189,6 +189,7 @@ CreateProjectCommand::CreateProjectCommand()
m_Options.add_option("", "", "enginedir", "Absolute path to engine root directory", cxxopts::value(m_EngineRootDir), "<engineroot>");
m_Options.add_option("", "", "projectdir", "Absolute path to project directory", cxxopts::value(m_ProjectRootDir), "<projectroot>");
m_Options.add_option("", "", "projectfile", "Absolute path to .uproject file", cxxopts::value(m_ProjectFile), "<projectfile>");
+ m_Options.add_option("", "f", "force-update", "Force update of existing project", cxxopts::value(m_ForceUpdate), "<force-update>");
m_Options.parse_positional({"project", "rootdir", "enginedir", "projectdir", "projectfile"});
}
@@ -224,13 +225,13 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
Session.SetUrl({fmt::format("{}/prj/{}", m_HostName, m_ProjectId)});
cpr::Response Response = Session.Get();
- if (zen::IsHttpSuccessCode(Response.status_code))
+ if (zen::IsHttpSuccessCode(Response.status_code) && !m_ForceUpdate)
{
ZEN_CONSOLE("Project already exists.\n{}", Response.text);
return 1;
}
- if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound))
+ if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound) || m_ForceUpdate)
{
zen::CbObjectWriter Project;
Project.AddString("id"sv, m_ProjectId);
@@ -241,9 +242,68 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
zen::IoBuffer ProjectPayload = Project.Save().GetBuffer().AsIoBuffer();
Session.SetBody(cpr::Body{(const char*)ProjectPayload.GetData(), ProjectPayload.GetSize()});
Session.SetHeader(cpr::Header{{"Accept", "text"}});
- Response = Session.Post();
+ Response = m_ForceUpdate ? Session.Post() : Session.Put();
+ }
+
+ ZEN_CONSOLE("{}", FormatHttpResponse(Response));
+ return MapHttpToCommandReturnCode(Response);
+}
+
+///////////////////////////////////////
+
+DeleteProjectCommand::DeleteProjectCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+ m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>");
+ m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>");
+}
+
+DeleteProjectCommand::~DeleteProjectCommand() = default;
+
+int
+DeleteProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ using namespace std::literals;
+
+ if (!ParseOptions(argc, argv))
+ {
+ return 0;
+ }
+
+ m_HostName = ResolveTargetHostSpec(m_HostName);
+
+ if (m_HostName.empty())
+ {
+ throw zen::OptionParseException("unable to resolve server specification");
}
+ cpr::Session Session;
+ Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
+
+ if (m_ProjectId.empty())
+ {
+ ZEN_ERROR("Project name must be given");
+ return 1;
+ }
+
+ Session.SetUrl({fmt::format("{}/prj/{}", m_HostName, m_ProjectId)});
+ cpr::Response Response = Session.Get();
+ if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound))
+ {
+ ZEN_CONSOLE("Project does not exist.\n{}", Response.text);
+ return 1;
+ }
+ if (!zen::IsHttpSuccessCode(Response.status_code))
+ {
+ ZEN_CONSOLE("{}", FormatHttpResponse(Response));
+ return 1;
+ }
+
+ Session.SetHeader(cpr::Header{{"Accept", "text"}});
+ Response = Session.Delete();
+
ZEN_CONSOLE("{}", FormatHttpResponse(Response));
return MapHttpToCommandReturnCode(Response);
}
@@ -257,6 +317,7 @@ CreateOplogCommand::CreateOplogCommand()
m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>");
m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), "<oplogid>");
m_Options.add_option("", "", "gcpath", "Absolute path to oplog lifetime marker file", cxxopts::value(m_GcPath), "<path>");
+ m_Options.add_option("", "f", "force-update", "Force update of existing oplog", cxxopts::value(m_ForceUpdate), "<force-update>");
m_Options.parse_positional({"project", "oplog", "gcpath"});
}
@@ -296,13 +357,13 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
Session.SetUrl({fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectId, m_OplogId)});
cpr::Response Response = Session.Get();
- if (zen::IsHttpSuccessCode(Response.status_code))
+ if (zen::IsHttpSuccessCode(Response.status_code) && !m_ForceUpdate)
{
ZEN_CONSOLE("Oplog already exists.\n{}", Response.text);
return 1;
}
- if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound))
+ if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound) || m_ForceUpdate)
{
Session.SetHeader(cpr::Header{{"Accept", "text"}});
if (!m_GcPath.empty())
@@ -314,7 +375,7 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
Session.SetHeader(cpr::Header{{"Accept", "text"}, {"Content-Type", std::string(ToString(zen::HttpContentType::kCbObject))}});
}
- Response = Session.Post();
+ Response = m_ForceUpdate ? Session.Post() : Session.Put();
}
ZEN_CONSOLE("{}", FormatHttpResponse(Response));
@@ -324,6 +385,72 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
///////////////////////////////////////
+DeleteOplogCommand::DeleteOplogCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+ m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>");
+ m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>");
+ m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), "<oplogid>");
+ m_Options.parse_positional({"project", "oplog", "gcpath"});
+}
+
+DeleteOplogCommand::~DeleteOplogCommand() = default;
+
+int
+DeleteOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ using namespace std::literals;
+
+ if (!ParseOptions(argc, argv))
+ {
+ return 0;
+ }
+
+ m_HostName = ResolveTargetHostSpec(m_HostName);
+
+ if (m_HostName.empty())
+ {
+ throw zen::OptionParseException("unable to resolve server specification");
+ }
+
+ cpr::Session Session;
+ Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
+
+ if (m_ProjectId.empty())
+ {
+ throw zen::OptionParseException("project name must be specified");
+ }
+
+ if (m_OplogId.empty())
+ {
+ throw zen::OptionParseException("oplog name must be specified");
+ }
+
+ Session.SetUrl({fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectId, m_OplogId)});
+ cpr::Response Response = Session.Get();
+ if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound))
+ {
+ ZEN_CONSOLE("Oplog does not exist.\n{}", Response.text);
+ return 1;
+ }
+ if (!zen::IsHttpSuccessCode(Response.status_code))
+ {
+ ZEN_CONSOLE("{}", FormatHttpResponse(Response));
+ return 1;
+ }
+
+ Session.SetHeader(cpr::Header{{"Accept", "text"}});
+ Response = Session.Delete();
+
+ ZEN_CONSOLE("{}", FormatHttpResponse(Response));
+
+ return MapHttpToCommandReturnCode(Response);
+}
+
+///////////////////////////////////////
+
ExportOplogCommand::ExportOplogCommand()
{
m_Options.add_options()("h,help", "Print help");
diff --git a/src/zen/cmds/projectstore.h b/src/zen/cmds/projectstore.h
index 01675e885..5b1cd05db 100644
--- a/src/zen/cmds/projectstore.h
+++ b/src/zen/cmds/projectstore.h
@@ -45,13 +45,29 @@ public:
virtual cxxopts::Options& Options() override { return m_Options; }
private:
- cxxopts::Options m_Options{"project-create", "Create project"};
+ cxxopts::Options m_Options{"project-create", "Create project, the project must not already exist."};
std::string m_HostName;
std::string m_ProjectId;
std::string m_RootDir;
std::string m_EngineRootDir;
std::string m_ProjectRootDir;
std::string m_ProjectFile;
+ bool m_ForceUpdate = false;
+};
+
+class DeleteProjectCommand : public ZenCmdBase
+{
+public:
+ DeleteProjectCommand();
+ ~DeleteProjectCommand();
+
+ virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+
+private:
+ cxxopts::Options m_Options{"project-delete", "Delete project and all its oplogs"};
+ std::string m_HostName;
+ std::string m_ProjectId;
};
class CreateOplogCommand : public ZenCmdBase
@@ -64,11 +80,28 @@ public:
virtual cxxopts::Options& Options() override { return m_Options; }
private:
- cxxopts::Options m_Options{"oplog-create", "Create oplog"};
+ cxxopts::Options m_Options{"oplog-create", "Create oplog in an existing project, the oplog must not already exist."};
std::string m_HostName;
std::string m_ProjectId;
std::string m_OplogId;
std::string m_GcPath;
+ bool m_ForceUpdate = false;
+};
+
+class DeleteOplogCommand : public ZenCmdBase
+{
+public:
+ DeleteOplogCommand();
+ ~DeleteOplogCommand();
+
+ virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+
+private:
+ cxxopts::Options m_Options{"oplog-delete", "Delete oplog and all its data"};
+ std::string m_HostName;
+ std::string m_ProjectId;
+ std::string m_OplogId;
};
class ExportOplogCommand : public ZenCmdBase
diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp
index 6031c8be6..6772e7ab4 100644
--- a/src/zen/zen.cpp
+++ b/src/zen/zen.cpp
@@ -188,7 +188,9 @@ main(int argc, char** argv)
CacheStatsCommand CacheStatsCmd;
CopyCommand CopyCmd;
CreateOplogCommand CreateOplogCmd;
+ DeleteOplogCommand DeleteOplogCmd;
CreateProjectCommand CreateProjectCmd;
+ DeleteProjectCommand DeleteProjectCmd;
DedupCommand DedupCmd;
DownCommand DownCmd;
DropCommand DropCmd;
@@ -238,12 +240,14 @@ main(int argc, char** argv)
{"gc", &GcCmd, "Garbage collect zen storage"},
{"hash", &HashCmd, "Compute file hashes"},
{"oplog-create", &CreateOplogCmd, "Create a project oplog"},
+ {"oplog-delete", &DeleteOplogCmd, "Delete a project oplog"},
{"oplog-export", &ExportOplogCmd, "Export project store oplog"},
{"oplog-import", &ImportOplogCmd, "Import project store oplog"},
{"oplog-snapshot", &SnapshotOplogCmd, "Snapshot project store oplog"},
{"print", &PrintCmd, "Print compact binary object"},
{"printpackage", &PrintPkgCmd, "Print compact binary package"},
{"project-create", &CreateProjectCmd, "Create a project"},
+ {"project-delete", &DeleteProjectCmd, "Delete a project"},
{"project-details", &ProjectDetailsCmd, "Details on project store"},
{"project-drop", &ProjectDropCmd, "Drop project or project oplog"},
{"project-info", &ProjectInfoCmd, "Info on project or project oplog"},
diff --git a/src/zenserver/projectstore/httpprojectstore.cpp b/src/zenserver/projectstore/httpprojectstore.cpp
index f52473d99..124f26692 100644
--- a/src/zenserver/projectstore/httpprojectstore.cpp
+++ b/src/zenserver/projectstore/httpprojectstore.cpp
@@ -1035,6 +1035,45 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
}
break;
+ case HttpVerb::kPut:
+ {
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+
+ std::filesystem::path OplogMarkerPath;
+ if (CbObject Params = HttpReq.ReadPayloadObject())
+ {
+ OplogMarkerPath = Params["gcpath"sv].AsString();
+ }
+
+ ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
+ if (!FoundLog)
+ {
+ if (!Project->NewOplog(OplogId, OplogMarkerPath))
+ {
+ // TODO: indicate why the operation failed!
+ return HttpReq.WriteResponse(HttpResponseCode::InternalServerError);
+ }
+ Project->TouchOplog(OplogId);
+
+ m_ProjectStats.OpLogWriteCount++;
+ ZEN_INFO("established oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+
+ return HttpReq.WriteResponse(HttpResponseCode::Created);
+ }
+ Project->TouchOplog(OplogId);
+
+ FoundLog->Update(OplogMarkerPath);
+
+ m_ProjectStats.OpLogWriteCount++;
+ ZEN_INFO("updated oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
+
+ return HttpReq.WriteResponse(HttpResponseCode::OK);
+ }
+ break;
+
case HttpVerb::kDelete:
{
ZEN_INFO("deleting oplog '{}/{}'", ProjectId, OplogId);
@@ -1050,7 +1089,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
break;
}
},
- HttpVerb::kPost | HttpVerb::kGet | HttpVerb::kDelete);
+ HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete);
m_Router.RegisterRoute(
"{project}/oplog/{log}/entries",
@@ -1150,6 +1189,54 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
}
break;
+ case HttpVerb::kPut:
+ {
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+
+ IoBuffer Payload = HttpReq.ReadPayload();
+ CbObject Params = LoadCompactBinaryObject(Payload);
+ std::string_view Root = Params["root"sv].AsString(); // Workspace root (i.e `D:/UE5/`)
+ std::string_view EngineRoot = Params["engine"sv].AsString(); // Engine root (i.e `D:/UE5/Engine`)
+ std::string_view ProjectRoot =
+ Params["project"sv].AsString(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
+ std::string_view ProjectFilePath =
+ Params["projectfile"sv].AsString(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
+
+ if (m_ProjectStore->UpdateProject(ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath))
+ {
+ m_ProjectStats.ProjectWriteCount++;
+ ZEN_INFO("updated project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
+ ProjectId,
+ Root,
+ EngineRoot,
+ ProjectRoot,
+ ProjectFilePath,
+ ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+
+ HttpReq.WriteResponse(HttpResponseCode::OK);
+ }
+ else
+ {
+ const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
+ m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
+
+ m_ProjectStats.ProjectWriteCount++;
+ ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
+ ProjectId,
+ Root,
+ EngineRoot,
+ ProjectRoot,
+ ProjectFilePath,
+ ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+
+ HttpReq.WriteResponse(HttpResponseCode::Created);
+ }
+ }
+ break;
+
case HttpVerb::kGet:
{
Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
@@ -1212,7 +1299,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
break;
}
},
- HttpVerb::kGet | HttpVerb::kPost | HttpVerb::kDelete);
+ HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete);
// Push a oplog container
m_Router.RegisterRoute(
diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp
index 38674d28d..d20466161 100644
--- a/src/zenserver/projectstore/projectstore.cpp
+++ b/src/zenserver/projectstore/projectstore.cpp
@@ -559,6 +559,16 @@ ProjectStore::Oplog::Write()
}
void
+ProjectStore::Oplog::Update(const std::filesystem::path& MarkerPath)
+{
+ if (m_MarkerPath == MarkerPath)
+ {
+ return;
+ }
+ Write();
+}
+
+void
ProjectStore::Oplog::ReplayLog()
{
RwLock::ExclusiveLockScope OplogLock(m_OplogLock);
@@ -1814,6 +1824,36 @@ ProjectStore::NewProject(const std::filesystem::path& BasePath,
}
bool
+ProjectStore::UpdateProject(std::string_view ProjectId,
+ std::string_view RootDir,
+ std::string_view EngineRootDir,
+ std::string_view ProjectRootDir,
+ std::string_view ProjectFilePath)
+{
+ ZEN_TRACE_CPU("ProjectStore::UpdateProject");
+
+ ZEN_INFO("updating project {}", ProjectId);
+
+ RwLock::ExclusiveLockScope ProjectsLock(m_ProjectsLock);
+
+ auto ProjIt = m_Projects.find(std::string{ProjectId});
+
+ if (ProjIt == m_Projects.end())
+ {
+ return false;
+ }
+ Ref<ProjectStore::Project> Prj = ProjIt->second;
+
+ Prj->RootDir = RootDir;
+ Prj->EngineRootDir = EngineRootDir;
+ Prj->ProjectRootDir = ProjectRootDir;
+ Prj->ProjectFilePath = ProjectFilePath;
+ Prj->Write();
+
+ return true;
+}
+
+bool
ProjectStore::DeleteProject(std::string_view ProjectId)
{
ZEN_TRACE_CPU("ProjectStore::DeleteProject");
diff --git a/src/zenserver/projectstore/projectstore.h b/src/zenserver/projectstore/projectstore.h
index eef1b15e0..dcbe8cdab 100644
--- a/src/zenserver/projectstore/projectstore.h
+++ b/src/zenserver/projectstore/projectstore.h
@@ -82,6 +82,7 @@ public:
void Read();
void Write();
+ void Update(const std::filesystem::path& MarkerPath);
void IterateFileMap(std::function<void(const Oid&, const std::string_view& ServerPath, const std::string_view& ClientPath)>&& Fn);
void IterateOplog(std::function<void(CbObject)>&& Fn);
@@ -257,6 +258,11 @@ public:
std::string_view EngineRootDir,
std::string_view ProjectRootDir,
std::string_view ProjectFilePath);
+ bool UpdateProject(std::string_view ProjectId,
+ std::string_view RootDir,
+ std::string_view EngineRootDir,
+ std::string_view ProjectRootDir,
+ std::string_view ProjectFilePath);
bool DeleteProject(std::string_view ProjectId);
bool Exists(std::string_view ProjectId);
void Flush();