diff options
| author | Dan Engelbrecht <[email protected]> | 2023-02-02 10:51:07 -0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-02-02 19:51:07 +0100 |
| commit | c9a2721560dc4cd8bc2d2190126bb86c3ab8c3d7 (patch) | |
| tree | 24e932b290ddf1e2f98651ab0b39273a6227b2ce | |
| parent | Reduce lock scopes in oplog (#220) (diff) | |
| download | zen-c9a2721560dc4cd8bc2d2190126bb86c3ab8c3d7.tar.xz zen-c9a2721560dc4cd8bc2d2190126bb86c3ab8c3d7.zip | |
Add `project-create` and `oplog-create` to zen command line tool (#221)
* Add `project-create` and `oplog-create` to zen command line tool
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | zen/cmds/projectstore.cpp | 132 | ||||
| -rw-r--r-- | zen/cmds/projectstore.h | 36 | ||||
| -rw-r--r-- | zen/zen.cpp | 66 | ||||
| -rw-r--r-- | zen/zen.h | 9 |
5 files changed, 242 insertions, 3 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b5690c28c..3bb224d8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ## +- Feature: Zen command line tool `project-create` to create a project store project +- Feature: Zen command line tool `oplog-create` to create a project store oplog - Improvement: Faster oplog replay - reduces time to open an existing oplog - Improvement: Clearer error messages and logging when requests to project store fails - Changed: Removed remnants of old mesh experiment diff --git a/zen/cmds/projectstore.cpp b/zen/cmds/projectstore.cpp index 03e1130cb..d31a11abc 100644 --- a/zen/cmds/projectstore.cpp +++ b/zen/cmds/projectstore.cpp @@ -253,6 +253,138 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg /////////////////////////////////////// +CreateProjectCommand::CreateProjectCommand() +{ + m_Options.add_options()("h,help", "Print help"); + m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value("http://localhost:1337"), "<hosturl>"); + m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>"); + m_Options.add_option("", "", "rootdir", "Absolute path to root directory", cxxopts::value(m_RootDir), "<root>"); + 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.parse_positional({"project", "rootdir", "enginedir", "projectdir", "projectfile"}); +} + +CreateProjectCommand::~CreateProjectCommand() = default; + +int +CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) +{ + ZEN_UNUSED(GlobalOptions); + + using namespace std::literals; + + if (!ParseOptions(argc, argv)) + { + return 0; + } + + 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 (zen::IsHttpSuccessCode(Response.status_code)) + { + ZEN_CONSOLE("Project already exists.\n{}", Response.text); + return 1; + } + + if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound)) + { + zen::CbObjectWriter Project; + Project.AddString("id"sv, m_ProjectId); + Project.AddString("root"sv, m_RootDir); + Project.AddString("engine"sv, m_EngineRootDir); + Project.AddString("project"sv, m_ProjectRootDir); + Project.AddString("projectfile"sv, m_ProjectFile); + 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(); + } + + ZEN_CONSOLE("{}", FormatResponse(Response)); + return GetReturnCode(Response); +} + +/////////////////////////////////////// + +CreateOplogCommand::CreateOplogCommand() +{ + m_Options.add_options()("h,help", "Print help"); + m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value("http://localhost:1337"), "<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.add_option("", "", "gcpath", "Absolute path to oplog lifetime marker file", cxxopts::value(m_GcPath), "<path>"); + m_Options.parse_positional({"project", "oplog", "gcpath"}); +} + +CreateOplogCommand::~CreateOplogCommand() = default; + +int +CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) +{ + ZEN_UNUSED(GlobalOptions); + + using namespace std::literals; + + if (!ParseOptions(argc, argv)) + { + return 0; + } + + cpr::Session Session; + Session.SetHeader(cpr::Header{{"Accept", "application/json"}}); + + if (m_ProjectId.empty()) + { + ZEN_ERROR("Project name must be given"); + return 1; + } + + if (m_OplogId.empty()) + { + ZEN_ERROR("Oplog name must be given"); + return 1; + } + + Session.SetUrl({fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectId, m_OplogId)}); + cpr::Response Response = Session.Get(); + if (zen::IsHttpSuccessCode(Response.status_code)) + { + ZEN_CONSOLE("Oplog already exists.\n{}", Response.text); + return 1; + } + + if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound)) + { + Session.SetHeader(cpr::Header{{"Accept", "text"}}); + if (!m_GcPath.empty()) + { + zen::CbObjectWriter Oplog; + Oplog.AddString("gcpath"sv, m_GcPath); + zen::IoBuffer OplogPayload = Oplog.Save().GetBuffer().AsIoBuffer(); + Session.SetBody(cpr::Body{(const char*)OplogPayload.GetData(), OplogPayload.GetSize()}); + Session.SetHeader(cpr::Header{{"Accept", "text"}, {"Content-Type", std::string(ToString(zen::HttpContentType::kCbObject))}}); + } + + Response = Session.Post(); + } + + ZEN_CONSOLE("{}", FormatResponse(Response)); + + return GetReturnCode(Response); +} + +/////////////////////////////////////// + ExportProjectCommand::ExportProjectCommand() { m_Options.add_options()("h,help", "Print help"); diff --git a/zen/cmds/projectstore.h b/zen/cmds/projectstore.h index 5d42afd81..1bdcbdfac 100644 --- a/zen/cmds/projectstore.h +++ b/zen/cmds/projectstore.h @@ -35,6 +35,42 @@ private: std::string m_OplogName; }; +class CreateProjectCommand : public ZenCmdBase +{ +public: + CreateProjectCommand(); + ~CreateProjectCommand(); + + 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-create", "Create project"}; + std::string m_HostName; + std::string m_ProjectId; + std::string m_RootDir; + std::string m_EngineRootDir; + std::string m_ProjectRootDir; + std::string m_ProjectFile; +}; + +class CreateOplogCommand : public ZenCmdBase +{ +public: + CreateOplogCommand(); + ~CreateOplogCommand(); + + 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-create", "Create oplog"}; + std::string m_HostName; + std::string m_ProjectId; + std::string m_OplogId; + std::string m_GcPath; +}; + class ExportProjectCommand : public ZenCmdBase { public: diff --git a/zen/zen.cpp b/zen/zen.cpp index 180dea5f4..d7b552814 100644 --- a/zen/zen.cpp +++ b/zen/zen.cpp @@ -24,12 +24,17 @@ #include <zencore/string.h> #include <zencore/zencore.h> +#include <zenhttp/httpcommon.h> + #if ZEN_WITH_TESTS # define ZEN_TEST_WITH_RUNNER # include <zencore/testing.h> #endif +ZEN_THIRD_PARTY_INCLUDES_START +#include <cpr/cpr.h> #include <gsl/gsl-lite.hpp> +ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_USE_MIMALLOC # include <mimalloc-new-delete.h> @@ -87,6 +92,63 @@ ZenCmdBase::ParseOptions(int argc, char** argv) return true; } +std::string +ZenCmdBase::FormatResponse(const cpr::Response& Response) +{ + if (Response.error.code != cpr::ErrorCode::OK) + { + if (Response.error.message.empty()) + { + return fmt::format("Request '{}' failed, error code {}", Response.url.str(), static_cast<int>(Response.error.code)); + } + return fmt::format("Request '{}' failed. Reason: '{}' ({})", + Response.url.str(), + Response.error.message, + static_cast<int>(Response.error.code)); + } + + std::string Content; + if (auto It = Response.header.find("Content-Type"); It != Response.header.end()) + { + zen::HttpContentType ContentType = zen::ParseContentType(It->second); + if (ContentType == zen::HttpContentType::kText) + { + Content = fmt::format("'{}'", Response.text); + } + else if (ContentType == zen::HttpContentType::kJSON) + { + Content = fmt::format("\n{}", Response.text); + } + else if (!Response.text.empty()) + { + Content = fmt::format("[{}]", MapContentTypeToString(ContentType)); + } + } + + std::string_view ResponseString = zen::ReasonStringForHttpResultCode( + Response.status_code == static_cast<long>(zen::HttpResponseCode::NoContent) ? static_cast<long>(zen::HttpResponseCode::OK) + : Response.status_code); + if (Content.empty()) + { + return std::string(ResponseString); + } + return fmt::format("{}: {}", ResponseString, Content); +} + +int +ZenCmdBase::GetReturnCode(const cpr::Response& Response) +{ + if (zen::IsHttpSuccessCode(Response.status_code)) + { + return 0; + } + if (Response.error.code != cpr::ErrorCode::OK) + { + return static_cast<int>(Response.error.code); + } + return 1; +} + #if ZEN_WITH_TESTS class RunTestsCommand : public ZenCmdBase @@ -159,6 +221,8 @@ main(int argc, char** argv) CacheInfoCommand CacheInfoCmd; DropProjectCommand ProjectDropCmd; ProjectInfoCommand ProjectInfoCmd; + CreateProjectCommand CreateProjectCmd; + CreateOplogCommand CreateOplogCmd; GcCommand GcCmd; GcStatusCommand GcStatusCmd; @@ -191,6 +255,8 @@ main(int argc, char** argv) {"cache-info", &CacheInfoCmd, "Info on cache, namespace or bucket"}, {"project-drop", &ProjectDropCmd, "Drop project or project oplog"}, {"project-info", &ProjectInfoCmd, "Info on project or project oplog"}, + {"project-create", &CreateProjectCmd, "Create a project"}, + {"oplog-create", &CreateOplogCmd, "Create a project oplog"}, {"gc", &GcCmd, "Garbage collect zen storage"}, {"gc-status", &GcStatusCmd, "Garbage collect zen storage status check"}, #if ZEN_WITH_TESTS @@ -2,7 +2,6 @@ #pragma once -#include <zencore/refcount.h> #include <zencore/zencore.h> ZEN_THIRD_PARTY_INCLUDES_START @@ -13,7 +12,9 @@ ZEN_THIRD_PARTY_INCLUDES_END # include <zencore/windows.h> #endif -#include <filesystem> +namespace cpr { +class Response; +} struct ZenCliOptions { @@ -31,5 +32,7 @@ public: virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) = 0; virtual cxxopts::Options& Options() = 0; - bool ParseOptions(int argc, char** argv); + bool ParseOptions(int argc, char** argv); + static std::string FormatResponse(const cpr::Response& Response); + static int GetReturnCode(const cpr::Response& Response); }; |