aboutsummaryrefslogtreecommitdiff
path: root/src/zenremotestore/builds
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2025-11-24 10:06:52 +0100
committerGitHub Enterprise <[email protected]>2025-11-24 10:06:52 +0100
commit6dcdddbf733b0aa323ffb7ecbe56c04b15c6c16a (patch)
tree78685156e98214e4e1125501a8c09cac37bc45f4 /src/zenremotestore/builds
parentchangelog (#661) (diff)
downloadzen-6dcdddbf733b0aa323ffb7ecbe56c04b15c6c16a.tar.xz
zen-6dcdddbf733b0aa323ffb7ecbe56c04b15c6c16a.zip
update state when wildcard (#657)
* add --append option and improve state handling when using downloads for `zen builds download`
Diffstat (limited to 'src/zenremotestore/builds')
-rw-r--r--src/zenremotestore/builds/buildcontent.cpp253
-rw-r--r--src/zenremotestore/builds/buildsavedstate.cpp845
-rw-r--r--src/zenremotestore/builds/buildstorageoperations.cpp261
3 files changed, 899 insertions, 460 deletions
diff --git a/src/zenremotestore/builds/buildcontent.cpp b/src/zenremotestore/builds/buildcontent.cpp
new file mode 100644
index 000000000..e842550b4
--- /dev/null
+++ b/src/zenremotestore/builds/buildcontent.cpp
@@ -0,0 +1,253 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenremotestore/builds/buildcontent.h>
+
+#include <zencore/compactbinaryutil.h>
+#include <zencore/fmtutils.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <tsl/robin_set.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+using namespace std::literals;
+
+void
+WriteBuildContentToCompactBinary(CbObjectWriter& PartManifestWriter,
+ const SourcePlatform Platform,
+ std::span<const std::filesystem::path> Paths,
+ std::span<const IoHash> RawHashes,
+ std::span<const uint64_t> RawSizes,
+ std::span<const uint32_t> Attributes,
+ std::span<const IoHash> SequenceRawHashes,
+ std::span<const uint32_t> ChunkCounts,
+ std::span<const IoHash> LocalChunkHashes,
+ std::span<const uint64_t> LocalChunkRawSizes,
+ const std::vector<uint32_t>& AbsoluteChunkOrders,
+ const std::span<const uint32_t> LooseLocalChunkIndexes,
+ const std::span<IoHash> BlockHashes)
+{
+ ZEN_ASSERT(Platform != SourcePlatform::_Count);
+ PartManifestWriter.AddString("platform"sv, ToString(Platform));
+
+ uint64_t TotalSize = 0;
+ for (const uint64_t Size : RawSizes)
+ {
+ TotalSize += Size;
+ }
+ PartManifestWriter.AddInteger("totalSize", TotalSize);
+
+ PartManifestWriter.BeginObject("files"sv);
+ {
+ compactbinary_helpers::WriteArray(Paths, "paths"sv, PartManifestWriter);
+ compactbinary_helpers::WriteArray(RawHashes, "rawhashes"sv, PartManifestWriter);
+ compactbinary_helpers::WriteArray(RawSizes, "rawsizes"sv, PartManifestWriter);
+ if (Platform == SourcePlatform::Windows)
+ {
+ compactbinary_helpers::WriteArray(Attributes, "attributes"sv, PartManifestWriter);
+ }
+ if (Platform == SourcePlatform::Linux || Platform == SourcePlatform::MacOS)
+ {
+ compactbinary_helpers::WriteArray(Attributes, "mode"sv, PartManifestWriter);
+ }
+ }
+ PartManifestWriter.EndObject(); // files
+
+ PartManifestWriter.BeginObject("chunkedContent");
+ {
+ compactbinary_helpers::WriteArray(SequenceRawHashes, "sequenceRawHashes"sv, PartManifestWriter);
+ compactbinary_helpers::WriteArray(ChunkCounts, "chunkcounts"sv, PartManifestWriter);
+ compactbinary_helpers::WriteArray(AbsoluteChunkOrders, "chunkorders"sv, PartManifestWriter);
+ }
+ PartManifestWriter.EndObject(); // chunkedContent
+
+ size_t LooseChunkCount = LooseLocalChunkIndexes.size();
+ if (LooseChunkCount > 0)
+ {
+ PartManifestWriter.BeginObject("chunkAttachments");
+ {
+ PartManifestWriter.BeginArray("rawHashes"sv);
+ for (uint32_t ChunkIndex : LooseLocalChunkIndexes)
+ {
+ PartManifestWriter.AddBinaryAttachment(LocalChunkHashes[ChunkIndex]);
+ }
+ PartManifestWriter.EndArray(); // rawHashes
+
+ PartManifestWriter.BeginArray("chunkRawSizes"sv);
+ for (uint32_t ChunkIndex : LooseLocalChunkIndexes)
+ {
+ PartManifestWriter.AddInteger(LocalChunkRawSizes[ChunkIndex]);
+ }
+ PartManifestWriter.EndArray(); // chunkSizes
+ }
+ PartManifestWriter.EndObject(); //
+ }
+
+ if (BlockHashes.size() > 0)
+ {
+ PartManifestWriter.BeginObject("blockAttachments");
+ {
+ compactbinary_helpers::WriteBinaryAttachmentArray(BlockHashes, "rawHashes"sv, PartManifestWriter);
+ }
+ PartManifestWriter.EndObject(); // blocks
+ }
+}
+
+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();
+
+ OutPaths = compactbinary_helpers::ReadArray<std::filesystem::path>("paths"sv, FilesObject);
+ OutRawHashes = compactbinary_helpers::ReadArray<IoHash>("rawhashes"sv, FilesObject);
+ OutRawSizes = compactbinary_helpers::ReadArray<uint64_t>("rawsizes"sv, FilesObject);
+
+ 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<uint32_t>("mode"sv, FilesObject);
+ 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<uint32_t>("attributes"sv, FilesObject);
+ 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)
+ {
+ OutSequenceRawHashes = compactbinary_helpers::ReadArray<IoHash>("sequenceRawHashes"sv, ChunkContentView);
+ OutChunkCounts = compactbinary_helpers::ReadArray<uint32_t>("chunkcounts"sv, ChunkContentView);
+ if (OutChunkCounts.size() != OutSequenceRawHashes.size())
+ {
+ throw std::runtime_error(fmt::format("Number of chunk count entries does not match number of paths"));
+ }
+ OutAbsoluteChunkOrders = compactbinary_helpers::ReadArray<uint32_t>("chunkorders"sv, ChunkContentView);
+ }
+ else if (FilesObject["chunkcounts"sv])
+ {
+ // Legacy zen style
+
+ std::vector<uint32_t> LegacyChunkCounts = compactbinary_helpers::ReadArray<uint32_t>("chunkcounts"sv, FilesObject);
+ 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<uint32_t>("chunkorders"sv, FilesObject);
+
+ 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();
+ {
+ OutLooseChunkHashes = compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, ChunkAttachmentsView);
+ OutLooseChunkRawSizes = compactbinary_helpers::ReadArray<uint64_t>("chunkRawSizes"sv, ChunkAttachmentsView);
+ 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();
+ {
+ OutBlockRawHashes = compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, BlocksView);
+ }
+}
+
+} // namespace zen
diff --git a/src/zenremotestore/builds/buildsavedstate.cpp b/src/zenremotestore/builds/buildsavedstate.cpp
index cf46668f9..5a86ee865 100644
--- a/src/zenremotestore/builds/buildsavedstate.cpp
+++ b/src/zenremotestore/builds/buildsavedstate.cpp
@@ -13,7 +13,10 @@ ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_set.h>
ZEN_THIRD_PARTY_INCLUDES_END
-#define EXTRA_VERIFY 0
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+#endif // ZEN_WITH_TESTS
namespace zen {
@@ -26,299 +29,181 @@ namespace {
}
} // 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)
+///////////////////////// BuildsSelection
+
+void
+BuildsSelection::Write(const BuildsSelection& Selection, CbWriter& Output)
{
- CbObjectWriter CurrentStateWriter;
- CurrentStateWriter.AddString("path", (const char*)LocalPath.u8string().c_str());
- CurrentStateWriter.BeginArray("builds"sv);
+ Output.BeginArray("builds"sv);
+ for (const BuildsSelection::Build& Build : Selection.Builds)
{
- CurrentStateWriter.BeginObject();
+ Output.BeginObject();
{
- CurrentStateWriter.AddObjectId("buildId"sv, BuildId);
- CurrentStateWriter.BeginArray("parts"sv);
- for (size_t PartIndex = 0; PartIndex < AllBuildParts.size(); PartIndex++)
+ ZEN_ASSERT(Build.Id != Oid::Zero);
+ Output.AddObjectId("buildId"sv, Build.Id);
+ compactbinary_helpers::WriteArray(Build.IncludeWildcards, "includeWildcards"sv, Output);
+ compactbinary_helpers::WriteArray(Build.ExcludeWildcards, "excludeWildcards"sv, Output);
+
+ Output.BeginArray("parts"sv);
+ for (const BuildsSelection::BuildPart& BuildPart : Build.Parts)
{
- const Oid BuildPartId = AllBuildParts[PartIndex].first;
- CurrentStateWriter.BeginObject();
+ Output.BeginObject();
{
- CurrentStateWriter.AddObjectId("partId"sv, BuildPartId);
- CurrentStateWriter.AddString("partName"sv, AllBuildParts[PartIndex].second);
- CurrentStateWriter.BeginObject("content");
- {
- SaveChunkedFolderContentToCompactBinary(PartContents[PartIndex], CurrentStateWriter);
- }
- CurrentStateWriter.EndObject();
+ ZEN_ASSERT(BuildPart.Id != Oid::Zero);
+ Output.AddObjectId("partId"sv, BuildPart.Id);
+ Output.AddString("partName"sv, BuildPart.Name);
}
- CurrentStateWriter.EndObject();
+ Output.EndObject();
}
- CurrentStateWriter.EndArray(); // parts
+ Output.EndArray(); // parts
}
- CurrentStateWriter.EndObject();
+ Output.EndObject();
}
- CurrentStateWriter.EndArray(); // builds
-
- CurrentStateWriter.BeginObject("localFolderState"sv);
- {
- SaveFolderContentToCompactBinary(LocalFolderState, CurrentStateWriter);
- }
- CurrentStateWriter.EndObject(); // localFolderState
-
- return CurrentStateWriter.Save();
+ Output.EndArray(); // builds
}
-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)
+BuildsSelection
+BuildsSelection::Read(CbObjectView& Input, std::vector<ChunkedFolderContent>* OptionalOutLegacyPartsContent)
{
- OutPlatform = FromString(BuildPartManifest["platform"sv].AsString(), SourcePlatform::_Count);
-
- CbObjectView FilesObject = BuildPartManifest["files"sv].AsObjectView();
-
- OutPaths = compactbinary_helpers::ReadArray<std::filesystem::path>("paths"sv, FilesObject);
- OutRawHashes = compactbinary_helpers::ReadArray<IoHash>("rawhashes"sv, FilesObject);
- OutRawSizes = compactbinary_helpers::ReadArray<uint64_t>("rawsizes"sv, FilesObject);
-
- 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<BuildsSelection::Build> Builds;
- std::vector<uint32_t> ModeArray = compactbinary_helpers::ReadArray<uint32_t>("mode"sv, FilesObject);
- if (ModeArray.size() != PathCount && ModeArray.size() != 0)
+ CbArrayView BuildsArray = Input["builds"sv].AsArrayView();
+ Builds.reserve(BuildsArray.Num());
+ for (CbFieldView BuildView : BuildsArray)
{
- throw std::runtime_error(fmt::format("Number of attribute entries does not match number of paths"));
- }
-
- std::vector<uint32_t> AttributeArray = compactbinary_helpers::ReadArray<uint32_t>("attributes"sv, FilesObject);
- 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)
+ std::vector<BuildsSelection::BuildPart> Parts;
+ CbObjectView BuildObjectView = BuildView.AsObjectView();
+ Oid BuildId = BuildObjectView["buildId"sv].AsObjectId();
+ if (BuildId == Oid::Zero)
{
- OutPlatform = SourcePlatform::Windows;
+ throw std::runtime_error(fmt::format("BuildsSelection build id is invalid '{}'", BuildId));
}
- OutAttributes = std::move(AttributeArray);
- }
- else
- {
- if (OutPlatform == SourcePlatform::_Count)
- {
- OutPlatform = GetSourceCurrentPlatform();
- }
- }
- if (CbObjectView ChunkContentView = BuildPartManifest["chunkedContent"sv].AsObjectView(); ChunkContentView)
- {
- OutSequenceRawHashes = compactbinary_helpers::ReadArray<IoHash>("sequenceRawHashes"sv, ChunkContentView);
- OutChunkCounts = compactbinary_helpers::ReadArray<uint32_t>("chunkcounts"sv, ChunkContentView);
- if (OutChunkCounts.size() != OutSequenceRawHashes.size())
- {
- throw std::runtime_error(fmt::format("Number of chunk count entries does not match number of paths"));
- }
- OutAbsoluteChunkOrders = compactbinary_helpers::ReadArray<uint32_t>("chunkorders"sv, ChunkContentView);
- }
- else if (FilesObject["chunkcounts"sv])
- {
- // Legacy zen style
-
- std::vector<uint32_t> LegacyChunkCounts = compactbinary_helpers::ReadArray<uint32_t>("chunkcounts"sv, FilesObject);
- 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<uint32_t>("chunkorders"sv, FilesObject);
-
- 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);
+ std::vector<std::string> IncludeWildcards = compactbinary_helpers::ReadArray<std::string>("includeWildcards"sv, BuildObjectView);
+ std::vector<std::string> ExcludeWildcards = compactbinary_helpers::ReadArray<std::string>("excludeWildcards"sv, BuildObjectView);
- uint32_t OrderIndexOffset = 0;
- for (uint32_t PathIndex = 0; PathIndex < OutPaths.size(); PathIndex++)
+ CbArrayView BuildPartsArray = BuildObjectView["parts"sv].AsArrayView();
+ Parts.reserve(BuildPartsArray.Num());
+ for (CbFieldView PartView : BuildPartsArray)
{
- const IoHash& PathRawHash = OutRawHashes[PathIndex];
- uint32_t LegacyChunkCount = LegacyChunkCounts[PathIndex];
-
- if (FoundRawHashes.insert(PathRawHash).second)
+ CbObjectView PartObjectView = PartView.AsObjectView();
+ Oid BuildPartId = PartObjectView["partId"sv].AsObjectId();
+ if (BuildPartId == Oid::Zero)
{
- 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());
+ throw std::runtime_error(fmt::format("BuildsSelection build part id in build '{}' is invalid '{}'", BuildId, BuildPartId));
}
- OrderIndexOffset += LegacyChunkCounts[PathIndex];
- }
- }
- else
- {
- // Legacy C# style
+ std::string_view BuildPartName = PartObjectView["partName"sv].AsString();
- 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)
+ if (OptionalOutLegacyPartsContent != nullptr)
{
- const IoHash& PathRawHash = OutRawHashes[PathIndex];
- if (FoundRawHashes.insert(PathRawHash).second)
+ CbObjectView PartContentObjectView = PartObjectView["content"sv].AsObjectView();
+ if (PartContentObjectView)
{
- OutSequenceRawHashes.push_back(PathRawHash);
- OutChunkCounts.push_back(1);
- OutAbsoluteChunkOrders.push_back(OrderIndexOffset);
- OutLooseChunkHashes.push_back(PathRawHash);
- OutLooseChunkRawSizes.push_back(OutRawSizes[PathIndex]);
- OrderIndexOffset += 1;
+ OptionalOutLegacyPartsContent->push_back(LoadChunkedFolderContentFromCompactBinary(PartContentObjectView));
}
}
+ Parts.push_back(BuildsSelection::BuildPart{.Id = BuildPartId, .Name = std::string(BuildPartName)});
}
- }
- CbObjectView ChunkAttachmentsView = BuildPartManifest["chunkAttachments"sv].AsObjectView();
- {
- OutLooseChunkHashes = compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, ChunkAttachmentsView);
- OutLooseChunkRawSizes = compactbinary_helpers::ReadArray<uint64_t>("chunkRawSizes"sv, ChunkAttachmentsView);
- 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();
- {
- OutBlockRawHashes = compactbinary_helpers::ReadBinaryAttachmentArray("rawHashes"sv, BlocksView);
+ Builds.push_back(BuildsSelection::Build{.Id = BuildId,
+ .Parts = std::move(Parts),
+ .IncludeWildcards = std::move(IncludeWildcards),
+ .ExcludeWildcards = std::move(ExcludeWildcards)});
}
+ return BuildsSelection{.Builds = std::move(Builds)};
}
+///////////////////////// BuildState
+
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)
+BuildState::Write(const BuildState& State, CbWriter& Output)
{
- ZEN_TRACE_CPU("CalculateLocalChunkOrders");
+ BuildsSelection::Write(State.Selection, Output);
- 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)
+ Output.BeginObject("content");
{
- AbsoluteChunkHashes.insert(AbsoluteChunkHashes.end(), Block.ChunkRawHashes.begin(), Block.ChunkRawHashes.end());
- AbsoluteChunkRawSizes.insert(AbsoluteChunkRawSizes.end(), Block.ChunkRawLengths.begin(), Block.ChunkRawLengths.end());
+ SaveChunkedFolderContentToCompactBinary(State.ChunkedContent, Output);
}
- OutLocalChunkHashes.reserve(AbsoluteChunkHashes.size());
- OutLocalChunkRawSizes.reserve(AbsoluteChunkRawSizes.size());
- OutLocalChunkOrders.reserve(AbsoluteChunkOrders.size());
+ Output.EndObject(); // content
+}
- tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> ChunkHashToChunkIndex;
- ChunkHashToChunkIndex.reserve(AbsoluteChunkHashes.size());
+BuildState
+BuildState::Read(CbObjectView& Input)
+{
+ std::vector<ChunkedFolderContent> LegacyPartsContent;
+ BuildsSelection Selection = BuildsSelection::Read(Input, &LegacyPartsContent);
- for (uint32_t AbsoluteChunkOrderIndex = 0; AbsoluteChunkOrderIndex < AbsoluteChunkOrders.size(); AbsoluteChunkOrderIndex++)
+ ChunkedFolderContent ChunkedContent;
+ CbObjectView ContentObjectView = Input["content"sv].AsObjectView();
+ if (ContentObjectView)
{
- 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())
+ ZEN_ASSERT(LegacyPartsContent.empty());
+ ChunkedContent = LoadChunkedFolderContentFromCompactBinary(ContentObjectView);
+ }
+ else
+ {
+ if (LegacyPartsContent.size() == 1)
{
- const uint32_t LocalChunkIndex = It->second;
- OutLocalChunkOrders.push_back(LocalChunkIndex);
+ ChunkedContent = std::move(LegacyPartsContent.front());
}
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
+ ChunkedContent =
+ MergeChunkedFolderContents(LegacyPartsContent[0], std::span<const ChunkedFolderContent>(LegacyPartsContent).subspan(1));
+ }
}
-#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];
+ return BuildState{.Selection = std::move(Selection), .ChunkedContent = std::move(ChunkedContent)};
+}
+
+///////////////////////// BuildSaveState
- ZEN_ASSERT(LocalChunkHash == VerifyChunkHash);
- ZEN_ASSERT(LocalChunkRawSize == VerifyChunkRawSize);
+void
+BuildSaveState::Write(const BuildSaveState& SaveState, CbWriter& Output)
+{
+ ZEN_ASSERT(!SaveState.LocalPath.empty());
+
+ Output.AddString("path", (const char*)SaveState.LocalPath.u8string().c_str());
+ BuildsSelection::Write(SaveState.State.Selection, Output);
+
+ Output.BeginObject("content");
+ {
+ SaveChunkedFolderContentToCompactBinary(SaveState.State.ChunkedContent, Output);
}
-#endif // EXTRA_VERIFY
+ Output.EndObject(); // content
+
+ Output.BeginObject("localFolderState"sv);
+ {
+ SaveFolderContentToCompactBinary(SaveState.FolderState, Output);
+ }
+ Output.EndObject(); // localFolderState
}
-void
-ReadStateObject(CbObjectView StateView,
- Oid& OutBuildId,
- std::vector<Oid>& BuildPartsIds,
- std::vector<std::string>& BuildPartsNames,
- std::vector<ChunkedFolderContent>& OutPartContents,
- FolderContent& OutLocalFolderState)
+BuildSaveState
+BuildSaveState::Read(CbObjectView& Input)
{
- CbObjectView BuildView = StateView["builds"sv].AsArrayView().CreateViewIterator().AsObjectView();
- OutBuildId = BuildView["buildId"sv].AsObjectId();
- for (CbFieldView PartView : BuildView["parts"sv].AsArrayView())
+ BuildState State = BuildState::Read(Input);
+ CbObjectView LocalFolderStateObject = Input["localFolderState"sv].AsObjectView();
+ FolderContent FolderState = LoadFolderContentToCompactBinary(LocalFolderStateObject);
+ std::filesystem::path LocalPath = Input["path"sv].AsU8String();
+ if (LocalPath.empty())
{
- 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()));
+ throw std::runtime_error("BuildSaveState is invalid, 'path' field is empty");
}
- OutLocalFolderState = LoadFolderContentToCompactBinary(StateView["localFolderState"sv].AsObjectView());
+
+ return BuildSaveState{.State = std::move(State), .FolderState = std::move(FolderState), .LocalPath = std::move(LocalPath)};
}
-void
-ReadStateFile(const std::filesystem::path& StateFilePath, FolderContent& OutLocalFolderState, ChunkedFolderContent& OutLocalContent)
+
+CbObject
+CreateBuildSaveStateObject(const BuildSaveState& SaveState)
+{
+ CbObjectWriter CurrentStateWriter;
+ BuildSaveState::Write(SaveState, CurrentStateWriter);
+ return CurrentStateWriter.Save();
+}
+
+BuildSaveState
+ReadBuildSaveStateFile(const std::filesystem::path& StateFilePath)
{
ZEN_TRACE_CPU("ReadStateFile");
@@ -329,28 +214,11 @@ ReadStateFile(const std::filesystem::path& StateFilePath, FolderContent& OutLoca
{
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));
- }
- }
+ return BuildSaveState::Read(CurrentStateObject);
+ }
+ else
+ {
+ throw std::runtime_error(fmt::format("Compact binary object read from '{}' is empty", StateFilePath));
}
}
else
@@ -360,48 +228,61 @@ ReadStateFile(const std::filesystem::path& StateFilePath, FolderContent& OutLoca
}
}
+///////////////////////// BuildsDownloadInfo
+
+void
+BuildsDownloadInfo::Write(const BuildsDownloadInfo& Info, CbWriter& Output)
+{
+ ZEN_ASSERT(!Info.LocalPath.empty());
+ ZEN_ASSERT(!Info.StateFilePath.empty());
+ ZEN_ASSERT(!Info.Iso8601Date.empty());
+
+ Output.AddString("path", (const char*)Info.LocalPath.u8string().c_str());
+ Output.AddString("statePath", (const char*)Info.StateFilePath.u8string().c_str());
+ Output.AddString("date", Info.Iso8601Date);
+
+ BuildsSelection::Write(Info.Selection, Output);
+}
+
+BuildsDownloadInfo
+BuildsDownloadInfo::Read(CbObjectView& Input)
+{
+ BuildsSelection Selection = BuildsSelection::Read(Input, /*OptionalOutLegacyPartsContent*/ nullptr);
+
+ std::filesystem::path LocalPath = Input["path"sv].AsU8String();
+ if (LocalPath.empty())
+ {
+ throw std::runtime_error("BuildsDownloadInfo is invalid, 'path' field is empty");
+ }
+ std::filesystem::path StateFilePath = Input["statePath"sv].AsU8String();
+ if (StateFilePath.empty())
+ {
+ throw std::runtime_error("BuildsDownloadInfo is invalid, 'statePath' field is empty");
+ }
+ std::string_view Iso8601Date = Input["date"sv].AsString();
+
+ return BuildsDownloadInfo{.Selection = std::move(Selection),
+ .LocalPath = std::move(LocalPath),
+ .StateFilePath = std::move(StateFilePath),
+ .Iso8601Date = std::string(Iso8601Date)};
+}
+
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)
+AddDownloadedPath(const std::filesystem::path& SystemRootDir, const BuildsDownloadInfo& Info)
{
ZEN_TRACE_CPU("AddDownloadedPath");
ZEN_ASSERT(!SystemRootDir.empty());
- ZEN_ASSERT(!StateFilePath.empty());
- ZEN_ASSERT(!LocalPath.empty());
- const std::u8string LocalPathString = LocalPath.generic_u8string();
+ ZEN_ASSERT(!Info.StateFilePath.empty());
+ ZEN_ASSERT(!Info.LocalPath.empty());
+ const std::u8string LocalPathString = Info.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
+ BuildsDownloadInfo::Write(Info, Writer);
CbObject Payload = Writer.Save();
ExtendableStringBuilder<512> SB;
@@ -410,6 +291,21 @@ AddDownloadedPath(const std::filesystem::path& SystemRootDir,
TemporaryFile::SafeWriteFile(WritePath, JsonPayload);
}
+BuildsDownloadInfo
+ReadDownloadedInfoFile(const std::filesystem::path& DownloadInfoPath)
+{
+ IoBuffer MetaDataJson = ReadFile(DownloadInfoPath).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())
+ {
+ throw std::runtime_error(fmt::format("Invalid download state file at {}. '{}'", DownloadInfoPath, JsonError));
+ }
+
+ return BuildsDownloadInfo::Read(DownloadInfo);
+}
+
std::vector<std::filesystem::path>
GetDownloadedStatePaths(const std::filesystem::path& SystemRootDir)
{
@@ -436,4 +332,365 @@ GetDownloadedStatePaths(const std::filesystem::path& SystemRootDir)
return Result;
}
+#if ZEN_WITH_TESTS
+
+void
+buildsavedstate_forcelink()
+{
+}
+
+namespace buildsavestate_test {
+ bool Equal(const BuildsSelection& Lhs, const BuildsSelection& Rhs)
+ {
+ if (Lhs.Builds.size() != Rhs.Builds.size())
+ {
+ return false;
+ }
+ for (size_t BuildIndex = 0; BuildIndex < Lhs.Builds.size(); BuildIndex++)
+ {
+ const BuildsSelection::Build LhsBuild = Lhs.Builds[BuildIndex];
+ const BuildsSelection::Build RhsBuild = Rhs.Builds[BuildIndex];
+ if (LhsBuild.Id != RhsBuild.Id)
+ {
+ return false;
+ }
+ if (LhsBuild.Parts.size() != RhsBuild.Parts.size())
+ {
+ return false;
+ }
+ for (size_t PartIndex = 0; PartIndex < LhsBuild.Parts.size(); PartIndex++)
+ {
+ const BuildsSelection::BuildPart LhsPart = LhsBuild.Parts[PartIndex];
+ const BuildsSelection::BuildPart RhsPart = RhsBuild.Parts[PartIndex];
+ if (LhsPart.Id != RhsPart.Id)
+ {
+ return false;
+ }
+ if (LhsPart.Name != RhsPart.Name)
+ {
+ return false;
+ }
+ }
+ if (LhsBuild.IncludeWildcards != RhsBuild.IncludeWildcards)
+ {
+ return false;
+ }
+ if (LhsBuild.ExcludeWildcards != RhsBuild.ExcludeWildcards)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool Equal(const ChunkedContentData& Lhs, const ChunkedContentData& Rhs)
+ {
+ if (Lhs.SequenceRawHashes != Rhs.SequenceRawHashes)
+ {
+ return false;
+ }
+ if (Lhs.ChunkCounts != Rhs.ChunkCounts)
+ {
+ return false;
+ }
+ if (Lhs.ChunkOrders != Rhs.ChunkOrders)
+ {
+ return false;
+ }
+ if (Lhs.ChunkHashes != Rhs.ChunkHashes)
+ {
+ return false;
+ }
+ if (Lhs.ChunkRawSizes != Rhs.ChunkRawSizes)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ bool Equal(const ChunkedFolderContent& Lhs, const ChunkedFolderContent& Rhs)
+ {
+ if (Lhs.Platform != Rhs.Platform)
+ {
+ return false;
+ }
+ if (Lhs.Paths != Rhs.Paths)
+ {
+ return false;
+ }
+ if (Lhs.RawSizes != Rhs.RawSizes)
+ {
+ return false;
+ }
+ if (Lhs.Attributes != Rhs.Attributes)
+ {
+ return false;
+ }
+ if (Lhs.RawHashes != Rhs.RawHashes)
+ {
+ return false;
+ }
+ return Equal(Lhs.ChunkedContent, Rhs.ChunkedContent);
+ }
+
+ bool Equal(const BuildState& Lhs, const BuildState& Rhs)
+ {
+ if (!Equal(Lhs.Selection, Rhs.Selection))
+ {
+ return false;
+ }
+ if (!Equal(Lhs.ChunkedContent, Rhs.ChunkedContent))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ bool Equal(const FolderContent& Lhs, const FolderContent& Rhs)
+ {
+ if (Lhs.Platform != Rhs.Platform)
+ {
+ return false;
+ }
+ if (Lhs.Paths != Rhs.Paths)
+ {
+ return false;
+ }
+ if (Lhs.RawSizes != Rhs.RawSizes)
+ {
+ return false;
+ }
+ if (Lhs.Attributes != Rhs.Attributes)
+ {
+ return false;
+ }
+ if (Lhs.ModificationTicks != Rhs.ModificationTicks)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ bool Equal(const BuildSaveState& Lhs, const BuildSaveState& Rhs)
+ {
+ if (!Equal(Lhs.State, Rhs.State))
+ {
+ return false;
+ }
+ if (!Equal(Lhs.FolderState, Rhs.FolderState))
+ {
+ return false;
+ }
+ if (Lhs.LocalPath != Rhs.LocalPath)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ bool Equal(const BuildsDownloadInfo& Lhs, const BuildsDownloadInfo& Rhs)
+ {
+ if (!Equal(Lhs.Selection, Rhs.Selection))
+ {
+ return false;
+ }
+ if (Lhs.LocalPath != Rhs.LocalPath)
+ {
+ return false;
+ }
+ if (Lhs.StateFilePath != Rhs.StateFilePath)
+ {
+ return false;
+ }
+ if (Lhs.Iso8601Date != Rhs.Iso8601Date)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ BuildsSelection MakeBuildsSelectionA()
+ {
+ return BuildsSelection{
+ .Builds = std::vector<BuildsSelection::Build>{
+ BuildsSelection::Build{
+ .Id = Oid::NewOid(),
+ .Parts = std::vector<BuildsSelection::BuildPart>{BuildsSelection::BuildPart{.Id = Oid::NewOid(), .Name = "default"}}},
+ BuildsSelection::Build{
+ .Id = Oid::NewOid(),
+ .Parts =
+ std::vector<BuildsSelection::BuildPart>{BuildsSelection::BuildPart{.Id = Oid::NewOid(), .Name = "first_part"},
+ BuildsSelection::BuildPart{.Id = Oid::NewOid(), .Name = "second_part"}},
+ .IncludeWildcards = std::vector<std::string>{std::string{"*.exe"}, std::string{"*.dll"}},
+ .ExcludeWildcards = std::vector<std::string>{std::string{"*.pdb"}}},
+ BuildsSelection::Build{
+ .Id = Oid::NewOid(),
+ .Parts = std::vector<BuildsSelection::BuildPart>{BuildsSelection::BuildPart{.Id = Oid::NewOid(), .Name = "default"}},
+ .IncludeWildcards = std::vector<std::string>{std::string{"*.ucas"}, std::string{"*.utok"}},
+ .ExcludeWildcards = std::vector<std::string>{std::string{"*.pak"}}}}};
+ }
+
+ BuildsSelection MakeBuildsSelectionB()
+ {
+ return BuildsSelection{
+ .Builds = std::vector<BuildsSelection::Build>{
+ BuildsSelection::Build{
+ .Id = Oid::NewOid(),
+ .Parts = std::vector<BuildsSelection::BuildPart>{BuildsSelection::BuildPart{.Id = Oid::NewOid(), .Name = "default"}}},
+ BuildsSelection::Build{
+ .Id = Oid::NewOid(),
+ .Parts = std::vector<BuildsSelection::BuildPart>{BuildsSelection::BuildPart{.Id = Oid::NewOid(), .Name = "default"}},
+ .IncludeWildcards = std::vector<std::string>{std::string{"*.exe"}, std::string{"*.dll"}},
+ .ExcludeWildcards = std::vector<std::string>{std::string{"*.pdb"}, std::string{"*.xSYM"}}}}};
+ }
+
+ BuildState MakeBuildStateA(FastRandom& Random)
+ {
+ BuildsSelection Selection = MakeBuildsSelectionA();
+ std::vector<IoBuffer> Chunks;
+
+ ChunkedFolderContent Content = chunkedcontent_testutils::CreateChunkedFolderContent(
+ Random,
+ std::vector<std::pair<const std::string, uint64_t>>{
+ std::pair<std::string, uint64_t>{"file_1", 6u * 1024u},
+ std::pair<std::string, uint64_t>{"file_2.exe", 0},
+ std::pair<std::string, uint64_t>{"file_3.txt", 798},
+ std::pair<std::string, uint64_t>{"dir_1/dir1_file_1.exe", 19u * 1024u},
+ std::pair<std::string, uint64_t>{"dir_1/dir1_file_2.pdb", 7u * 1024u},
+ std::pair<std::string, uint64_t>{"dir_1/dir1_file_3.txt", 93},
+ std::pair<std::string, uint64_t>{"dir_2/dir2_dir1/dir2_dir1_file_1.exe", 31u * 1024u},
+ std::pair<std::string, uint64_t>{"dir_2/dir2_dir1/dir2_dir1_file_2.pdb", 17u * 1024u},
+ std::pair<std::string, uint64_t>{"dir_2/dir2_dir1/dir2_dir1_file_3.dll", 13u * 1024u},
+ std::pair<std::string, uint64_t>{"dir_2/dir2_dir2/dir2_dir2_file_1.txt", 2u * 1024u},
+ std::pair<std::string, uint64_t>{"dir_2/dir2_dir2/dir2_dir2_file_2.json", 3u * 1024u}},
+ 4u * 1024u,
+ Chunks);
+ return BuildState{.Selection = std::move(Selection), .ChunkedContent = std::move(Content)};
+ }
+
+ FolderContent MakeFolderContent(ChunkedFolderContent& Content)
+ {
+ FolderContent Result = {.Platform = Content.Platform};
+ Result.Paths = Content.Paths;
+ Result.RawSizes = Content.RawSizes;
+ Result.Attributes = Content.Attributes;
+ Result.ModificationTicks.resize(Result.Paths.size(), 0);
+ uint64_t AttributeIndex = 0;
+ for (uint64_t& ModificationTick : Result.ModificationTicks)
+ {
+ ModificationTick = ++AttributeIndex;
+ }
+ return Result;
+ }
+} // namespace buildsavestate_test
+
+TEST_CASE("buildsavestate.BuildsSelection")
+{
+ using namespace buildsavestate_test;
+
+ const BuildsSelection Selection = MakeBuildsSelectionA();
+
+ CbObjectWriter Writer;
+ BuildsSelection::Write(Selection, Writer);
+ CbObject SavedObject = Writer.Save();
+
+ BuildsSelection ReadbackSelection = BuildsSelection::Read(SavedObject, nullptr);
+
+ CHECK(Equal(Selection, ReadbackSelection));
+}
+
+TEST_CASE("buildsavestate.BuildsState")
+{
+ using namespace buildsavestate_test;
+
+ FastRandom Random;
+
+ BuildState State = MakeBuildStateA(Random);
+
+ CbObjectWriter Writer;
+ BuildState::Write(State, Writer);
+ CbObject SavedObject = Writer.Save();
+
+ BuildState ReadbackState = BuildState::Read(SavedObject);
+
+ CHECK(Equal(State, ReadbackState));
+}
+
+TEST_CASE("buildsavestate.BuildsSaveState")
+{
+ using namespace buildsavestate_test;
+
+ FastRandom Random;
+
+ BuildState State = MakeBuildStateA(Random);
+ FolderContent FolderState = MakeFolderContent(State.ChunkedContent);
+ BuildSaveState SaveState = {.State = std::move(State),
+ .FolderState = std::move(FolderState),
+ .LocalPath = "\\\\?\\E:\\temp\\localpath"};
+
+ CbObjectWriter Writer;
+ BuildSaveState::Write(SaveState, Writer);
+ CbObject SavedObject = Writer.Save();
+
+ BuildSaveState ReadbackSavedState = BuildSaveState::Read(SavedObject);
+
+ CHECK(Equal(SaveState, ReadbackSavedState));
+}
+
+TEST_CASE("buildsavestate.BuildsDownloadInfo")
+{
+ using namespace buildsavestate_test;
+
+ BuildsDownloadInfo DownloadInfo = {.Selection = MakeBuildsSelectionA(),
+ .LocalPath = "\\\\?\\E:\\temp\\localpath",
+ .StateFilePath = "\\\\?\\E:\\temp\\localpath\\.zen\\state.cbo",
+ .Iso8601Date = DateTime::Now().ToIso8601()};
+
+ CbObjectWriter Writer;
+ BuildsDownloadInfo::Write(DownloadInfo, Writer);
+ CbObject SavedObject = Writer.Save();
+
+ BuildsDownloadInfo ReadbackDownloadInfo = BuildsDownloadInfo::Read(SavedObject);
+
+ CHECK(Equal(DownloadInfo, ReadbackDownloadInfo));
+}
+
+TEST_CASE("buildsavestate.DownloadedPaths")
+{
+ using namespace buildsavestate_test;
+
+ ScopedTemporaryDirectory SystemDir;
+
+ BuildsDownloadInfo DownloadInfoA = {.Selection = MakeBuildsSelectionA(),
+ .LocalPath = "\\\\?\\E:\\temp\\localpathA",
+ .StateFilePath = "\\\\?\\E:\\temp\\localpathA\\.zen\\state.cbo",
+ .Iso8601Date = DateTime(DateTime::Now().GetTicks() - 200).ToIso8601()};
+
+ BuildsDownloadInfo DownloadInfoB = {.Selection = MakeBuildsSelectionB(),
+ .LocalPath = "\\\\?\\E:\\temp\\localpathB",
+ .StateFilePath = "\\\\?\\E:\\temp\\localpathB\\.zen\\state.cbo",
+ .Iso8601Date = DateTime(DateTime::Now().GetTicks() - 100).ToIso8601()};
+
+ AddDownloadedPath(SystemDir.Path(), DownloadInfoA);
+ AddDownloadedPath(SystemDir.Path(), DownloadInfoB);
+
+ std::vector<std::filesystem::path> DownloadedPaths = GetDownloadedStatePaths(SystemDir.Path());
+
+ CHECK_EQ(2u, DownloadedPaths.size());
+ BuildsDownloadInfo ReadBackDownloadInfo0 = ReadDownloadedInfoFile(DownloadedPaths[0]);
+ BuildsDownloadInfo ReadBackDownloadInfo1 = ReadDownloadedInfoFile(DownloadedPaths[1]);
+
+ if (DownloadInfoA.LocalPath == ReadBackDownloadInfo0.LocalPath)
+ {
+ CHECK(Equal(DownloadInfoA, ReadBackDownloadInfo0));
+ CHECK(Equal(DownloadInfoB, ReadBackDownloadInfo1));
+ }
+ else
+ {
+ CHECK(Equal(DownloadInfoA, ReadBackDownloadInfo1));
+ CHECK(Equal(DownloadInfoB, ReadBackDownloadInfo0));
+ }
+}
+
+#endif // ZEN_WITH_TESTS
+
} // namespace zen
diff --git a/src/zenremotestore/builds/buildstorageoperations.cpp b/src/zenremotestore/builds/buildstorageoperations.cpp
index 798ef4dae..92017e98d 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/buildcontent.h>
#include <zenremotestore/builds/buildsavedstate.h>
#include <zenremotestore/builds/buildstorage.h>
#include <zenremotestore/builds/buildstoragecache.h>
@@ -30,8 +31,6 @@ 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;
@@ -1104,6 +1103,14 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState)
NeededBlockChunkIndexes.push_back(ChunkBlockIndex);
}
}
+ else
+ {
+ ZEN_ASSERT(!RemoteChunkIndexNeedsCopyFromSourceFlags[RemoteChunkIndex]);
+ }
+ }
+ else
+ {
+ ZEN_DEBUG("Chunk {} not found in block {}", ChunkHash, BlockDescription.BlockHash);
}
}
return NeededBlockChunkIndexes;
@@ -1973,9 +1980,6 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState)
if (!m_Options.PrimeCacheOnly)
{
- ZEN_ASSERT(m_WrittenChunkByteCount == BytesToWrite);
- ZEN_ASSERT(m_ValidatedChunkByteCount == BytesToValidate);
-
uint32_t RawSequencesMissingWriteCount = 0;
for (uint32_t SequenceIndex = 0; SequenceIndex < SequenceIndexChunksLeftToWriteCounters.size(); SequenceIndex++)
{
@@ -1999,6 +2003,8 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState)
}
}
ZEN_ASSERT(RawSequencesMissingWriteCount == 0);
+ ZEN_ASSERT(m_WrittenChunkByteCount == BytesToWrite);
+ ZEN_ASSERT(m_ValidatedChunkByteCount == BytesToValidate);
}
const uint64_t DownloadedBytes = m_DownloadStats.DownloadedChunkByteCount.load() +
@@ -2702,46 +2708,43 @@ BuildsOperationUpdateFolder::FindScavengeSources()
std::vector<ScavengeSource> Result;
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())
+ if (IsFile(EntryPath))
{
- std::filesystem::path StateFilePath = DownloadInfo["statePath"].AsU8String();
- if (IsFile(StateFilePath))
+ bool DeleteEntry = false;
+
+ try
{
- std::filesystem::path Path = DownloadInfo["path"].AsU8String();
- if (!std::filesystem::equivalent(Path, m_Path))
+ BuildsDownloadInfo Info = ReadDownloadedInfoFile(EntryPath);
+ if (!Info.LocalPath.empty())
{
- if (IsDir(Path))
- {
- Result.push_back({.StateFilePath = std::move(StateFilePath), .Path = std::move(Path)});
- }
- else
+ if (!std::filesystem::equivalent(Info.LocalPath, m_Path))
{
- DeleteEntry = true;
+ if (IsDir(Info.LocalPath) && IsFile(Info.StateFilePath))
+ {
+ Result.push_back({.StateFilePath = std::move(Info.StateFilePath), .Path = std::move(Info.LocalPath)});
+ }
+ else
+ {
+ DeleteEntry = true;
+ }
}
}
+ else
+ {
+ DeleteEntry = true;
+ }
}
- else
+ catch (const std::exception& Ex)
{
+ ZEN_OPERATION_LOG_WARN(m_LogOutput, "{}", Ex.what());
DeleteEntry = true;
}
- }
- else
- {
- ZEN_OPERATION_LOG_WARN(m_LogOutput, "Invalid download state file at {}. '{}'", EntryPath, JsonError);
- DeleteEntry = true;
- }
- if (DeleteEntry)
- {
- std::error_code DummyEc;
- std::filesystem::remove(EntryPath, DummyEc);
+ if (DeleteEntry)
+ {
+ std::error_code DummyEc;
+ std::filesystem::remove(EntryPath, DummyEc);
+ }
}
}
return Result;
@@ -2828,7 +2831,9 @@ BuildsOperationUpdateFolder::FindScavengeContent(const ScavengeSource& Source,
FolderContent LocalFolderState;
try
{
- ReadStateFile(Source.StateFilePath, LocalFolderState, OutScavengedLocalContent);
+ BuildSaveState SavedState = ReadBuildSaveStateFile(Source.StateFilePath);
+ OutScavengedLocalContent = std::move(SavedState.State.ChunkedContent);
+ LocalFolderState = std::move(SavedState.FolderState);
}
catch (const std::exception& Ex)
{
@@ -5127,51 +5132,53 @@ BuildsOperationUploadFolder::Execute()
{
AllChunkBlockHashes.push_back(BlockDescription.BlockHash);
}
-#if EXTRA_VERIFY
- tsl::robin_map<IoHash, size_t, IoHash::Hasher> ChunkHashToAbsoluteChunkIndex;
- std::vector<IoHash> AbsoluteChunkHashes;
- AbsoluteChunkHashes.reserve(LocalContent.ChunkedContent.ChunkHashes.size());
- for (uint32_t ChunkIndex : LooseChunkIndexes)
+ std::vector<IoHash> AbsoluteChunkHashes;
+ if (m_Options.DoExtraContentValidation)
{
- ChunkHashToAbsoluteChunkIndex.insert({LocalContent.ChunkedContent.ChunkHashes[ChunkIndex], AbsoluteChunkHashes.size()});
- AbsoluteChunkHashes.push_back(LocalContent.ChunkedContent.ChunkHashes[ChunkIndex]);
- }
- for (const ChunkBlockDescription& Block : AllChunkBlockDescriptions)
- {
- for (const IoHash& ChunkHash : Block.ChunkRawHashes)
+ tsl::robin_map<IoHash, size_t, IoHash::Hasher> ChunkHashToAbsoluteChunkIndex;
+ AbsoluteChunkHashes.reserve(LocalContent.ChunkedContent.ChunkHashes.size());
+ for (uint32_t ChunkIndex : LooseChunkIndexes)
{
- ChunkHashToAbsoluteChunkIndex.insert({ChunkHash, AbsoluteChunkHashes.size()});
- AbsoluteChunkHashes.push_back(ChunkHash);
+ ChunkHashToAbsoluteChunkIndex.insert({LocalContent.ChunkedContent.ChunkHashes[ChunkIndex], AbsoluteChunkHashes.size()});
+ AbsoluteChunkHashes.push_back(LocalContent.ChunkedContent.ChunkHashes[ChunkIndex]);
+ }
+ for (const ChunkBlockDescription& Block : AllChunkBlockDescriptions)
+ {
+ for (const IoHash& ChunkHash : Block.ChunkRawHashes)
+ {
+ ChunkHashToAbsoluteChunkIndex.insert({ChunkHash, AbsoluteChunkHashes.size()});
+ AbsoluteChunkHashes.push_back(ChunkHash);
+ }
+ }
+ for (const IoHash& ChunkHash : LocalContent.ChunkedContent.ChunkHashes)
+ {
+ ZEN_ASSERT(AbsoluteChunkHashes[ChunkHashToAbsoluteChunkIndex.at(ChunkHash)] == ChunkHash);
+ ZEN_ASSERT(LocalContent.ChunkedContent.ChunkHashes[LocalLookup.ChunkHashToChunkIndex.at(ChunkHash)] == ChunkHash);
+ }
+ for (const uint32_t ChunkIndex : LocalContent.ChunkedContent.ChunkOrders)
+ {
+ ZEN_ASSERT(AbsoluteChunkHashes[ChunkHashToAbsoluteChunkIndex.at(LocalContent.ChunkedContent.ChunkHashes[ChunkIndex])] ==
+ LocalContent.ChunkedContent.ChunkHashes[ChunkIndex]);
+ ZEN_ASSERT(LocalLookup.ChunkHashToChunkIndex.at(LocalContent.ChunkedContent.ChunkHashes[ChunkIndex]) == ChunkIndex);
}
}
- for (const IoHash& ChunkHash : LocalContent.ChunkedContent.ChunkHashes)
- {
- ZEN_ASSERT(AbsoluteChunkHashes[ChunkHashToAbsoluteChunkIndex.at(ChunkHash)] == ChunkHash);
- ZEN_ASSERT(LocalContent.ChunkedContent.ChunkHashes[LocalLookup.ChunkHashToChunkIndex.at(ChunkHash)] == ChunkHash);
- }
- for (const uint32_t ChunkIndex : LocalContent.ChunkedContent.ChunkOrders)
- {
- ZEN_ASSERT(AbsoluteChunkHashes[ChunkHashToAbsoluteChunkIndex.at(LocalContent.ChunkedContent.ChunkHashes[ChunkIndex])] ==
- LocalContent.ChunkedContent.ChunkHashes[ChunkIndex]);
- ZEN_ASSERT(LocalLookup.ChunkHashToChunkIndex.at(LocalContent.ChunkedContent.ChunkHashes[ChunkIndex]) == ChunkIndex);
- }
-#endif // EXTRA_VERIFY
std::vector<uint32_t> AbsoluteChunkOrders = CalculateAbsoluteChunkOrders(LocalContent.ChunkedContent.ChunkHashes,
LocalContent.ChunkedContent.ChunkOrders,
LocalLookup.ChunkHashToChunkIndex,
LooseChunkIndexes,
AllChunkBlockDescriptions);
-#if EXTRA_VERIFY
- for (uint32_t ChunkOrderIndex = 0; ChunkOrderIndex < LocalContent.ChunkedContent.ChunkOrders.size(); ChunkOrderIndex++)
+ if (m_Options.DoExtraContentValidation)
{
- uint32_t LocalChunkIndex = LocalContent.ChunkedContent.ChunkOrders[ChunkOrderIndex];
- uint32_t AbsoluteChunkIndex = AbsoluteChunkOrders[ChunkOrderIndex];
- const IoHash& LocalChunkHash = LocalContent.ChunkedContent.ChunkHashes[LocalChunkIndex];
- const IoHash& AbsoluteChunkHash = AbsoluteChunkHashes[AbsoluteChunkIndex];
- ZEN_ASSERT(LocalChunkHash == AbsoluteChunkHash);
+ for (uint32_t ChunkOrderIndex = 0; ChunkOrderIndex < LocalContent.ChunkedContent.ChunkOrders.size(); ChunkOrderIndex++)
+ {
+ uint32_t LocalChunkIndex = LocalContent.ChunkedContent.ChunkOrders[ChunkOrderIndex];
+ uint32_t AbsoluteChunkIndex = AbsoluteChunkOrders[ChunkOrderIndex];
+ const IoHash& LocalChunkHash = LocalContent.ChunkedContent.ChunkHashes[LocalChunkIndex];
+ const IoHash& AbsoluteChunkHash = AbsoluteChunkHashes[AbsoluteChunkIndex];
+ ZEN_ASSERT(LocalChunkHash == AbsoluteChunkHash);
+ }
}
-#endif // EXTRA_VERIFY
WriteBuildContentToCompactBinary(PartManifestWriter,
LocalContent.Platform,
@@ -5187,7 +5194,7 @@ BuildsOperationUploadFolder::Execute()
LooseChunkIndexes,
AllChunkBlockHashes);
-#if EXTRA_VERIFY
+ if (m_Options.DoExtraContentValidation)
{
ChunkedFolderContent VerifyFolderContent;
@@ -5226,7 +5233,8 @@ BuildsOperationUploadFolder::Execute()
AllChunkBlockDescriptions,
VerifyFolderContent.ChunkedContent.ChunkHashes,
VerifyFolderContent.ChunkedContent.ChunkRawSizes,
- VerifyFolderContent.ChunkedContent.ChunkOrders);
+ VerifyFolderContent.ChunkedContent.ChunkOrders,
+ m_Options.DoExtraContentValidation);
ZEN_ASSERT(LocalContent.Paths == VerifyFolderContent.Paths);
ZEN_ASSERT(LocalContent.RawHashes == VerifyFolderContent.RawHashes);
@@ -5249,7 +5257,6 @@ BuildsOperationUploadFolder::Execute()
ZEN_ASSERT(LocalChunkRawSize == VerifyChunkRawSize);
}
}
-#endif // EXTRA_VERIFY
PartManifest = PartManifestWriter.Save();
}
@@ -6097,19 +6104,21 @@ BuildsOperationUploadFolder::CalculateAbsoluteChunkOrders(
{
ZEN_TRACE_CPU("CalculateAbsoluteChunkOrders");
-#if EXTRA_VERIFY
std::vector<IoHash> TmpAbsoluteChunkHashes;
- TmpAbsoluteChunkHashes.reserve(LocalChunkHashes.size());
-#endif // EXTRA_VERIFY
+ if (m_Options.DoExtraContentValidation)
+ {
+ TmpAbsoluteChunkHashes.reserve(LocalChunkHashes.size());
+ }
std::vector<uint32_t> LocalChunkIndexToAbsoluteChunkIndex;
LocalChunkIndexToAbsoluteChunkIndex.resize(LocalChunkHashes.size(), (uint32_t)-1);
std::uint32_t AbsoluteChunkCount = 0;
for (uint32_t ChunkIndex : LooseChunkIndexes)
{
LocalChunkIndexToAbsoluteChunkIndex[ChunkIndex] = AbsoluteChunkCount;
-#if EXTRA_VERIFY
- TmpAbsoluteChunkHashes.push_back(LocalChunkHashes[ChunkIndex]);
-#endif // EXTRA_VERIFY
+ if (m_Options.DoExtraContentValidation)
+ {
+ TmpAbsoluteChunkHashes.push_back(LocalChunkHashes[ChunkIndex]);
+ }
AbsoluteChunkCount++;
}
for (const ChunkBlockDescription& Block : BlockDescriptions)
@@ -6122,9 +6131,10 @@ BuildsOperationUploadFolder::CalculateAbsoluteChunkOrders(
ZEN_ASSERT_SLOW(LocalChunkHashes[LocalChunkIndex] == ChunkHash);
LocalChunkIndexToAbsoluteChunkIndex[LocalChunkIndex] = AbsoluteChunkCount;
}
-#if EXTRA_VERIFY
- TmpAbsoluteChunkHashes.push_back(ChunkHash);
-#endif // EXTRA_VERIFY
+ if (m_Options.DoExtraContentValidation)
+ {
+ TmpAbsoluteChunkHashes.push_back(ChunkHash);
+ }
AbsoluteChunkCount++;
}
}
@@ -6133,12 +6143,13 @@ BuildsOperationUploadFolder::CalculateAbsoluteChunkOrders(
for (const uint32_t LocalChunkIndex : LocalChunkOrder)
{
const uint32_t AbsoluteChunkIndex = LocalChunkIndexToAbsoluteChunkIndex[LocalChunkIndex];
-#if EXTRA_VERIFY
- ZEN_ASSERT(LocalChunkHashes[LocalChunkIndex] == TmpAbsoluteChunkHashes[AbsoluteChunkIndex]);
-#endif // EXTRA_VERIFY
+ if (m_Options.DoExtraContentValidation)
+ {
+ ZEN_ASSERT(LocalChunkHashes[LocalChunkIndex] == TmpAbsoluteChunkHashes[AbsoluteChunkIndex]);
+ }
AbsoluteChunkOrder.push_back(AbsoluteChunkIndex);
}
-#if EXTRA_VERIFY
+ if (m_Options.DoExtraContentValidation)
{
uint32_t OrderIndex = 0;
while (OrderIndex < LocalChunkOrder.size())
@@ -6151,91 +6162,9 @@ BuildsOperationUploadFolder::CalculateAbsoluteChunkOrders(
OrderIndex++;
}
}
-#endif // EXTRA_VERIFY
return AbsoluteChunkOrder;
}
-void
-BuildsOperationUploadFolder::WriteBuildContentToCompactBinary(CbObjectWriter& PartManifestWriter,
- const SourcePlatform Platform,
- std::span<const std::filesystem::path> Paths,
- std::span<const IoHash> RawHashes,
- std::span<const uint64_t> RawSizes,
- std::span<const uint32_t> Attributes,
- std::span<const IoHash> SequenceRawHashes,
- std::span<const uint32_t> ChunkCounts,
- std::span<const IoHash> LocalChunkHashes,
- std::span<const uint64_t> LocalChunkRawSizes,
- const std::vector<uint32_t>& AbsoluteChunkOrders,
- const std::span<const uint32_t> LooseLocalChunkIndexes,
- const std::span<IoHash> BlockHashes)
-{
- ZEN_ASSERT(Platform != SourcePlatform::_Count);
- PartManifestWriter.AddString("platform"sv, ToString(Platform));
-
- uint64_t TotalSize = 0;
- for (const uint64_t Size : RawSizes)
- {
- TotalSize += Size;
- }
- PartManifestWriter.AddInteger("totalSize", TotalSize);
-
- PartManifestWriter.BeginObject("files"sv);
- {
- compactbinary_helpers::WriteArray(Paths, "paths"sv, PartManifestWriter);
- compactbinary_helpers::WriteArray(RawHashes, "rawhashes"sv, PartManifestWriter);
- compactbinary_helpers::WriteArray(RawSizes, "rawsizes"sv, PartManifestWriter);
- if (Platform == SourcePlatform::Windows)
- {
- compactbinary_helpers::WriteArray(Attributes, "attributes"sv, PartManifestWriter);
- }
- if (Platform == SourcePlatform::Linux || Platform == SourcePlatform::MacOS)
- {
- compactbinary_helpers::WriteArray(Attributes, "mode"sv, PartManifestWriter);
- }
- }
- PartManifestWriter.EndObject(); // files
-
- PartManifestWriter.BeginObject("chunkedContent");
- {
- compactbinary_helpers::WriteArray(SequenceRawHashes, "sequenceRawHashes"sv, PartManifestWriter);
- compactbinary_helpers::WriteArray(ChunkCounts, "chunkcounts"sv, PartManifestWriter);
- compactbinary_helpers::WriteArray(AbsoluteChunkOrders, "chunkorders"sv, PartManifestWriter);
- }
- PartManifestWriter.EndObject(); // chunkedContent
-
- size_t LooseChunkCount = LooseLocalChunkIndexes.size();
- if (LooseChunkCount > 0)
- {
- PartManifestWriter.BeginObject("chunkAttachments");
- {
- PartManifestWriter.BeginArray("rawHashes"sv);
- for (uint32_t ChunkIndex : LooseLocalChunkIndexes)
- {
- PartManifestWriter.AddBinaryAttachment(LocalChunkHashes[ChunkIndex]);
- }
- PartManifestWriter.EndArray(); // rawHashes
-
- PartManifestWriter.BeginArray("chunkRawSizes"sv);
- for (uint32_t ChunkIndex : LooseLocalChunkIndexes)
- {
- PartManifestWriter.AddInteger(LocalChunkRawSizes[ChunkIndex]);
- }
- PartManifestWriter.EndArray(); // chunkSizes
- }
- PartManifestWriter.EndObject(); //
- }
-
- if (BlockHashes.size() > 0)
- {
- PartManifestWriter.BeginObject("blockAttachments");
- {
- compactbinary_helpers::WriteBinaryAttachmentArray(BlockHashes, "rawHashes"sv, PartManifestWriter);
- }
- PartManifestWriter.EndObject(); // blocks
- }
-}
-
CompositeBuffer
BuildsOperationUploadFolder::FetchChunk(const ChunkedFolderContent& Content,
const ChunkedContentLookup& Lookup,