// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END namespace zen { struct MinioProcess::Impl { Impl(const MinioProcessOptions& Options) : m_Options(Options), m_HttpClient(fmt::format("http://localhost:{}/", Options.Port)) {} ~Impl() = default; void SpawnMinioServer() { if (m_ProcessHandle.IsValid()) { return; } // Create a clean temp data directory, removing any stale data from a previous run std::error_code Ec; m_DataDir = std::filesystem::temp_directory_path(Ec) / fmt::format("zen-minio-{}", GetCurrentProcessId()); if (Ec) { ZEN_WARN("MinIO: Failed to get temp directory: {}", Ec.message()); return; } std::filesystem::remove_all(m_DataDir, Ec); Ec.clear(); std::filesystem::create_directories(m_DataDir, Ec); if (Ec) { ZEN_WARN("MinIO: Failed to create data directory '{}': {}", m_DataDir.string(), Ec.message()); return; } CreateProcOptions Options; Options.Flags |= CreateProcOptions::Flag_Windows_NewProcessGroup; Options.Environment.emplace_back("MINIO_ROOT_USER", m_Options.RootUser); Options.Environment.emplace_back("MINIO_ROOT_PASSWORD", m_Options.RootPassword); const std::filesystem::path MinioExe = GetRunningExecutablePath().parent_path() / ("minio" ZEN_EXE_SUFFIX_LITERAL); std::string CommandLine = fmt::format("minio" ZEN_EXE_SUFFIX_LITERAL " server {} --address :{} --quiet", m_DataDir.string(), m_Options.Port); CreateProcResult Result = CreateProc(MinioExe, CommandLine, Options); if (Result) { m_ProcessHandle.Initialize(Result); Stopwatch Timer; // Poll to check when the server is ready do { Sleep(100); HttpClient::Response Resp = m_HttpClient.Get("minio/health/live"); if (Resp) { ZEN_INFO("MinIO server started successfully (waited {})", NiceTimeSpanMs(Timer.GetElapsedTimeMs())); return; } } while (Timer.GetElapsedTimeMs() < 10000); } // Report failure ZEN_WARN("MinIO server failed to start within timeout period"); } void StopMinioServer() { if (!m_ProcessHandle.IsValid()) { return; } m_ProcessHandle.Kill(); // Clean up temp data directory std::error_code Ec; std::filesystem::remove_all(m_DataDir, Ec); if (Ec) { ZEN_WARN("MinIO: Failed to clean up data directory '{}': {}", m_DataDir.string(), Ec.message()); } } void CreateBucket(std::string_view BucketName) { if (m_DataDir.empty()) { ZEN_WARN("MinIO: Cannot create bucket before data directory is initialized — call SpawnMinioServer() first"); return; } std::filesystem::path BucketDir = m_DataDir / std::string(BucketName); std::error_code Ec; std::filesystem::create_directories(BucketDir, Ec); if (Ec) { ZEN_WARN("MinIO: Failed to create bucket directory '{}': {}", BucketDir.string(), Ec.message()); } } MinioProcessOptions m_Options; ProcessHandle m_ProcessHandle; HttpClient m_HttpClient; std::filesystem::path m_DataDir; }; MinioProcess::MinioProcess(const MinioProcessOptions& Options) : m_Impl(std::make_unique(Options)) { } MinioProcess::~MinioProcess() { m_Impl->StopMinioServer(); } void MinioProcess::SpawnMinioServer() { m_Impl->SpawnMinioServer(); } void MinioProcess::StopMinioServer() { m_Impl->StopMinioServer(); } void MinioProcess::CreateBucket(std::string_view BucketName) { m_Impl->CreateBucket(BucketName); } uint16_t MinioProcess::Port() const { return m_Impl->m_Options.Port; } std::string_view MinioProcess::RootUser() const { return m_Impl->m_Options.RootUser; } std::string_view MinioProcess::RootPassword() const { return m_Impl->m_Options.RootPassword; } std::string MinioProcess::Endpoint() const { return fmt::format("http://localhost:{}", m_Impl->m_Options.Port); } } // namespace zen