// Copyright Epic Games, Inc. All Rights Reserved. #include "hydration.h" #include #include namespace zen { /////////////////////////////////////////////////////////////////////////// struct FileHydrator : public HydrationStrategyBase { virtual void Configure(const HydrationConfig& Config) override; virtual void Hydrate() override; virtual void Dehydrate() override; private: HydrationConfig m_Config; std::filesystem::path m_StorageModuleRootDir; }; void FileHydrator::Configure(const HydrationConfig& Config) { m_Config = Config; std::filesystem::path ConfigPath(Utf8ToWide(m_Config.TargetSpecification)); if (!std::filesystem::exists(ConfigPath)) { throw std::invalid_argument(fmt::format("Target does not exist: '{}'", ConfigPath.string())); } m_StorageModuleRootDir = ConfigPath / m_Config.ModuleId; CreateDirectories(m_StorageModuleRootDir); } void FileHydrator::Hydrate() { ZEN_INFO("Hydrating state from '{}' to '{}'", m_StorageModuleRootDir, m_Config.ServerStateDir); // Ensure target is clean ZEN_DEBUG("Wiping server state at '{}'", m_Config.ServerStateDir); const bool ForceRemoveReadOnlyFiles = true; CleanDirectory(m_Config.ServerStateDir, ForceRemoveReadOnlyFiles); bool WipeServerState = false; try { ZEN_DEBUG("Copying '{}' to '{}'", m_StorageModuleRootDir, m_Config.ServerStateDir); CopyTree(m_StorageModuleRootDir, m_Config.ServerStateDir, {.EnableClone = true}); } catch (std::exception& Ex) { ZEN_WARN("Copy failed: {}. Will wipe any partially copied state from '{}'", Ex.what(), m_Config.ServerStateDir); // We don't do the clean right here to avoid potentially running into double-throws WipeServerState = true; } if (WipeServerState) { ZEN_DEBUG("Cleaning server state '{}'", m_Config.ServerStateDir); CleanDirectory(m_Config.ServerStateDir, ForceRemoveReadOnlyFiles); } // Note that we leave the storage state intact until next dehydration replaces the content } void FileHydrator::Dehydrate() { ZEN_INFO("Dehydrating state from '{}' to '{}'", m_Config.ServerStateDir, m_StorageModuleRootDir); const std::filesystem::path TargetDir = m_StorageModuleRootDir; // Ensure target is clean. This could be replaced with an atomic copy at a later date // (i.e copy into a temporary directory name and rename it once complete) ZEN_DEBUG("Cleaning storage root '{}'", TargetDir); const bool ForceRemoveReadOnlyFiles = true; CleanDirectory(TargetDir, ForceRemoveReadOnlyFiles); bool CopySuccess = true; try { ZEN_DEBUG("Copying '{}' to '{}'", m_Config.ServerStateDir, TargetDir); CopyTree(m_Config.ServerStateDir, TargetDir, {.EnableClone = true}); } catch (std::exception& Ex) { ZEN_WARN("Copy failed: {}. Will wipe any partially copied state from '{}'", Ex.what(), m_StorageModuleRootDir); // We don't do the clean right here to avoid potentially running into double-throws CopySuccess = false; } if (!CopySuccess) { ZEN_DEBUG("Removing partially copied state from '{}'", TargetDir); CleanDirectory(TargetDir, ForceRemoveReadOnlyFiles); } ZEN_DEBUG("Wiping server state '{}'", m_Config.ServerStateDir); CleanDirectory(m_Config.ServerStateDir, ForceRemoveReadOnlyFiles); } std::unique_ptr CreateFileHydrator() { return std::make_unique(); } } // namespace zen