// Copyright Epic Games, Inc. All Rights Reserved. #include "projectstore.h" #include #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END namespace { using namespace std::literals; const std::string DefaultCloudAccessTokenEnvVariableName( #if ZEN_PLATFORM_WINDOWS "UE-CloudDataCacheAccessToken"sv #endif #if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC "UE_CloudDataCacheAccessToken"sv #endif ); } // namespace /////////////////////////////////////// DropProjectCommand::DropProjectCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), ""); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), ""); m_Options.parse_positional({"project", "oplog"}); m_Options.positional_help("[ []]"); } DropProjectCommand::~DropProjectCommand() { } int DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return 0; } m_HostName = ResolveTargetHostSpec(m_HostName); if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } if (m_ProjectName.empty()) { throw zen::OptionParseException("Drop command requires a project"); } cpr::Session Session; if (m_OplogName.empty()) { ZEN_CONSOLE("Dropping project '{}' from '{}'", m_ProjectName, m_HostName); Session.SetUrl({fmt::format("{}/prj/{}", m_HostName, m_ProjectName)}); } else { ZEN_CONSOLE("Dropping oplog '{}/{}' from '{}'", m_ProjectName, m_OplogName, m_HostName); Session.SetUrl({fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectName, m_OplogName)}); } cpr::Response Result = Session.Delete(); if (zen::IsHttpSuccessCode(Result.status_code)) { ZEN_CONSOLE("OK: drop succeeded"); return 0; } if (Result.status_code) { ZEN_ERROR("Drop failed: {}: {} ({})", Result.status_code, Result.reason, Result.text); } else { ZEN_ERROR("Drop failed: {}", Result.error.message); } return 1; } /////////////////////////////////////// ProjectInfoCommand::ProjectInfoCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), ""); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), ""); m_Options.parse_positional({"project", "oplog"}); m_Options.positional_help("[ []]"); } ProjectInfoCommand::~ProjectInfoCommand() { } int ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return 0; } m_HostName = ResolveTargetHostSpec(m_HostName); if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } if (!m_OplogName.empty() && m_ProjectName.empty()) { throw zen::OptionParseException("an oplog can't be specified without also specifying a project"); } std::string Url; if (m_ProjectName.empty()) { Url = fmt::format("{}/prj", m_HostName); ZEN_CONSOLE("Info from '{}'", Url); } else if (m_OplogName.empty()) { Url = fmt::format("{}/prj/{}", m_HostName, m_ProjectName); ZEN_CONSOLE("Info on project '{}' from '{}'", m_ProjectName, Url); } else { Url = fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectName, m_OplogName); ZEN_CONSOLE("Info on oplog '{}/{}' from '{}'", m_ProjectName, m_OplogName, Url); } cpr::Session Session; Session.SetHeader(cpr::Header{{"Accept", "application/json"}}); Session.SetUrl(Url); cpr::Response Result = Session.Get(); if (zen::IsHttpSuccessCode(Result.status_code)) { ZEN_CONSOLE("{}", Result.text); return 0; } if (Result.status_code) { ZEN_ERROR("Info failed: {}: {} ({})", Result.status_code, Result.reason, Result.text); } else { ZEN_ERROR("Info failed: {}", Result.error.message); } return 1; } /////////////////////////////////////// CreateProjectCommand::CreateProjectCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), ""); m_Options.add_option("", "", "rootdir", "Absolute path to root directory", cxxopts::value(m_RootDir), ""); m_Options.add_option("", "", "enginedir", "Absolute path to engine root directory", cxxopts::value(m_EngineRootDir), ""); m_Options.add_option("", "", "projectdir", "Absolute path to project directory", cxxopts::value(m_ProjectRootDir), ""); m_Options.add_option("", "", "projectfile", "Absolute path to .uproject file", cxxopts::value(m_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; } 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 (zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("Project already exists.\n{}", Response.text); return 1; } if (Response.status_code == static_cast(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("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(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(""), ""); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), ""); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), ""); m_Options.add_option("", "", "gcpath", "Absolute path to oplog lifetime marker file", cxxopts::value(m_GcPath), ""); 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; } 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 (zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("Oplog already exists.\n{}", Response.text); return 1; } if (Response.status_code == static_cast(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("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } /////////////////////////////////////// ExportOplogCommand::ExportOplogCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), ""); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), ""); m_Options.add_option("", "", "maxblocksize", "Max size for bundled attachments", cxxopts::value(m_MaxBlockSize), ""); m_Options.add_option("", "", "maxchunkembedsize", "Max size for attachment to be bundled", cxxopts::value(m_MaxChunkEmbedSize), ""); m_Options.add_option("", "f", "force", "Force export of all attachments", cxxopts::value(m_Force), ""); m_Options.add_option("", "", "disableblocks", "Disable block creation and save all attachments individually (applies to file and cloud target)", cxxopts::value(m_DisableBlocks), ""); m_Options.add_option("", "", "cloud", "Cloud Storage URL", cxxopts::value(m_CloudUrl), ""); m_Options.add_option("cloud", "", "namespace", "Cloud Storage namespace", cxxopts::value(m_CloudNamespace), ""); m_Options.add_option("cloud", "", "bucket", "Cloud Storage bucket", cxxopts::value(m_CloudBucket), ""); m_Options.add_option("cloud", "", "key", "Cloud Storage key", cxxopts::value(m_CloudKey), ""); m_Options .add_option("cloud", "", "openid-provider", "Cloud Storage openid provider", cxxopts::value(m_CloudOpenIdProvider), ""); m_Options.add_option("cloud", "", "access-token", "Cloud Storage access token", cxxopts::value(m_CloudAccessToken), ""); m_Options.add_option("cloud", "", "access-token-env", "Name of environment variable that holds the cloud Storage access token", cxxopts::value(m_CloudAccessTokenEnv)->default_value(DefaultCloudAccessTokenEnvVariableName), ""); m_Options.add_option("cloud", "", "disabletempblocks", "Disable temp block creation and upload blocks without waiting for oplog container to be uploaded", cxxopts::value(m_CloudDisableTempBlocks), ""); m_Options.add_option("", "", "zen", "Zen service upload address", cxxopts::value(m_ZenUrl), ""); m_Options.add_option("zen", "", "target-project", "Zen target project name", cxxopts::value(m_ZenProjectName), ""); m_Options.add_option("zen", "", "target-oplog", "Zen target oplog name", cxxopts::value(m_ZenOplogName), ""); m_Options.add_option("zen", "", "clean", "Delete existing target Zen oplog", cxxopts::value(m_ZenClean), ""); m_Options.add_option("", "", "file", "Local folder path", cxxopts::value(m_FileDirectoryPath), ""); m_Options.add_option("file", "", "name", "Local file name", cxxopts::value(m_FileName), ""); m_Options.add_option("file", "", "forcetempblocks", "Force creation of temp attachment blocks", cxxopts::value(m_FileForceEnableTempBlocks), ""); m_Options.parse_positional({"project", "oplog"}); } ExportOplogCommand::~ExportOplogCommand() { } int ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return 0; } m_HostName = ResolveTargetHostSpec(m_HostName); if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } if (m_ProjectName.empty()) { throw zen::OptionParseException("project name must be specified"); } if (m_OplogName.empty()) { throw zen::OptionParseException("oplog identifier must be specified"); } size_t TargetCount = 0; TargetCount += m_CloudUrl.empty() ? 0 : 1; TargetCount += m_ZenUrl.empty() ? 0 : 1; TargetCount += m_FileDirectoryPath.empty() ? 0 : 1; if (TargetCount != 1) { if (TargetCount == 0) { throw zen::OptionParseException("an export target must be specified"); } else { throw zen::OptionParseException("a single export target must be specified"); } } cpr::Session Session; if (!m_CloudUrl.empty()) { if (m_CloudNamespace.empty() || m_CloudBucket.empty()) { ZEN_ERROR("Options for cloud target are missing"); ZEN_CONSOLE("{}", m_Options.help({"cloud"}).c_str()); return 1; } if (m_CloudKey.empty()) { std::string KeyString = fmt::format("{}/{}/{}/{}", m_ProjectName, m_OplogName, m_CloudNamespace, m_CloudBucket); zen::IoHash Key = zen::IoHash::HashBuffer(KeyString.data(), KeyString.size()); m_CloudKey = Key.ToHexString(); ZEN_WARN("Using auto generated cloud key '{}'", m_CloudKey); } } if (!m_ZenUrl.empty()) { if (m_ZenProjectName.empty()) { m_ZenProjectName = m_ProjectName; ZEN_WARN("Using default zen target project id '{}'", m_ZenProjectName); } if (m_ZenOplogName.empty()) { m_ZenOplogName = m_OplogName; ZEN_WARN("Using default zen target oplog id '{}'", m_ZenOplogName); } std::string TargetUrlBase = fmt::format("{}/prj", m_ZenUrl); if (TargetUrlBase.find("://") == std::string::npos) { // Assume https URL TargetUrlBase = fmt::format("http://{}", TargetUrlBase); } Session.SetUrl({fmt::format("{}/{}/oplog/{}", TargetUrlBase, m_ZenProjectName, m_ZenOplogName)}); cpr::Response Response = Session.Get(); if (Response.status_code == static_cast(zen::HttpResponseCode::NotFound)) { ZEN_WARN("Automatically creating oplog '{}/{}'", m_ZenProjectName, m_ZenOplogName) Response = Session.Post(); if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } } else if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } else if (m_ZenClean) { ZEN_WARN("Cleaning oplog '{}/{}'", m_ZenProjectName, m_ZenOplogName) Response = Session.Delete(); if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } Response = Session.Post(); if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } } } if (!m_FileDirectoryPath.empty()) { if (m_FileName.empty()) { m_FileName = m_OplogName; ZEN_WARN("Using default file name '{}'", m_FileName); } } const std::string SourceUrlBase = fmt::format("{}/prj", m_HostName); std::string TargetDescription; Session.SetUrl({fmt::format("{}/{}/oplog/{}/rpc", SourceUrlBase, m_ProjectName, m_OplogName)}); Session.SetHeader({{"Content-Type", std::string(zen::MapContentTypeToString(zen::HttpContentType::kCbObject))}}); zen::CbObjectWriter Writer; Writer.AddString("method"sv, "export"sv); Writer.BeginObject("params"sv); { if (m_MaxBlockSize != 0) { Writer.AddInteger("maxblocksize"sv, m_MaxBlockSize); } if (m_MaxChunkEmbedSize != 0) { Writer.AddInteger("maxchunkembedsize"sv, m_MaxChunkEmbedSize); } if (m_Force) { Writer.AddBool("force"sv, true); } if (!m_FileDirectoryPath.empty()) { Writer.BeginObject("file"sv); { Writer.AddString("path"sv, m_FileDirectoryPath); Writer.AddString("name"sv, m_FileName); if (m_DisableBlocks) { Writer.AddBool("disableblocks"sv, true); } if (m_FileForceEnableTempBlocks) { Writer.AddBool("enabletempblocks"sv, true); } } Writer.EndObject(); // "file" TargetDescription = fmt::format("[file] '{}/{}'", m_FileDirectoryPath, m_FileName); } if (!m_CloudUrl.empty()) { Writer.BeginObject("cloud"sv); { Writer.AddString("url"sv, m_CloudUrl); Writer.AddString("namespace"sv, m_CloudNamespace); Writer.AddString("bucket"sv, m_CloudBucket); Writer.AddString("key"sv, m_CloudKey); if (!m_CloudOpenIdProvider.empty()) { Writer.AddString("openid-provider"sv, m_CloudOpenIdProvider); } if (!m_CloudAccessToken.empty()) { Writer.AddString("access-token"sv, m_CloudAccessToken); } if (!m_CloudAccessTokenEnv.empty()) { Writer.AddString("access-token-env"sv, m_CloudAccessTokenEnv); } if (m_DisableBlocks) { Writer.AddBool("disableblocks"sv, true); } if (m_CloudDisableTempBlocks) { Writer.AddBool("disabletempblocks"sv, true); } } Writer.EndObject(); // "cloud" TargetDescription = fmt::format("[cloud] '{}/{}/{}/{}'", m_CloudUrl, m_CloudNamespace, m_CloudBucket, m_CloudKey); } if (!m_ZenUrl.empty()) { Writer.BeginObject("zen"sv); { Writer.AddString("url"sv, m_ZenUrl); Writer.AddString("project"sv, m_ZenProjectName); Writer.AddString("oplog"sv, m_ZenOplogName); } Writer.EndObject(); // "zen" TargetDescription = fmt::format("[zen] '{}/{}/{}'", m_ZenUrl, m_ZenProjectName, m_ZenOplogName); } } Writer.EndObject(); // "params" zen::BinaryWriter MemOut; Writer.Save(MemOut); Session.SetBody(cpr::Body{(const char*)MemOut.GetData(), MemOut.GetSize()}); ZEN_CONSOLE("Saving oplog '{}/{}' from '{}' to {}", m_ProjectName, m_OplogName, m_HostName, TargetDescription); cpr::Response Response = Session.Post(); ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } //////////////////////////// ImportOplogCommand::ImportOplogCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), ""); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), ""); m_Options.add_option("", "", "maxblocksize", "Max size for bundled attachments", cxxopts::value(m_MaxBlockSize), ""); m_Options.add_option("", "", "maxchunkembedsize", "Max size for attachment to be bundled", cxxopts::value(m_MaxChunkEmbedSize), ""); m_Options.add_option("", "f", "force", "Force import of all attachments", cxxopts::value(m_Force), ""); m_Options.add_option("", "", "cloud", "Cloud Storage URL", cxxopts::value(m_CloudUrl), ""); m_Options.add_option("cloud", "", "namespace", "Cloud Storage namespace", cxxopts::value(m_CloudNamespace), ""); m_Options.add_option("cloud", "", "bucket", "Cloud Storage bucket", cxxopts::value(m_CloudBucket), ""); m_Options.add_option("cloud", "", "key", "Cloud Storage key", cxxopts::value(m_CloudKey), ""); m_Options .add_option("cloud", "", "openid-provider", "Cloud Storage openid provider", cxxopts::value(m_CloudOpenIdProvider), ""); m_Options.add_option("cloud", "", "access-token", "Cloud Storage access token", cxxopts::value(m_CloudAccessToken), ""); m_Options.add_option("cloud", "", "access-token-env", "Name of environment variable that holds the cloud Storage access token", cxxopts::value(m_CloudAccessTokenEnv)->default_value(DefaultCloudAccessTokenEnvVariableName), ""); m_Options.add_option("", "", "zen", "Zen service upload address", cxxopts::value(m_ZenUrl), ""); m_Options.add_option("zen", "", "source-project", "Zen source project name", cxxopts::value(m_ZenProjectName), ""); m_Options.add_option("zen", "", "source-oplog", "Zen source oplog name", cxxopts::value(m_ZenOplogName), ""); m_Options.add_option("zen", "", "clean", "Delete existing target Zen oplog", cxxopts::value(m_ZenClean), ""); m_Options.add_option("", "", "file", "Local folder path", cxxopts::value(m_FileDirectoryPath), ""); m_Options.add_option("file", "", "name", "Local file name", cxxopts::value(m_FileName), ""); m_Options.parse_positional({"project", "oplog"}); } ImportOplogCommand::~ImportOplogCommand() { } int ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return 0; } m_HostName = ResolveTargetHostSpec(m_HostName); if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } if (m_ProjectName.empty()) { ZEN_ERROR("Project name must be given"); return 1; } if (m_OplogName.empty()) { ZEN_ERROR("Oplog name must be given"); return 1; } size_t TargetCount = 0; TargetCount += m_CloudUrl.empty() ? 0 : 1; TargetCount += m_ZenUrl.empty() ? 0 : 1; TargetCount += m_FileDirectoryPath.empty() ? 0 : 1; if (TargetCount != 1) { ZEN_ERROR("Provide one source only"); ZEN_CONSOLE("{}", m_Options.help({""}).c_str()); return 1; } cpr::Session Session; if (!m_CloudUrl.empty()) { if (m_CloudNamespace.empty() || m_CloudBucket.empty()) { ZEN_ERROR("Options for cloud source are missing"); ZEN_CONSOLE("{}", m_Options.help({"cloud"}).c_str()); return 1; } if (m_CloudKey.empty()) { std::string KeyString = fmt::format("{}/{}/{}/{}", m_ProjectName, m_OplogName, m_CloudNamespace, m_CloudBucket); zen::IoHash Key = zen::IoHash::HashBuffer(KeyString.data(), KeyString.size()); m_CloudKey = Key.ToHexString(); ZEN_WARN("Using auto generated cloud key '{}'", m_CloudKey); } } if (!m_ZenUrl.empty()) { if (m_ZenProjectName.empty()) { m_ZenProjectName = m_ProjectName; ZEN_WARN("Using default zen target project id '{}'", m_ZenProjectName); } if (m_ZenOplogName.empty()) { m_ZenOplogName = m_OplogName; ZEN_WARN("Using default zen target oplog id '{}'", m_ZenOplogName); } } if (!m_FileDirectoryPath.empty()) { if (m_FileName.empty()) { m_FileName = m_OplogName; ZEN_WARN("Using auto generated file name '{}'", m_FileName); } } const std::string TargetUrlBase = fmt::format("{}/prj", m_HostName); Session.SetUrl({fmt::format("{}/{}/oplog/{}", TargetUrlBase, m_ProjectName, m_OplogName)}); cpr::Response Response = Session.Get(); if (Response.status_code == static_cast(zen::HttpResponseCode::NotFound)) { ZEN_WARN("Automatically creating oplog '{}/{}'", m_ProjectName, m_OplogName) Response = Session.Post(); if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } } else if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } else if (m_ZenClean) { ZEN_WARN("Cleaning oplog '{}/{}'", m_ProjectName, m_OplogName) Response = Session.Delete(); if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } Response = Session.Post(); if (!zen::IsHttpSuccessCode(Response.status_code)) { ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } } std::string SourceDescription; Session.SetUrl(fmt::format("{}/{}/oplog/{}/rpc", TargetUrlBase, m_ProjectName, m_OplogName)); Session.SetHeader({{"Content-Type", std::string(zen::MapContentTypeToString(zen::HttpContentType::kCbObject))}}); zen::CbObjectWriter Writer; Writer.AddString("method"sv, "import"sv); Writer.BeginObject("params"sv); { if (m_Force) { Writer.AddBool("force"sv, true); } if (!m_FileDirectoryPath.empty()) { Writer.BeginObject("file"sv); { Writer.AddString("file"sv, m_FileDirectoryPath); Writer.AddString("name"sv, m_FileName); } Writer.EndObject(); // "file" SourceDescription = fmt::format("[file] '{}/{}'", m_FileDirectoryPath, m_FileName); } if (!m_CloudUrl.empty()) { Writer.BeginObject("cloud"sv); { Writer.AddString("url"sv, m_CloudUrl); Writer.AddString("namespace"sv, m_CloudNamespace); Writer.AddString("bucket"sv, m_CloudBucket); Writer.AddString("key"sv, m_CloudKey); if (!m_CloudOpenIdProvider.empty()) { Writer.AddString("openid-provider"sv, m_CloudOpenIdProvider); } if (!m_CloudAccessToken.empty()) { Writer.AddString("access-token"sv, m_CloudAccessToken); } if (!m_CloudAccessTokenEnv.empty()) { Writer.AddString("access-token-env"sv, m_CloudAccessTokenEnv); } } Writer.EndObject(); // "cloud" SourceDescription = fmt::format("[cloud] '{}/{}/{}/{}'", m_CloudUrl, m_CloudNamespace, m_CloudBucket, m_CloudKey); } if (!m_ZenUrl.empty()) { Writer.BeginObject("zen"sv); { Writer.AddString("url"sv, m_ZenUrl); Writer.AddString("project"sv, m_ZenProjectName); Writer.AddString("oplog"sv, m_ZenOplogName); } Writer.EndObject(); // "zen" SourceDescription = fmt::format("[zen] '{}'", m_ZenUrl); } } Writer.EndObject(); // "params" zen::BinaryWriter MemOut; Writer.Save(MemOut); Session.SetBody(cpr::Body{(const char*)MemOut.GetData(), MemOut.GetSize()}); ZEN_CONSOLE("Loading oplog '{}/{}' from '{}' to {}", m_ProjectName, m_OplogName, SourceDescription, m_HostName); Response = Session.Post(); ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } //////////////////////////// SnapshotOplogCommand::SnapshotOplogCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), ""); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), ""); m_Options.parse_positional({"project", "oplog"}); } SnapshotOplogCommand::~SnapshotOplogCommand() { } int SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { using namespace std::literals; ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return 0; } m_HostName = ResolveTargetHostSpec(m_HostName); if (m_HostName.empty()) { throw zen::OptionParseException("unable to resolve server specification"); } if (m_ProjectName.empty()) { ZEN_ERROR("Project name must be given"); return 1; } if (m_OplogName.empty()) { ZEN_ERROR("Oplog name must be given"); return 1; } cpr::Session Session; const std::string TargetUrlBase = fmt::format("{}/prj", m_HostName); Session.SetUrl(fmt::format("{}/{}/oplog/{}/rpc", TargetUrlBase, m_ProjectName, m_OplogName)); Session.SetHeader( {{"Accept", "application/json"}, {"Content-Type", std::string(zen::MapContentTypeToString(zen::HttpContentType::kCbObject))}}); zen::CbObjectWriter Writer; Writer.AddString("method"sv, "snapshot"sv); zen::BinaryWriter MemOut; Writer.Save(MemOut); Session.SetBody(cpr::Body{(const char*)MemOut.GetData(), MemOut.GetSize()}); ZEN_CONSOLE("Snapshotting oplog '{}/{}' to {}", m_ProjectName, m_OplogName, m_HostName); cpr::Response Response = Session.Post(); ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } //////////////////////////// ProjectStatsCommand::ProjectStatsCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); } ProjectStatsCommand::~ProjectStatsCommand() { } int ProjectStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); 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.SetUrl({fmt::format("{}/stats/prj", m_HostName)}); Session.SetHeader(cpr::Header{{"Accept", "application/json"}}); cpr::Response Result = Session.Get(); if (zen::IsHttpSuccessCode(Result.status_code)) { ZEN_CONSOLE("{}", Result.text); return 0; } if (Result.status_code) { ZEN_ERROR("Info failed: {}: {} ({})", Result.status_code, Result.reason, Result.text); } else { ZEN_ERROR("Info failed: {}", Result.error.message); } return 1; } //////////////////////////// ProjectDetailsCommand::ProjectDetailsCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options.add_option("", "c", "csv", "Output in CSV format (default is JSon)", cxxopts::value(m_CSV), ""); m_Options.add_option("", "d", "details", "Detailed info on oplog", cxxopts::value(m_Details), "
"); m_Options.add_option("", "o", "opdetails", "Details info on oplog body", cxxopts::value(m_OpDetails), ""); m_Options.add_option("", "p", "project", "Project name to get info from", cxxopts::value(m_ProjectName), ""); m_Options.add_option("", "l", "oplog", "Oplog name to get info from", cxxopts::value(m_OplogName), ""); m_Options.add_option("", "i", "opid", "Oid of a specific op info for", cxxopts::value(m_OpId), ""); m_Options.add_option("", "a", "attachmentdetails", "Get detailed information about attachments", cxxopts::value(m_AttachmentDetails), ""); } ProjectDetailsCommand::~ProjectDetailsCommand() { } int ProjectDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); 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; cpr::Parameters Parameters; if (m_OpDetails) { Parameters.Add({"opdetails", "true"}); } if (m_Details) { Parameters.Add({"details", "true"}); } if (m_AttachmentDetails) { Parameters.Add({"attachmentdetails", "true"}); } if (m_CSV) { Parameters.Add({"csv", "true"}); } else { Session.SetHeader(cpr::Header{{"Accept", "application/json"}}); } if (!m_OpId.empty()) { if (m_ProjectName.empty() || m_OplogName.empty()) { ZEN_ERROR("Provide project and oplog name"); ZEN_CONSOLE("{}", m_Options.help({""}).c_str()); return 1; } Session.SetUrl({fmt::format("{}/prj/details$/{}/{}/{}", m_HostName, m_ProjectName, m_OplogName, m_OpId)}); } else if (!m_OplogName.empty()) { if (m_ProjectName.empty()) { ZEN_ERROR("Provide project name"); ZEN_CONSOLE("{}", m_Options.help({""}).c_str()); return 1; } Session.SetUrl({fmt::format("{}/prj/details$/{}/{}", m_HostName, m_ProjectName, m_OplogName)}); } else if (!m_ProjectName.empty()) { Session.SetUrl({fmt::format("{}/prj/details$/{}", m_HostName, m_ProjectName)}); } else { Session.SetUrl({fmt::format("{}/prj/details$", m_HostName)}); } Session.SetParameters(Parameters); cpr::Response Result = Session.Get(); if (zen::IsHttpSuccessCode(Result.status_code)) { ZEN_CONSOLE("{}", Result.text); return 0; } if (Result.status_code) { ZEN_ERROR("Info failed: {}: {} ({})", Result.status_code, Result.reason, Result.text); } else { ZEN_ERROR("Info failed: {}", Result.error.message); } return 1; }