// Copyright Epic Games, Inc. All Rights Reserved. #include "serve_cmd.h" #include #include #include #include #include #include #include #if ZEN_PLATFORM_WINDOWS # include // TEMPORARY HACK #endif namespace zen { using namespace std::literals; ServeCommand::ServeCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", kHostUrlHelp, 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("", "", "path", "Root path to directory", cxxopts::value(m_RootPath), ""); m_Options.parse_positional({"project", "path"}); m_Options.positional_help("[ ]"); } ServeCommand::~ServeCommand() { } void ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return; } if (m_ProjectName.empty()) { throw OptionParseException("'--project' is required", m_Options.help()); } if (m_OplogName.empty()) { if (auto pos = m_ProjectName.find_first_of('/'); pos != std::string::npos) { m_OplogName = m_ProjectName.substr(pos + 1); m_ProjectName = m_ProjectName.substr(0, pos); } else { throw OptionParseException("'--oplog' is required", m_Options.help()); } } if (m_RootPath.empty()) { throw OptionParseException("'--path' is required", m_Options.help()); } if (!IsDir(m_RootPath)) { throw std::runtime_error(fmt::format("'--path' ('{}') must exist and must be a directory", m_RootPath)); } uint16_t ServerPort = 0; m_HostName = ResolveTargetHostSpec(m_HostName, ServerPort); ZenServerEnvironment ServerEnvironment; std::optional ServerInstance; if (m_HostName.empty()) { // Spawn a server try { std::filesystem::path ExePath = zen::GetRunningExecutablePath(); ServerEnvironment.Initialize(ExePath.parent_path()); ServerInstance.emplace(ServerEnvironment); ServerInstance->SetOwnerPid(zen::GetCurrentProcessId()); ServerInstance->SpawnServerAndWait(ServerPort); } catch (const std::exception& Ex) { ZEN_CONSOLE_ERROR("Failed to spawn server on port {}: '{}'", ServerPort, Ex.what()); throw std::runtime_error("Unable to resolve server specification (even after spawning server)"); } } else { std::filesystem::path ExePath = zen::GetRunningExecutablePath(); ServerEnvironment.Initialize(ExePath.parent_path()); ServerInstance.emplace(ServerEnvironment); ServerInstance->DisableShutdownOnDestroy(); ServerInstance->AttachToRunningServer(); } if (ServerInstance) { m_HostName = ServerInstance->GetBaseUri(); ZEN_CONSOLE("Base uri: {}", m_HostName); } // Generate manifest for tree FileSystemTraversal Traversal; struct FsVisitor : public FileSystemTraversal::TreeVisitor { virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize, uint32_t, uint64_t) override { std::filesystem::path ServerPath = std::filesystem::relative(Parent / File, RootPath); std::string ServerPathString = reinterpret_cast(ServerPath.generic_u8string().c_str()); if (ServerPathString.starts_with("./")) { ServerPathString = ServerPathString.substr(2); } Files.emplace_back(FileEntry{ServerPathString, ServerPathString, FileSize}); } virtual bool VisitDirectory(const std::filesystem::path&, const path_view&, uint32_t) override { return true; } struct FileEntry { std::string FilePath; std::string ClientFilePath; uint64_t FileSize; }; std::filesystem::path RootPath; std::vector Files; }; FsVisitor Visitor; Visitor.RootPath = m_RootPath; Traversal.TraverseFileSystem(m_RootPath, Visitor); CbObjectWriter Cbo; Cbo << "key" << "file_manifest"; Cbo.BeginArray("files"); for (const FsVisitor::FileEntry& Entry : Visitor.Files) { ZEN_CONSOLE("File: {}", Entry.FilePath); Cbo.BeginObject(); BLAKE3 Hash = BLAKE3::HashMemory(Entry.ClientFilePath.data(), Entry.ClientFilePath.size()); Hash.Hash[11] = 7; // FIoChunkType::ExternalFile Oid FileChunkId = Oid::FromMemory(Hash.Hash); Cbo << "id"sv << FileChunkId; Cbo << "serverpath"sv << Entry.FilePath; Cbo << "clientpath"sv << Entry.ClientFilePath; Cbo.EndObject(); } Cbo.EndArray(); CbObject Manifest = Cbo.Save(); // Persist manifest const std::string ProjectUri = fmt::format("/prj/{}", m_ProjectName); const std::string ProjectOplogUri = fmt::format("/prj/{}/oplog/{}", m_ProjectName, m_OplogName); HttpClient Client = CreateHttpClient(m_HostName); // Ensure project exists if (HttpClient::Response ProjectResponse = Client.Get(ProjectUri); !ProjectResponse) { // Create project CbObjectWriter Project; Project << "root" << m_RootPath; if (auto NewProjectResponse = Client.Post(ProjectUri, Project.Save()); !NewProjectResponse) { NewProjectResponse.ThrowError("Failed to create project"); } } // Ensure oplog exists if (HttpClient::Response OplogResponse = Client.Get(ProjectOplogUri); !OplogResponse) { // Create oplog CbObjectWriter Oplog; if (auto NewOplogResponse = Client.Post(ProjectOplogUri, Oplog.Save()); !NewOplogResponse) { NewOplogResponse.ThrowError("Failed to create oplog"); } } // Append manifest const std::string Uri = fmt::format("/prj/{}/oplog/{}/new", m_ProjectName, m_OplogName); HttpClient::Response HttpResponse = Client.Post(Uri, Manifest); if (!HttpResponse) { HttpResponse.ThrowError("Failed to append manifest"); } ZEN_CONSOLE("Ok serving files now"); #if ZEN_PLATFORM_WINDOWS _getch(); // TEMPORARY HACK #endif } } // namespace zen