diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/builds_cmd.cpp | 464 | ||||
| -rw-r--r-- | src/zenremotestore/builds/buildsavedstate.cpp | 443 | ||||
| -rw-r--r-- | src/zenremotestore/builds/buildstorageoperations.cpp | 216 | ||||
| -rw-r--r-- | src/zenremotestore/chunking/chunkedcontent.cpp | 44 | ||||
| -rw-r--r-- | src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h | 60 | ||||
| -rw-r--r-- | src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h | 2 |
6 files changed, 604 insertions, 625 deletions
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp index a911d1e44..76322013d 100644 --- a/src/zen/cmds/builds_cmd.cpp +++ b/src/zen/cmds/builds_cmd.cpp @@ -23,6 +23,7 @@ #include <zenhttp/httpclient.h> #include <zenhttp/httpclientauth.h> #include <zenhttp/httpcommon.h> +#include <zenremotestore/builds/buildsavedstate.h> #include <zenremotestore/builds/buildstoragecache.h> #include <zenremotestore/builds/buildstorageoperations.h> #include <zenremotestore/builds/filebuildstorage.h> @@ -475,353 +476,6 @@ namespace { return Count * 1000000 / ElapsedWallTimeUS; } - void CalculateLocalChunkOrders(const std::span<const uint32_t>& AbsoluteChunkOrders, - const std::span<const IoHash> LooseChunkHashes, - const std::span<const uint64_t> LooseChunkRawSizes, - const std::span<const ChunkBlockDescription>& BlockDescriptions, - std::vector<IoHash>& OutLocalChunkHashes, - std::vector<uint64_t>& OutLocalChunkRawSizes, - std::vector<uint32_t>& OutLocalChunkOrders) - { - ZEN_TRACE_CPU("CalculateLocalChunkOrders"); - - std::vector<IoHash> AbsoluteChunkHashes; - std::vector<uint64_t> AbsoluteChunkRawSizes; - AbsoluteChunkHashes.insert(AbsoluteChunkHashes.end(), LooseChunkHashes.begin(), LooseChunkHashes.end()); - AbsoluteChunkRawSizes.insert(AbsoluteChunkRawSizes.end(), LooseChunkRawSizes.begin(), LooseChunkRawSizes.end()); - for (const ChunkBlockDescription& Block : BlockDescriptions) - { - AbsoluteChunkHashes.insert(AbsoluteChunkHashes.end(), Block.ChunkRawHashes.begin(), Block.ChunkRawHashes.end()); - AbsoluteChunkRawSizes.insert(AbsoluteChunkRawSizes.end(), Block.ChunkRawLengths.begin(), Block.ChunkRawLengths.end()); - } - OutLocalChunkHashes.reserve(AbsoluteChunkHashes.size()); - OutLocalChunkRawSizes.reserve(AbsoluteChunkRawSizes.size()); - OutLocalChunkOrders.reserve(AbsoluteChunkOrders.size()); - - tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> ChunkHashToChunkIndex; - ChunkHashToChunkIndex.reserve(AbsoluteChunkHashes.size()); - - for (uint32_t AbsoluteChunkOrderIndex = 0; AbsoluteChunkOrderIndex < AbsoluteChunkOrders.size(); AbsoluteChunkOrderIndex++) - { - const uint32_t AbsoluteChunkIndex = AbsoluteChunkOrders[AbsoluteChunkOrderIndex]; - const IoHash& AbsoluteChunkHash = AbsoluteChunkHashes[AbsoluteChunkIndex]; - const uint64_t AbsoluteChunkRawSize = AbsoluteChunkRawSizes[AbsoluteChunkIndex]; - - if (auto It = ChunkHashToChunkIndex.find(AbsoluteChunkHash); It != ChunkHashToChunkIndex.end()) - { - const uint32_t LocalChunkIndex = It->second; - OutLocalChunkOrders.push_back(LocalChunkIndex); - } - else - { - uint32_t LocalChunkIndex = gsl::narrow<uint32_t>(OutLocalChunkHashes.size()); - OutLocalChunkHashes.push_back(AbsoluteChunkHash); - OutLocalChunkRawSizes.push_back(AbsoluteChunkRawSize); - OutLocalChunkOrders.push_back(LocalChunkIndex); - ChunkHashToChunkIndex.insert_or_assign(AbsoluteChunkHash, LocalChunkIndex); - } -#if EXTRA_VERIFY - const uint32_t LocalChunkIndex = OutLocalChunkOrders[AbsoluteChunkOrderIndex]; - const IoHash& LocalChunkHash = OutLocalChunkHashes[LocalChunkIndex]; - const uint64_t& LocalChunkRawSize = OutLocalChunkRawSizes[LocalChunkIndex]; - ZEN_ASSERT(LocalChunkHash == AbsoluteChunkHash); - ZEN_ASSERT(LocalChunkRawSize == AbsoluteChunkRawSize); -#endif // EXTRA_VERIFY - } -#if EXTRA_VERIFY - for (uint32_t OrderIndex = 0; OrderIndex < OutLocalChunkOrders.size(); OrderIndex++) - { - uint32_t LocalChunkIndex = OutLocalChunkOrders[OrderIndex]; - const IoHash LocalChunkHash = OutLocalChunkHashes[LocalChunkIndex]; - uint64_t LocalChunkRawSize = OutLocalChunkRawSizes[LocalChunkIndex]; - - uint32_t VerifyChunkIndex = AbsoluteChunkOrders[OrderIndex]; - const IoHash VerifyChunkHash = AbsoluteChunkHashes[VerifyChunkIndex]; - uint64_t VerifyChunkRawSize = AbsoluteChunkRawSizes[VerifyChunkIndex]; - - ZEN_ASSERT(LocalChunkHash == VerifyChunkHash); - ZEN_ASSERT(LocalChunkRawSize == VerifyChunkRawSize); - } -#endif // EXTRA_VERIFY - } - - void ReadBuildContentFromCompactBinary(CbObjectView BuildPartManifest, - SourcePlatform& OutPlatform, - std::vector<std::filesystem::path>& OutPaths, - std::vector<IoHash>& OutRawHashes, - std::vector<uint64_t>& OutRawSizes, - std::vector<uint32_t>& OutAttributes, - std::vector<IoHash>& OutSequenceRawHashes, - std::vector<uint32_t>& OutChunkCounts, - std::vector<uint32_t>& OutAbsoluteChunkOrders, - std::vector<IoHash>& OutLooseChunkHashes, - std::vector<uint64_t>& OutLooseChunkRawSizes, - std::vector<IoHash>& OutBlockRawHashes) - { - OutPlatform = FromString(BuildPartManifest["platform"sv].AsString(), SourcePlatform::_Count); - - CbObjectView FilesObject = BuildPartManifest["files"sv].AsObjectView(); - - compactbinary_helpers::ReadArray("paths"sv, FilesObject, OutPaths); - compactbinary_helpers::ReadArray("rawhashes"sv, FilesObject, OutRawHashes); - compactbinary_helpers::ReadArray("rawsizes"sv, FilesObject, OutRawSizes); - - uint64_t PathCount = OutPaths.size(); - if (OutRawHashes.size() != PathCount) - { - throw std::runtime_error(fmt::format("Number of raw hashes entries does not match number of paths")); - } - if (OutRawSizes.size() != PathCount) - { - throw std::runtime_error(fmt::format("Number of raw sizes entries does not match number of paths")); - } - - std::vector<uint32_t> ModeArray; - compactbinary_helpers::ReadArray("mode"sv, FilesObject, ModeArray); - if (ModeArray.size() != PathCount && ModeArray.size() != 0) - { - throw std::runtime_error(fmt::format("Number of attribute entries does not match number of paths")); - } - - std::vector<uint32_t> AttributeArray; - compactbinary_helpers::ReadArray("attributes"sv, FilesObject, ModeArray); - if (AttributeArray.size() != PathCount && AttributeArray.size() != 0) - { - throw std::runtime_error(fmt::format("Number of attribute entries does not match number of paths")); - } - - if (ModeArray.size() > 0) - { - if (OutPlatform == SourcePlatform::_Count) - { - OutPlatform = SourcePlatform::Linux; // Best guess - under dev format - } - OutAttributes = std::move(ModeArray); - } - else if (AttributeArray.size() > 0) - { - if (OutPlatform == SourcePlatform::_Count) - { - OutPlatform = SourcePlatform::Windows; - } - OutAttributes = std::move(AttributeArray); - } - else - { - if (OutPlatform == SourcePlatform::_Count) - { - OutPlatform = GetSourceCurrentPlatform(); - } - } - - if (CbObjectView ChunkContentView = BuildPartManifest["chunkedContent"sv].AsObjectView(); ChunkContentView) - { - compactbinary_helpers::ReadArray("sequenceRawHashes"sv, ChunkContentView, OutSequenceRawHashes); - compactbinary_helpers::ReadArray("chunkcounts"sv, ChunkContentView, OutChunkCounts); - if (OutChunkCounts.size() != OutSequenceRawHashes.size()) - { - throw std::runtime_error(fmt::format("Number of chunk count entries does not match number of paths")); - } - compactbinary_helpers::ReadArray("chunkorders"sv, ChunkContentView, OutAbsoluteChunkOrders); - } - else if (FilesObject["chunkcounts"sv]) - { - // Legacy zen style - - std::vector<uint32_t> LegacyChunkCounts; - compactbinary_helpers::ReadArray("chunkcounts"sv, FilesObject, LegacyChunkCounts); - if (LegacyChunkCounts.size() != PathCount) - { - throw std::runtime_error(fmt::format("Number of chunk count entries does not match number of paths")); - } - std::vector<uint32_t> LegacyAbsoluteChunkOrders; - compactbinary_helpers::ReadArray("chunkorders"sv, FilesObject, LegacyAbsoluteChunkOrders); - - CbArrayView ChunkOrdersArray = BuildPartManifest["chunkorders"sv].AsArrayView(); - const uint64_t ChunkOrdersCount = ChunkOrdersArray.Num(); - - tsl::robin_set<IoHash, IoHash::Hasher> FoundRawHashes; - FoundRawHashes.reserve(PathCount); - - OutChunkCounts.reserve(PathCount); - OutAbsoluteChunkOrders.reserve(ChunkOrdersCount); - - uint32_t OrderIndexOffset = 0; - for (uint32_t PathIndex = 0; PathIndex < OutPaths.size(); PathIndex++) - { - const IoHash& PathRawHash = OutRawHashes[PathIndex]; - uint32_t LegacyChunkCount = LegacyChunkCounts[PathIndex]; - - if (FoundRawHashes.insert(PathRawHash).second) - { - OutSequenceRawHashes.push_back(PathRawHash); - OutChunkCounts.push_back(LegacyChunkCount); - std::span<uint32_t> AbsoluteChunkOrder = - std::span<uint32_t>(LegacyAbsoluteChunkOrders).subspan(OrderIndexOffset, LegacyChunkCount); - OutAbsoluteChunkOrders.insert(OutAbsoluteChunkOrders.end(), AbsoluteChunkOrder.begin(), AbsoluteChunkOrder.end()); - } - OrderIndexOffset += LegacyChunkCounts[PathIndex]; - } - } - else - { - // Legacy C# style - - tsl::robin_set<IoHash, IoHash::Hasher> FoundRawHashes; - FoundRawHashes.reserve(PathCount); - uint32_t OrderIndexOffset = 0; - for (uint32_t PathIndex = 0; PathIndex < OutPaths.size(); PathIndex++) - { - if (OutRawSizes[PathIndex] > 0) - { - const IoHash& PathRawHash = OutRawHashes[PathIndex]; - if (FoundRawHashes.insert(PathRawHash).second) - { - OutSequenceRawHashes.push_back(PathRawHash); - OutChunkCounts.push_back(1); - OutAbsoluteChunkOrders.push_back(OrderIndexOffset); - OutLooseChunkHashes.push_back(PathRawHash); - OutLooseChunkRawSizes.push_back(OutRawSizes[PathIndex]); - OrderIndexOffset += 1; - } - } - } - } - - CbObjectView ChunkAttachmentsView = BuildPartManifest["chunkAttachments"sv].AsObjectView(); - { - compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, ChunkAttachmentsView, OutLooseChunkHashes); - compactbinary_helpers::ReadArray("chunkRawSizes"sv, ChunkAttachmentsView, OutLooseChunkRawSizes); - if (OutLooseChunkHashes.size() != OutLooseChunkRawSizes.size()) - { - throw std::runtime_error( - fmt::format("Number of attachment chunk hashes does not match number of attachemnt chunk raw sizes")); - } - } - - CbObjectView BlocksView = BuildPartManifest["blockAttachments"sv].AsObjectView(); - { - compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, BlocksView, OutBlockRawHashes); - } - } - - bool ReadStateObject(CbObjectView StateView, - Oid& OutBuildId, - std::vector<Oid>& BuildPartsIds, - std::vector<std::string>& BuildPartsNames, - std::vector<ChunkedFolderContent>& OutPartContents, - FolderContent& OutLocalFolderState) - { - try - { - CbObjectView BuildView = StateView["builds"sv].AsArrayView().CreateViewIterator().AsObjectView(); - OutBuildId = BuildView["buildId"sv].AsObjectId(); - for (CbFieldView PartView : BuildView["parts"sv].AsArrayView()) - { - CbObjectView PartObjectView = PartView.AsObjectView(); - BuildPartsIds.push_back(PartObjectView["partId"sv].AsObjectId()); - BuildPartsNames.push_back(std::string(PartObjectView["partName"sv].AsString())); - OutPartContents.push_back(LoadChunkedFolderContentToCompactBinary(PartObjectView["content"sv].AsObjectView())); - } - OutLocalFolderState = LoadFolderContentToCompactBinary(StateView["localFolderState"sv].AsObjectView()); - return true; - } - catch (const std::exception& Ex) - { - ZEN_CONSOLE_WARN("Unable to read local state: ", Ex.what()); - return false; - } - } - - CbObject CreateStateObject(const Oid& BuildId, - const std::vector<std::pair<Oid, std::string>>& AllBuildParts, - std::span<const ChunkedFolderContent> PartContents, - const FolderContent& LocalFolderState, - const std::filesystem::path& LocalPath) - { - CbObjectWriter CurrentStateWriter; - CurrentStateWriter.AddString("path", (const char*)LocalPath.u8string().c_str()); - CurrentStateWriter.BeginArray("builds"sv); - { - CurrentStateWriter.BeginObject(); - { - CurrentStateWriter.AddObjectId("buildId"sv, BuildId); - CurrentStateWriter.BeginArray("parts"sv); - for (size_t PartIndex = 0; PartIndex < AllBuildParts.size(); PartIndex++) - { - const Oid BuildPartId = AllBuildParts[PartIndex].first; - CurrentStateWriter.BeginObject(); - { - CurrentStateWriter.AddObjectId("partId"sv, BuildPartId); - CurrentStateWriter.AddString("partName"sv, AllBuildParts[PartIndex].second); - CurrentStateWriter.BeginObject("content"); - { - SaveChunkedFolderContentToCompactBinary(PartContents[PartIndex], CurrentStateWriter); - } - CurrentStateWriter.EndObject(); - } - CurrentStateWriter.EndObject(); - } - CurrentStateWriter.EndArray(); // parts - } - CurrentStateWriter.EndObject(); - } - CurrentStateWriter.EndArray(); // builds - - CurrentStateWriter.BeginObject("localFolderState"sv); - { - SaveFolderContentToCompactBinary(LocalFolderState, CurrentStateWriter); - } - CurrentStateWriter.EndObject(); // localFolderState - - return CurrentStateWriter.Save(); - } - - void AddDownloadedPath(const std::filesystem::path& SystemRootDir, - const Oid& BuildId, - const std::vector<std::pair<Oid, std::string>>& BuildParts, - const std::filesystem::path& StateFilePath, - const std::filesystem::path& LocalPath) - { - ZEN_ASSERT(!SystemRootDir.empty()); - ZEN_ASSERT(!StateFilePath.empty()); - ZEN_ASSERT(!LocalPath.empty()); - const std::u8string LocalPathString = LocalPath.generic_u8string(); - IoHash PathHash = IoHash::HashBuffer(LocalPathString.data(), LocalPathString.length()); - std::filesystem::path WritePath = SystemRootDir / "builds" / "downloads" / (PathHash.ToHexString() + ".json"); - CreateDirectories(WritePath.parent_path()); - CbObjectWriter Writer; - Writer.AddString("path", (const char*)LocalPath.u8string().c_str()); - Writer.AddString("statePath", (const char*)StateFilePath.u8string().c_str()); - Writer.AddDateTime("date", DateTime::Now()); - Writer.BeginArray("builds"sv); - { - Writer.BeginObject(); - { - Writer.AddObjectId("buildId", BuildId); - Writer.BeginArray("parts"); - for (const auto& It : BuildParts) - { - Writer.BeginObject(); - { - Writer.AddObjectId("partId", It.first); - Writer.AddString("partName", It.second); - } - Writer.EndObject(); - } - Writer.EndArray(); // parts - } - Writer.EndObject(); - } - Writer.EndArray(); // builds - - CbObject Payload = Writer.Save(); - ExtendableStringBuilder<512> SB; - CompactBinaryToJson(Payload.GetView(), SB); - MemoryView JsonPayload(SB.Data(), SB.Size()); - TemporaryFile::SafeWriteFile(WritePath, JsonPayload); - } - void ValidateBuildPart(BuildStorage& Storage, const Oid& BuildId, Oid BuildPartId, const std::string_view BuildPartName) { ZEN_TRACE_CPU("ValidateBuildPart"); @@ -1294,53 +948,6 @@ namespace { } } - // TODO: Move out of this file and make it one with implementation in buildstorageoperations.cpp - bool ReadStateFile(const std::filesystem::path& StateFilePath, - FolderContent& OutLocalFolderState, - ChunkedFolderContent& OutLocalContent) - { - ZEN_TRACE_CPU("ReadStateFile"); - bool HasLocalState = false; - try - { - CbObject CurrentStateObject = LoadCompactBinaryObject(StateFilePath).Object; - if (CurrentStateObject) - { - Oid CurrentBuildId; - std::vector<Oid> SavedBuildPartIds; - std::vector<std::string> SavedBuildPartsNames; - std::vector<ChunkedFolderContent> SavedPartContents; - if (ReadStateObject(CurrentStateObject, - CurrentBuildId, - SavedBuildPartIds, - SavedBuildPartsNames, - SavedPartContents, - OutLocalFolderState)) - { - if (!SavedPartContents.empty()) - { - if (SavedPartContents.size() == 1) - { - OutLocalContent = std::move(SavedPartContents[0]); - } - else - { - OutLocalContent = - MergeChunkedFolderContents(SavedPartContents[0], - std::span<const ChunkedFolderContent>(SavedPartContents).subspan(1)); - } - HasLocalState = true; - } - } - } - } - catch (const std::exception& Ex) - { - ZEN_CONSOLE_WARN("Failed reading state file {}, falling back to scannning. Reason: {}", StateFilePath, Ex.what()); - } - return HasLocalState; - } - FolderContent GetValidFolderContent(GetFolderContentStatistics& LocalFolderScanStats, const std::filesystem::path& Path, std::span<const std::filesystem::path> PathsToCheck, @@ -2004,12 +1611,26 @@ namespace { FolderContent LocalFolderState; ChunkedFolderContent LocalContent; - Stopwatch ReadStateTimer; - const bool HasLocalState = IsFile(StateFilePath) && ReadStateFile(StateFilePath, LocalFolderState, LocalContent); - if (HasLocalState && !IsQuiet) + Stopwatch ReadStateTimer; + bool HasLocalState = false; + bool FileExists = IsFile(StateFilePath); + if (FileExists) { - ZEN_CONSOLE("Read local state file {} in {}", StateFilePath, NiceTimeSpanMs(ReadStateTimer.GetElapsedTimeMs())); + try + { + ReadStateFile(StateFilePath, LocalFolderState, LocalContent); + if (IsQuiet) + { + ZEN_CONSOLE("Read local state file {} in {}", StateFilePath, NiceTimeSpanMs(ReadStateTimer.GetElapsedTimeMs())); + } + HasLocalState = true; + } + catch (const std::exception& Ex) + { + ZEN_CONSOLE_WARN("Failed reading state file {}, falling back to scannning. Reason: {}", StateFilePath, Ex.what()); + } } + { const uint32_t LocalPathCount = gsl::narrow<uint32_t>(LocalFolderState.Paths.size()); const uint32_t RemotePathCount = gsl::narrow<uint32_t>(ReferencePaths.size()); @@ -2446,49 +2067,8 @@ namespace { { return; } - auto CompareContent = [](const ChunkedFolderContent& Lhs, const ChunkedFolderContent& Rhs) { - tsl::robin_map<std::string, size_t> RhsPathToIndex; - const size_t RhsPathCount = Rhs.Paths.size(); - RhsPathToIndex.reserve(RhsPathCount); - for (size_t RhsPathIndex = 0; RhsPathIndex < RhsPathCount; RhsPathIndex++) - { - RhsPathToIndex.insert({Rhs.Paths[RhsPathIndex].generic_string(), RhsPathIndex}); - } - const size_t LhsPathCount = Lhs.Paths.size(); - for (size_t LhsPathIndex = 0; LhsPathIndex < LhsPathCount; LhsPathIndex++) - { - if (auto It = RhsPathToIndex.find(Lhs.Paths[LhsPathIndex].generic_string()); It != RhsPathToIndex.end()) - { - const size_t RhsPathIndex = It->second; - if ((Lhs.RawHashes[LhsPathIndex] != Rhs.RawHashes[RhsPathIndex]) || - (!FolderContent::AreFileAttributesEqual(Lhs.Attributes[LhsPathIndex], Rhs.Attributes[RhsPathIndex]))) - { - return false; - } - } - else - { - return false; - } - } - tsl::robin_set<std::string> LhsPathExists; - LhsPathExists.reserve(LhsPathCount); - for (size_t LhsPathIndex = 0; LhsPathIndex < LhsPathCount; LhsPathIndex++) - { - LhsPathExists.insert({Lhs.Paths[LhsPathIndex].generic_string()}); - } - for (size_t RhsPathIndex = 0; RhsPathIndex < RhsPathCount; RhsPathIndex++) - { - if (!LhsPathExists.contains(Rhs.Paths[RhsPathIndex].generic_string())) - { - return false; - } - } - return true; - }; - - if (Options.EnableTargetFolderScavenging && !Options.CleanTargetFolder && CompareContent(RemoteContent, LocalContent)) + if (Options.EnableTargetFolderScavenging && !Options.CleanTargetFolder && CompareChunkedContent(RemoteContent, LocalContent)) { if (!IsQuiet) { @@ -2566,6 +2146,8 @@ namespace { { if (!Options.PrimeCacheOnly) { + AddDownloadedPath(Options.SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(Options.ZenFolderPath), Path); + ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Verify, TaskSteps::StepCount); VerifyFolder(RemoteContent, Path, Options.PostDownloadVerify, VerifyFolderStats); @@ -2580,8 +2162,6 @@ namespace { ZEN_CONSOLE("Wrote local state in {}", NiceTimeSpanMs(WriteStateTimer.GetElapsedTimeMs())); } - AddDownloadedPath(Options.SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(Options.ZenFolderPath), Path); - #if 0 ExtendableStringBuilder<1024> SB; CompactBinaryToJson(StateObject, SB); diff --git a/src/zenremotestore/builds/buildsavedstate.cpp b/src/zenremotestore/builds/buildsavedstate.cpp new file mode 100644 index 000000000..933616856 --- /dev/null +++ b/src/zenremotestore/builds/buildsavedstate.cpp @@ -0,0 +1,443 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zenremotestore/builds/buildsavedstate.h> + +#include <zencore/basicfile.h> +#include <zencore/compactbinaryutil.h> +#include <zencore/compactbinaryvalidation.h> +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> +#include <zencore/trace.h> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <tsl/robin_set.h> +ZEN_THIRD_PARTY_INCLUDES_END + +#define EXTRA_VERIFY 0 + +namespace zen { + +using namespace std::literals; + +namespace { + std::filesystem::path ZenStateDownloadFolder(const std::filesystem::path& SystemRootDir) + { + return SystemRootDir / "builds" / "downloads"; + } +} // namespace + +CbObject +CreateStateObject(const Oid& BuildId, + const std::vector<std::pair<Oid, std::string>>& AllBuildParts, + std::span<const ChunkedFolderContent> PartContents, + const FolderContent& LocalFolderState, + const std::filesystem::path& LocalPath) +{ + CbObjectWriter CurrentStateWriter; + CurrentStateWriter.AddString("path", (const char*)LocalPath.u8string().c_str()); + CurrentStateWriter.BeginArray("builds"sv); + { + CurrentStateWriter.BeginObject(); + { + CurrentStateWriter.AddObjectId("buildId"sv, BuildId); + CurrentStateWriter.BeginArray("parts"sv); + for (size_t PartIndex = 0; PartIndex < AllBuildParts.size(); PartIndex++) + { + const Oid BuildPartId = AllBuildParts[PartIndex].first; + CurrentStateWriter.BeginObject(); + { + CurrentStateWriter.AddObjectId("partId"sv, BuildPartId); + CurrentStateWriter.AddString("partName"sv, AllBuildParts[PartIndex].second); + CurrentStateWriter.BeginObject("content"); + { + SaveChunkedFolderContentToCompactBinary(PartContents[PartIndex], CurrentStateWriter); + } + CurrentStateWriter.EndObject(); + } + CurrentStateWriter.EndObject(); + } + CurrentStateWriter.EndArray(); // parts + } + CurrentStateWriter.EndObject(); + } + CurrentStateWriter.EndArray(); // builds + + CurrentStateWriter.BeginObject("localFolderState"sv); + { + SaveFolderContentToCompactBinary(LocalFolderState, CurrentStateWriter); + } + CurrentStateWriter.EndObject(); // localFolderState + + return CurrentStateWriter.Save(); +} + +void +ReadBuildContentFromCompactBinary(CbObjectView BuildPartManifest, + SourcePlatform& OutPlatform, + std::vector<std::filesystem::path>& OutPaths, + std::vector<IoHash>& OutRawHashes, + std::vector<uint64_t>& OutRawSizes, + std::vector<uint32_t>& OutAttributes, + std::vector<IoHash>& OutSequenceRawHashes, + std::vector<uint32_t>& OutChunkCounts, + std::vector<uint32_t>& OutAbsoluteChunkOrders, + std::vector<IoHash>& OutLooseChunkHashes, + std::vector<uint64_t>& OutLooseChunkRawSizes, + std::vector<IoHash>& OutBlockRawHashes) +{ + OutPlatform = FromString(BuildPartManifest["platform"sv].AsString(), SourcePlatform::_Count); + + CbObjectView FilesObject = BuildPartManifest["files"sv].AsObjectView(); + + compactbinary_helpers::ReadArray("paths"sv, FilesObject, OutPaths); + compactbinary_helpers::ReadArray("rawhashes"sv, FilesObject, OutRawHashes); + compactbinary_helpers::ReadArray("rawsizes"sv, FilesObject, OutRawSizes); + + uint64_t PathCount = OutPaths.size(); + if (OutRawHashes.size() != PathCount) + { + throw std::runtime_error(fmt::format("Number of raw hashes entries does not match number of paths")); + } + if (OutRawSizes.size() != PathCount) + { + throw std::runtime_error(fmt::format("Number of raw sizes entries does not match number of paths")); + } + + std::vector<uint32_t> ModeArray; + compactbinary_helpers::ReadArray("mode"sv, FilesObject, ModeArray); + if (ModeArray.size() != PathCount && ModeArray.size() != 0) + { + throw std::runtime_error(fmt::format("Number of attribute entries does not match number of paths")); + } + + std::vector<uint32_t> AttributeArray; + compactbinary_helpers::ReadArray("attributes"sv, FilesObject, ModeArray); + if (AttributeArray.size() != PathCount && AttributeArray.size() != 0) + { + throw std::runtime_error(fmt::format("Number of attribute entries does not match number of paths")); + } + + if (ModeArray.size() > 0) + { + if (OutPlatform == SourcePlatform::_Count) + { + OutPlatform = SourcePlatform::Linux; // Best guess - under dev format + } + OutAttributes = std::move(ModeArray); + } + else if (AttributeArray.size() > 0) + { + if (OutPlatform == SourcePlatform::_Count) + { + OutPlatform = SourcePlatform::Windows; + } + OutAttributes = std::move(AttributeArray); + } + else + { + if (OutPlatform == SourcePlatform::_Count) + { + OutPlatform = GetSourceCurrentPlatform(); + } + } + + if (CbObjectView ChunkContentView = BuildPartManifest["chunkedContent"sv].AsObjectView(); ChunkContentView) + { + compactbinary_helpers::ReadArray("sequenceRawHashes"sv, ChunkContentView, OutSequenceRawHashes); + compactbinary_helpers::ReadArray("chunkcounts"sv, ChunkContentView, OutChunkCounts); + if (OutChunkCounts.size() != OutSequenceRawHashes.size()) + { + throw std::runtime_error(fmt::format("Number of chunk count entries does not match number of paths")); + } + compactbinary_helpers::ReadArray("chunkorders"sv, ChunkContentView, OutAbsoluteChunkOrders); + } + else if (FilesObject["chunkcounts"sv]) + { + // Legacy zen style + + std::vector<uint32_t> LegacyChunkCounts; + compactbinary_helpers::ReadArray("chunkcounts"sv, FilesObject, LegacyChunkCounts); + if (LegacyChunkCounts.size() != PathCount) + { + throw std::runtime_error(fmt::format("Number of chunk count entries does not match number of paths")); + } + std::vector<uint32_t> LegacyAbsoluteChunkOrders; + compactbinary_helpers::ReadArray("chunkorders"sv, FilesObject, LegacyAbsoluteChunkOrders); + + CbArrayView ChunkOrdersArray = BuildPartManifest["chunkorders"sv].AsArrayView(); + const uint64_t ChunkOrdersCount = ChunkOrdersArray.Num(); + + tsl::robin_set<IoHash, IoHash::Hasher> FoundRawHashes; + FoundRawHashes.reserve(PathCount); + + OutChunkCounts.reserve(PathCount); + OutAbsoluteChunkOrders.reserve(ChunkOrdersCount); + + uint32_t OrderIndexOffset = 0; + for (uint32_t PathIndex = 0; PathIndex < OutPaths.size(); PathIndex++) + { + const IoHash& PathRawHash = OutRawHashes[PathIndex]; + uint32_t LegacyChunkCount = LegacyChunkCounts[PathIndex]; + + if (FoundRawHashes.insert(PathRawHash).second) + { + OutSequenceRawHashes.push_back(PathRawHash); + OutChunkCounts.push_back(LegacyChunkCount); + std::span<uint32_t> AbsoluteChunkOrder = + std::span<uint32_t>(LegacyAbsoluteChunkOrders).subspan(OrderIndexOffset, LegacyChunkCount); + OutAbsoluteChunkOrders.insert(OutAbsoluteChunkOrders.end(), AbsoluteChunkOrder.begin(), AbsoluteChunkOrder.end()); + } + OrderIndexOffset += LegacyChunkCounts[PathIndex]; + } + } + else + { + // Legacy C# style + + tsl::robin_set<IoHash, IoHash::Hasher> FoundRawHashes; + FoundRawHashes.reserve(PathCount); + uint32_t OrderIndexOffset = 0; + for (uint32_t PathIndex = 0; PathIndex < OutPaths.size(); PathIndex++) + { + if (OutRawSizes[PathIndex] > 0) + { + const IoHash& PathRawHash = OutRawHashes[PathIndex]; + if (FoundRawHashes.insert(PathRawHash).second) + { + OutSequenceRawHashes.push_back(PathRawHash); + OutChunkCounts.push_back(1); + OutAbsoluteChunkOrders.push_back(OrderIndexOffset); + OutLooseChunkHashes.push_back(PathRawHash); + OutLooseChunkRawSizes.push_back(OutRawSizes[PathIndex]); + OrderIndexOffset += 1; + } + } + } + } + + CbObjectView ChunkAttachmentsView = BuildPartManifest["chunkAttachments"sv].AsObjectView(); + { + compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, ChunkAttachmentsView, OutLooseChunkHashes); + compactbinary_helpers::ReadArray("chunkRawSizes"sv, ChunkAttachmentsView, OutLooseChunkRawSizes); + if (OutLooseChunkHashes.size() != OutLooseChunkRawSizes.size()) + { + throw std::runtime_error(fmt::format("Number of attachment chunk hashes does not match number of attachemnt chunk raw sizes")); + } + } + + CbObjectView BlocksView = BuildPartManifest["blockAttachments"sv].AsObjectView(); + { + compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, BlocksView, OutBlockRawHashes); + } +} + +void +CalculateLocalChunkOrders(const std::span<const uint32_t>& AbsoluteChunkOrders, + const std::span<const IoHash> LooseChunkHashes, + const std::span<const uint64_t> LooseChunkRawSizes, + const std::span<const ChunkBlockDescription>& BlockDescriptions, + std::vector<IoHash>& OutLocalChunkHashes, + std::vector<uint64_t>& OutLocalChunkRawSizes, + std::vector<uint32_t>& OutLocalChunkOrders) +{ + ZEN_TRACE_CPU("CalculateLocalChunkOrders"); + + std::vector<IoHash> AbsoluteChunkHashes; + std::vector<uint64_t> AbsoluteChunkRawSizes; + AbsoluteChunkHashes.insert(AbsoluteChunkHashes.end(), LooseChunkHashes.begin(), LooseChunkHashes.end()); + AbsoluteChunkRawSizes.insert(AbsoluteChunkRawSizes.end(), LooseChunkRawSizes.begin(), LooseChunkRawSizes.end()); + for (const ChunkBlockDescription& Block : BlockDescriptions) + { + AbsoluteChunkHashes.insert(AbsoluteChunkHashes.end(), Block.ChunkRawHashes.begin(), Block.ChunkRawHashes.end()); + AbsoluteChunkRawSizes.insert(AbsoluteChunkRawSizes.end(), Block.ChunkRawLengths.begin(), Block.ChunkRawLengths.end()); + } + OutLocalChunkHashes.reserve(AbsoluteChunkHashes.size()); + OutLocalChunkRawSizes.reserve(AbsoluteChunkRawSizes.size()); + OutLocalChunkOrders.reserve(AbsoluteChunkOrders.size()); + + tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> ChunkHashToChunkIndex; + ChunkHashToChunkIndex.reserve(AbsoluteChunkHashes.size()); + + for (uint32_t AbsoluteChunkOrderIndex = 0; AbsoluteChunkOrderIndex < AbsoluteChunkOrders.size(); AbsoluteChunkOrderIndex++) + { + const uint32_t AbsoluteChunkIndex = AbsoluteChunkOrders[AbsoluteChunkOrderIndex]; + const IoHash& AbsoluteChunkHash = AbsoluteChunkHashes[AbsoluteChunkIndex]; + const uint64_t AbsoluteChunkRawSize = AbsoluteChunkRawSizes[AbsoluteChunkIndex]; + + if (auto It = ChunkHashToChunkIndex.find(AbsoluteChunkHash); It != ChunkHashToChunkIndex.end()) + { + const uint32_t LocalChunkIndex = It->second; + OutLocalChunkOrders.push_back(LocalChunkIndex); + } + else + { + uint32_t LocalChunkIndex = gsl::narrow<uint32_t>(OutLocalChunkHashes.size()); + OutLocalChunkHashes.push_back(AbsoluteChunkHash); + OutLocalChunkRawSizes.push_back(AbsoluteChunkRawSize); + OutLocalChunkOrders.push_back(LocalChunkIndex); + ChunkHashToChunkIndex.insert_or_assign(AbsoluteChunkHash, LocalChunkIndex); + } +#if EXTRA_VERIFY + const uint32_t LocalChunkIndex = OutLocalChunkOrders[AbsoluteChunkOrderIndex]; + const IoHash& LocalChunkHash = OutLocalChunkHashes[LocalChunkIndex]; + const uint64_t& LocalChunkRawSize = OutLocalChunkRawSizes[LocalChunkIndex]; + ZEN_ASSERT(LocalChunkHash == AbsoluteChunkHash); + ZEN_ASSERT(LocalChunkRawSize == AbsoluteChunkRawSize); +#endif // EXTRA_VERIFY + } +#if EXTRA_VERIFY + for (uint32_t OrderIndex = 0; OrderIndex < OutLocalChunkOrders.size(); OrderIndex++) + { + uint32_t LocalChunkIndex = OutLocalChunkOrders[OrderIndex]; + const IoHash LocalChunkHash = OutLocalChunkHashes[LocalChunkIndex]; + uint64_t LocalChunkRawSize = OutLocalChunkRawSizes[LocalChunkIndex]; + + uint32_t VerifyChunkIndex = AbsoluteChunkOrders[OrderIndex]; + const IoHash VerifyChunkHash = AbsoluteChunkHashes[VerifyChunkIndex]; + uint64_t VerifyChunkRawSize = AbsoluteChunkRawSizes[VerifyChunkIndex]; + + ZEN_ASSERT(LocalChunkHash == VerifyChunkHash); + ZEN_ASSERT(LocalChunkRawSize == VerifyChunkRawSize); + } +#endif // EXTRA_VERIFY +} + +void +ReadStateObject(CbObjectView StateView, + Oid& OutBuildId, + std::vector<Oid>& BuildPartsIds, + std::vector<std::string>& BuildPartsNames, + std::vector<ChunkedFolderContent>& OutPartContents, + FolderContent& OutLocalFolderState) +{ + CbObjectView BuildView = StateView["builds"sv].AsArrayView().CreateViewIterator().AsObjectView(); + OutBuildId = BuildView["buildId"sv].AsObjectId(); + for (CbFieldView PartView : BuildView["parts"sv].AsArrayView()) + { + CbObjectView PartObjectView = PartView.AsObjectView(); + BuildPartsIds.push_back(PartObjectView["partId"sv].AsObjectId()); + BuildPartsNames.push_back(std::string(PartObjectView["partName"sv].AsString())); + OutPartContents.push_back(LoadChunkedFolderContentToCompactBinary(PartObjectView["content"sv].AsObjectView())); + } + OutLocalFolderState = LoadFolderContentToCompactBinary(StateView["localFolderState"sv].AsObjectView()); +} +void +ReadStateFile(const std::filesystem::path& StateFilePath, FolderContent& OutLocalFolderState, ChunkedFolderContent& OutLocalContent) +{ + ZEN_TRACE_CPU("ReadStateFile"); + + FileContents FileData = ReadFile(StateFilePath); + CbValidateError ValidateError; + if (CbObject CurrentStateObject = ValidateAndReadCompactBinaryObject(FileData.Flatten(), ValidateError); + ValidateError == CbValidateError::None) + { + if (CurrentStateObject) + { + Oid CurrentBuildId; + std::vector<Oid> SavedBuildPartIds; + std::vector<std::string> SavedBuildPartsNames; + std::vector<ChunkedFolderContent> SavedPartContents; + ReadStateObject(CurrentStateObject, + CurrentBuildId, + SavedBuildPartIds, + SavedBuildPartsNames, + SavedPartContents, + OutLocalFolderState); + if (!SavedPartContents.empty()) + { + if (SavedPartContents.size() == 1) + { + OutLocalContent = std::move(SavedPartContents[0]); + } + else + { + OutLocalContent = MergeChunkedFolderContents(SavedPartContents[0], + std::span<const ChunkedFolderContent>(SavedPartContents).subspan(1)); + } + } + } + } + else + { + throw std::runtime_error( + fmt::format("Failed to read compact binary object from '{}', reason: {}", StateFilePath, ToString(ValidateError))); + } +} + +void +AddDownloadedPath(const std::filesystem::path& SystemRootDir, + const Oid& BuildId, + const std::vector<std::pair<Oid, std::string>>& BuildParts, + const std::filesystem::path& StateFilePath, + const std::filesystem::path& LocalPath) +{ + ZEN_TRACE_CPU("AddDownloadedPath"); + + ZEN_ASSERT(!SystemRootDir.empty()); + ZEN_ASSERT(!StateFilePath.empty()); + ZEN_ASSERT(!LocalPath.empty()); + const std::u8string LocalPathString = LocalPath.generic_u8string(); + IoHash PathHash = IoHash::HashBuffer(LocalPathString.data(), LocalPathString.length()); + const std::filesystem::path StateDownloadFolder = ZenStateDownloadFolder(SystemRootDir); + CreateDirectories(StateDownloadFolder); + std::filesystem::path WritePath = StateDownloadFolder / (PathHash.ToHexString() + ".json"); + + CbObjectWriter Writer; + Writer.AddString("path", (const char*)LocalPath.u8string().c_str()); + Writer.AddString("statePath", (const char*)StateFilePath.u8string().c_str()); + Writer.AddDateTime("date", DateTime::Now()); + Writer.BeginArray("builds"sv); + { + Writer.BeginObject(); + { + Writer.AddObjectId("buildId", BuildId); + Writer.BeginArray("parts"); + for (const auto& It : BuildParts) + { + Writer.BeginObject(); + { + Writer.AddObjectId("partId", It.first); + Writer.AddString("partName", It.second); + } + Writer.EndObject(); + } + Writer.EndArray(); // parts + } + Writer.EndObject(); + } + Writer.EndArray(); // builds + + CbObject Payload = Writer.Save(); + ExtendableStringBuilder<512> SB; + CompactBinaryToJson(Payload.GetView(), SB); + MemoryView JsonPayload(SB.Data(), SB.Size()); + TemporaryFile::SafeWriteFile(WritePath, JsonPayload); +} + +std::vector<std::filesystem::path> +GetDownloadedStatePaths(const std::filesystem::path& SystemRootDir) +{ + ZEN_ASSERT(!SystemRootDir.empty()); + + ZEN_TRACE_CPU("GetDownloadedPaths"); + + std::vector<std::filesystem::path> Result; + + const std::filesystem::path StateDownloadFolder = ZenStateDownloadFolder(SystemRootDir); + if (IsDir(StateDownloadFolder)) + { + DirectoryContent Content; + GetDirectoryContent(ZenStateDownloadFolder(SystemRootDir), DirectoryContentFlags::IncludeFiles, Content); + for (const std::filesystem::path& EntryPath : Content.Files) + { + IoHash EntryPathHash; + if (IoHash::TryParse(EntryPath.stem().string(), EntryPathHash)) + { + Result.push_back(EntryPath); + } + } + } + return Result; +} + +} // namespace zen diff --git a/src/zenremotestore/builds/buildstorageoperations.cpp b/src/zenremotestore/builds/buildstorageoperations.cpp index ca927e643..469d29e0d 100644 --- a/src/zenremotestore/builds/buildstorageoperations.cpp +++ b/src/zenremotestore/builds/buildstorageoperations.cpp @@ -2,6 +2,7 @@ #include <zenremotestore/builds/buildstorageoperations.h> +#include <zenremotestore/builds/buildsavedstate.h> #include <zenremotestore/builds/buildstorage.h> #include <zenremotestore/builds/buildstoragecache.h> #include <zenremotestore/chunking/chunkblock.h> @@ -343,84 +344,6 @@ namespace { } // namespace -bool -ReadStateObject(BuildOpLogOutput& LogOutput, - CbObjectView StateView, - Oid& OutBuildId, - std::vector<Oid>& BuildPartsIds, - std::vector<std::string>& BuildPartsNames, - std::vector<ChunkedFolderContent>& OutPartContents, - FolderContent& OutLocalFolderState) -{ - try - { - CbObjectView BuildView = StateView["builds"sv].AsArrayView().CreateViewIterator().AsObjectView(); - OutBuildId = BuildView["buildId"sv].AsObjectId(); - for (CbFieldView PartView : BuildView["parts"sv].AsArrayView()) - { - CbObjectView PartObjectView = PartView.AsObjectView(); - BuildPartsIds.push_back(PartObjectView["partId"sv].AsObjectId()); - BuildPartsNames.push_back(std::string(PartObjectView["partName"sv].AsString())); - OutPartContents.push_back(LoadChunkedFolderContentToCompactBinary(PartObjectView["content"sv].AsObjectView())); - } - OutLocalFolderState = LoadFolderContentToCompactBinary(StateView["localFolderState"sv].AsObjectView()); - return true; - } - catch (const std::exception& Ex) - { - LOG_OUTPUT_WARN(LogOutput, "Unable to read local state: ", Ex.what()); - return false; - } -} - -bool -ReadStateFile(BuildOpLogOutput& LogOutput, - const std::filesystem::path& StateFilePath, - FolderContent& OutLocalFolderState, - ChunkedFolderContent& OutLocalContent) -{ - ZEN_TRACE_CPU("ReadStateFile"); - bool HasLocalState = false; - try - { - CbObject CurrentStateObject = LoadCompactBinaryObject(StateFilePath).Object; - if (CurrentStateObject) - { - Oid CurrentBuildId; - std::vector<Oid> SavedBuildPartIds; - std::vector<std::string> SavedBuildPartsNames; - std::vector<ChunkedFolderContent> SavedPartContents; - if (ReadStateObject(LogOutput, - CurrentStateObject, - CurrentBuildId, - SavedBuildPartIds, - SavedBuildPartsNames, - SavedPartContents, - OutLocalFolderState)) - { - if (!SavedPartContents.empty()) - { - if (SavedPartContents.size() == 1) - { - OutLocalContent = std::move(SavedPartContents[0]); - } - else - { - OutLocalContent = MergeChunkedFolderContents(SavedPartContents[0], - std::span<const ChunkedFolderContent>(SavedPartContents).subspan(1)); - } - HasLocalState = true; - } - } - } - } - catch (const std::exception& Ex) - { - LOG_OUTPUT_WARN(LogOutput, "Failed reading state file {}, falling back to scannning. Reason: {}", StateFilePath, Ex.what()); - } - return HasLocalState; -} - struct BlockRangeDescriptor { uint32_t BlockIndex = (uint32_t)-1; @@ -3405,49 +3328,46 @@ BuildsOperationUpdateFolder::FindScavengeSources() { ZEN_TRACE_CPU("FindScavengeSources"); + std::vector<std::filesystem::path> StatePaths = GetDownloadedStatePaths(m_Options.SystemRootDir); + std::vector<ScavengeSource> Result; - DirectoryContent Content; - GetDirectoryContent(m_Options.SystemRootDir / "builds" / "downloads", DirectoryContentFlags::IncludeFiles, Content); - for (const std::filesystem::path& EntryPath : Content.Files) - { - bool DeleteEntry = false; - IoHash EntryPathHash; - if (IoHash::TryParse(EntryPath.stem().string(), EntryPathHash)) - { - // Read state and verify that it is valid - IoBuffer MetaDataJson = ReadFile(EntryPath).Flatten(); - std::string_view Json(reinterpret_cast<const char*>(MetaDataJson.GetData()), MetaDataJson.GetSize()); - std::string JsonError; - CbObject DownloadInfo = LoadCompactBinaryFromJson(Json, JsonError).AsObject(); - if (JsonError.empty()) + for (const std::filesystem::path& EntryPath : StatePaths) + { + bool DeleteEntry = false; + + // Read state and verify that it is valid + IoBuffer MetaDataJson = ReadFile(EntryPath).Flatten(); + std::string_view Json(reinterpret_cast<const char*>(MetaDataJson.GetData()), MetaDataJson.GetSize()); + std::string JsonError; + CbObject DownloadInfo = LoadCompactBinaryFromJson(Json, JsonError).AsObject(); + if (JsonError.empty()) + { + std::filesystem::path StateFilePath = DownloadInfo["statePath"].AsU8String(); + if (IsFile(StateFilePath)) { - std::filesystem::path StateFilePath = DownloadInfo["statePath"].AsU8String(); - if (IsFile(StateFilePath)) + std::filesystem::path Path = DownloadInfo["path"].AsU8String(); + if (!std::filesystem::equivalent(Path, m_Path)) { - std::filesystem::path Path = DownloadInfo["path"].AsU8String(); - if (!std::filesystem::equivalent(Path, m_Path)) + if (IsDir(Path)) { - if (IsDir(Path)) - { - Result.push_back({.StateFilePath = std::move(StateFilePath), .Path = std::move(Path)}); - } - else - { - DeleteEntry = true; - } + Result.push_back({.StateFilePath = std::move(StateFilePath), .Path = std::move(Path)}); + } + else + { + DeleteEntry = true; } - } - else - { - DeleteEntry = true; } } else { - LOG_OUTPUT_WARN(m_LogOutput, "Invalid download state file at {}. '{}'", EntryPath, JsonError); DeleteEntry = true; } } + else + { + LOG_OUTPUT_WARN(m_LogOutput, "Invalid download state file at {}. '{}'", EntryPath, JsonError); + DeleteEntry = true; + } if (DeleteEntry) { @@ -3525,12 +3445,13 @@ BuildsOperationUpdateFolder::FindScavengeContent(const ScavengeSource& Source, ZEN_TRACE_CPU("FindScavengeContent"); FolderContent LocalFolderState; - if (!ReadStateFile(m_LogOutput, Source.StateFilePath, LocalFolderState, OutScavengedLocalContent)) + try { - return false; + ReadStateFile(Source.StateFilePath, LocalFolderState, OutScavengedLocalContent); } - if (!IsDir(Source.Path)) + catch (const std::exception& Ex) { + ZEN_CONSOLE_DEBUG("Skipping invalid build state at '{}', reason: {}", Source.StateFilePath, Ex.what()); return false; } @@ -6066,77 +5987,6 @@ BuildsOperationUploadFolder::CalculateAbsoluteChunkOrders( } void -BuildsOperationUploadFolder::CalculateLocalChunkOrders(const std::span<const uint32_t>& AbsoluteChunkOrders, - const std::span<const IoHash> LooseChunkHashes, - const std::span<const uint64_t> LooseChunkRawSizes, - const std::span<const ChunkBlockDescription>& BlockDescriptions, - std::vector<IoHash>& OutLocalChunkHashes, - std::vector<uint64_t>& OutLocalChunkRawSizes, - std::vector<uint32_t>& OutLocalChunkOrders) -{ - ZEN_TRACE_CPU("CalculateLocalChunkOrders"); - - std::vector<IoHash> AbsoluteChunkHashes; - std::vector<uint64_t> AbsoluteChunkRawSizes; - AbsoluteChunkHashes.insert(AbsoluteChunkHashes.end(), LooseChunkHashes.begin(), LooseChunkHashes.end()); - AbsoluteChunkRawSizes.insert(AbsoluteChunkRawSizes.end(), LooseChunkRawSizes.begin(), LooseChunkRawSizes.end()); - for (const ChunkBlockDescription& Block : BlockDescriptions) - { - AbsoluteChunkHashes.insert(AbsoluteChunkHashes.end(), Block.ChunkRawHashes.begin(), Block.ChunkRawHashes.end()); - AbsoluteChunkRawSizes.insert(AbsoluteChunkRawSizes.end(), Block.ChunkRawLengths.begin(), Block.ChunkRawLengths.end()); - } - OutLocalChunkHashes.reserve(AbsoluteChunkHashes.size()); - OutLocalChunkRawSizes.reserve(AbsoluteChunkRawSizes.size()); - OutLocalChunkOrders.reserve(AbsoluteChunkOrders.size()); - - tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> ChunkHashToChunkIndex; - ChunkHashToChunkIndex.reserve(AbsoluteChunkHashes.size()); - - for (uint32_t AbsoluteChunkOrderIndex = 0; AbsoluteChunkOrderIndex < AbsoluteChunkOrders.size(); AbsoluteChunkOrderIndex++) - { - const uint32_t AbsoluteChunkIndex = AbsoluteChunkOrders[AbsoluteChunkOrderIndex]; - const IoHash& AbsoluteChunkHash = AbsoluteChunkHashes[AbsoluteChunkIndex]; - const uint64_t AbsoluteChunkRawSize = AbsoluteChunkRawSizes[AbsoluteChunkIndex]; - - if (auto It = ChunkHashToChunkIndex.find(AbsoluteChunkHash); It != ChunkHashToChunkIndex.end()) - { - const uint32_t LocalChunkIndex = It->second; - OutLocalChunkOrders.push_back(LocalChunkIndex); - } - else - { - uint32_t LocalChunkIndex = gsl::narrow<uint32_t>(OutLocalChunkHashes.size()); - OutLocalChunkHashes.push_back(AbsoluteChunkHash); - OutLocalChunkRawSizes.push_back(AbsoluteChunkRawSize); - OutLocalChunkOrders.push_back(LocalChunkIndex); - ChunkHashToChunkIndex.insert_or_assign(AbsoluteChunkHash, LocalChunkIndex); - } -#if EXTRA_VERIFY - const uint32_t LocalChunkIndex = OutLocalChunkOrders[AbsoluteChunkOrderIndex]; - const IoHash& LocalChunkHash = OutLocalChunkHashes[LocalChunkIndex]; - const uint64_t& LocalChunkRawSize = OutLocalChunkRawSizes[LocalChunkIndex]; - ZEN_ASSERT(LocalChunkHash == AbsoluteChunkHash); - ZEN_ASSERT(LocalChunkRawSize == AbsoluteChunkRawSize); -#endif // EXTRA_VERIFY - } -#if EXTRA_VERIFY - for (uint32_t OrderIndex = 0; OrderIndex < OutLocalChunkOrders.size(); OrderIndex++) - { - uint32_t LocalChunkIndex = OutLocalChunkOrders[OrderIndex]; - const IoHash LocalChunkHash = OutLocalChunkHashes[LocalChunkIndex]; - uint64_t LocalChunkRawSize = OutLocalChunkRawSizes[LocalChunkIndex]; - - uint32_t VerifyChunkIndex = AbsoluteChunkOrders[OrderIndex]; - const IoHash VerifyChunkHash = AbsoluteChunkHashes[VerifyChunkIndex]; - uint64_t VerifyChunkRawSize = AbsoluteChunkRawSizes[VerifyChunkIndex]; - - ZEN_ASSERT(LocalChunkHash == VerifyChunkHash); - ZEN_ASSERT(LocalChunkRawSize == VerifyChunkRawSize); - } -#endif // EXTRA_VERIFY -} - -void BuildsOperationUploadFolder::WriteBuildContentToCompactBinary(CbObjectWriter& PartManifestWriter, const SourcePlatform Platform, std::span<const std::filesystem::path> Paths, diff --git a/src/zenremotestore/chunking/chunkedcontent.cpp b/src/zenremotestore/chunking/chunkedcontent.cpp index 9df7725db..eb0e8bdc9 100644 --- a/src/zenremotestore/chunking/chunkedcontent.cpp +++ b/src/zenremotestore/chunking/chunkedcontent.cpp @@ -755,6 +755,50 @@ DeletePathsFromChunkedContent(const ChunkedFolderContent& BaseContent, std::span return DeletePathsFromChunkedContent(BaseContent, BaseLookup, DeletedPaths); } +bool +CompareChunkedContent(const ChunkedFolderContent& Lhs, const ChunkedFolderContent& Rhs) +{ + tsl::robin_map<std::string, size_t> RhsPathToIndex; + const size_t RhsPathCount = Rhs.Paths.size(); + RhsPathToIndex.reserve(RhsPathCount); + for (size_t RhsPathIndex = 0; RhsPathIndex < RhsPathCount; RhsPathIndex++) + { + RhsPathToIndex.insert({Rhs.Paths[RhsPathIndex].generic_string(), RhsPathIndex}); + } + const size_t LhsPathCount = Lhs.Paths.size(); + for (size_t LhsPathIndex = 0; LhsPathIndex < LhsPathCount; LhsPathIndex++) + { + if (auto It = RhsPathToIndex.find(Lhs.Paths[LhsPathIndex].generic_string()); It != RhsPathToIndex.end()) + { + const size_t RhsPathIndex = It->second; + if ((Lhs.RawHashes[LhsPathIndex] != Rhs.RawHashes[RhsPathIndex]) || + (!FolderContent::AreFileAttributesEqual(Lhs.Attributes[LhsPathIndex], Rhs.Attributes[RhsPathIndex]))) + { + return false; + } + } + else + { + return false; + } + } + tsl::robin_set<std::string> LhsPathExists; + LhsPathExists.reserve(LhsPathCount); + for (size_t LhsPathIndex = 0; LhsPathIndex < LhsPathCount; LhsPathIndex++) + { + LhsPathExists.insert({Lhs.Paths[LhsPathIndex].generic_string()}); + } + for (size_t RhsPathIndex = 0; RhsPathIndex < RhsPathCount; RhsPathIndex++) + { + if (!LhsPathExists.contains(Rhs.Paths[RhsPathIndex].generic_string())) + { + return false; + } + } + + return true; +}; + ChunkedFolderContent ChunkFolderContent(ChunkingStatistics& Stats, WorkerThreadPool& WorkerPool, diff --git a/src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h b/src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h new file mode 100644 index 000000000..a11641b0d --- /dev/null +++ b/src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zenremotestore/chunking/chunkblock.h> +#include <zenremotestore/chunking/chunkedcontent.h> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <tsl/robin_map.h> +ZEN_THIRD_PARTY_INCLUDES_END + +namespace zen { + +class CbObjectView; + +CbObject CreateStateObject(const Oid& BuildId, + const std::vector<std::pair<Oid, std::string>>& AllBuildParts, + std::span<const ChunkedFolderContent> PartContents, + const FolderContent& LocalFolderState, + const std::filesystem::path& LocalPath); + +void ReadBuildContentFromCompactBinary(CbObjectView BuildPartManifest, + SourcePlatform& OutPlatform, + std::vector<std::filesystem::path>& OutPaths, + std::vector<IoHash>& OutRawHashes, + std::vector<uint64_t>& OutRawSizes, + std::vector<uint32_t>& OutAttributes, + std::vector<IoHash>& OutSequenceRawHashes, + std::vector<uint32_t>& OutChunkCounts, + std::vector<uint32_t>& OutAbsoluteChunkOrders, + std::vector<IoHash>& OutLooseChunkHashes, + std::vector<uint64_t>& OutLooseChunkRawSizes, + std::vector<IoHash>& OutBlockRawHashes); + +void CalculateLocalChunkOrders(const std::span<const uint32_t>& AbsoluteChunkOrders, + const std::span<const IoHash> LooseChunkHashes, + const std::span<const uint64_t> LooseChunkRawSizes, + const std::span<const ChunkBlockDescription>& BlockDescriptions, + std::vector<IoHash>& OutLocalChunkHashes, + std::vector<uint64_t>& OutLocalChunkRawSizes, + std::vector<uint32_t>& OutLocalChunkOrders); + +void ReadStateObject(CbObjectView StateView, + Oid& OutBuildId, + std::vector<Oid>& BuildPartsIds, + std::vector<std::string>& BuildPartsNames, + std::vector<ChunkedFolderContent>& OutPartContents, + FolderContent& OutLocalFolderState); + +void ReadStateFile(const std::filesystem::path& StateFilePath, FolderContent& OutLocalFolderState, ChunkedFolderContent& OutLocalContent); + +void AddDownloadedPath(const std::filesystem::path& SystemRootDir, + const Oid& BuildId, + const std::vector<std::pair<Oid, std::string>>& BuildParts, + const std::filesystem::path& StateFilePath, + const std::filesystem::path& LocalPath); + +std::vector<std::filesystem::path> GetDownloadedStatePaths(const std::filesystem::path& SystemRootDir); + +} // namespace zen diff --git a/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h b/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h index 306a5d990..2a56d14d3 100644 --- a/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h +++ b/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h @@ -121,6 +121,8 @@ ChunkedFolderContent DeletePathsFromChunkedContent(const ChunkedFolderContent& std::span<const std::filesystem::path> DeletedPaths); ChunkedFolderContent DeletePathsFromChunkedContent(const ChunkedFolderContent& Base, std::span<const std::filesystem::path> DeletedPaths); +bool CompareChunkedContent(const ChunkedFolderContent& Lhs, const ChunkedFolderContent& Rhs); + struct ChunkingStatistics { std::atomic<uint64_t> FilesProcessed = 0; |