aboutsummaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/zen/cmds/builds_cmd.cpp886
-rw-r--r--src/zen/cmds/builds_cmd.h1
-rw-r--r--src/zenremotestore/builds/buildcontent.cpp253
-rw-r--r--src/zenremotestore/builds/buildsavedstate.cpp845
-rw-r--r--src/zenremotestore/builds/buildstorageoperations.cpp261
-rw-r--r--src/zenremotestore/chunking/chunkedcontent.cpp569
-rw-r--r--src/zenremotestore/include/zenremotestore/builds/buildcontent.h43
-rw-r--r--src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h101
-rw-r--r--src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h26
-rw-r--r--src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h39
-rw-r--r--src/zenremotestore/zenremotestore.cpp3
-rw-r--r--src/zenutil/include/zenutil/wildcard.h5
-rw-r--r--src/zenutil/wildcard.cpp33
13 files changed, 2037 insertions, 1028 deletions
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp
index fcda6e809..665676fcd 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/buildcontent.h>
#include <zenremotestore/builds/buildsavedstate.h>
#include <zenremotestore/builds/buildstoragecache.h>
#include <zenremotestore/builds/buildstorageoperations.h>
@@ -62,7 +63,7 @@ ZEN_THIRD_PARTY_INCLUDES_END
# include <unistd.h>
#endif
-#define EXTRA_VERIFY 0
+static const bool DoExtraContentVerify = false;
#define ZEN_CLOUD_STORAGE "Cloud Storage"
@@ -267,6 +268,29 @@ namespace {
static bool IsQuiet = false;
static ProgressBar::Mode ProgressMode = ProgressBar::Mode::Pretty;
+ enum EAppendNewContentMode
+ {
+ Replace,
+ Auto,
+ Append,
+ Invalid
+ };
+
+ EAppendNewContentMode AppendNewContentModeFromString(const std::string_view ModeString)
+ {
+ switch (HashStringAsLowerDjb2(ModeString))
+ {
+ case HashStringDjb2("false"):
+ return EAppendNewContentMode::Replace;
+ case HashStringDjb2("auto"):
+ return EAppendNewContentMode::Auto;
+ case HashStringDjb2("true"):
+ return EAppendNewContentMode::Append;
+ default:
+ return EAppendNewContentMode::Invalid;
+ }
+ }
+
#define ZEN_CONSOLE_VERBOSE(fmtstr, ...) \
if (IsVerbose) \
{ \
@@ -292,32 +316,7 @@ namespace {
std::span<const std::string> ExcludeWildcards,
const std::filesystem::path& Path)
{
- const std::string PathString = Path.generic_string();
- bool IncludePath = true;
- if (!IncludeWildcards.empty())
- {
- IncludePath = false;
- for (const std::string& IncludeWildcard : IncludeWildcards)
- {
- if (MatchWildcard(IncludeWildcard, PathString, /*CaseSensitive*/ false))
- {
- IncludePath = true;
- break;
- }
- }
- if (!IncludePath)
- {
- return false;
- }
- }
- for (const std::string& ExcludeWildcard : ExcludeWildcards)
- {
- if (MatchWildcard(ExcludeWildcard, PathString, /*CaseSensitive*/ false))
- {
- return false;
- }
- }
- return true;
+ return zen::IncludePath(IncludeWildcards, ExcludeWildcards, ToLower(Path.generic_string()), /*CaseSensitive*/ true);
}
class FilteredRate
@@ -490,6 +489,7 @@ namespace {
std::move(MetaData),
BuildsOperationUploadFolder::Options{.IsQuiet = IsQuiet,
.IsVerbose = IsVerbose,
+ .DoExtraContentValidation = DoExtraContentVerify,
.FindBlockMaxCount = FindBlockMaxCount,
.BlockReuseMinPercentLimit = BlockReuseMinPercentLimit,
.AllowMultiparts = AllowMultiparts,
@@ -1079,7 +1079,23 @@ namespace {
return SB.ToString();
}
- std::vector<std::pair<Oid, std::string>> ResolveBuildPartNames(BuildStorageBase& Storage,
+ CbObject GetBuild(BuildStorageBase& Storage, const Oid& BuildId)
+ {
+ Stopwatch GetBuildTimer;
+ CbObject BuildObject = Storage.GetBuild(BuildId);
+ if (!IsQuiet)
+ {
+ ZEN_CONSOLE("GetBuild took {}. Name: '{}', Payload size: {}",
+ NiceTimeSpanMs(GetBuildTimer.GetElapsedTimeMs()),
+ BuildObject["name"sv].AsString(),
+ NiceBytes(BuildObject.GetSize()));
+
+ ZEN_CONSOLE("{}", GetCbObjectAsNiceString(BuildObject, " "sv, "\n"sv));
+ }
+ return BuildObject;
+ }
+
+ std::vector<std::pair<Oid, std::string>> ResolveBuildPartNames(CbObjectView BuildObject,
const Oid& BuildId,
const std::vector<Oid>& BuildPartIds,
std::span<const std::string> BuildPartNames,
@@ -1087,18 +1103,6 @@ namespace {
{
std::vector<std::pair<Oid, std::string>> Result;
{
- Stopwatch GetBuildTimer;
- CbObject BuildObject = Storage.GetBuild(BuildId);
- if (!IsQuiet)
- {
- ZEN_CONSOLE("GetBuild took {}. Name: '{}', Payload size: {}",
- NiceTimeSpanMs(GetBuildTimer.GetElapsedTimeMs()),
- BuildObject["name"sv].AsString(),
- NiceBytes(BuildObject.GetSize()));
-
- ZEN_CONSOLE("{}", GetCbObjectAsNiceString(BuildObject, " "sv, "\n"sv));
- }
-
CbObjectView PartsObject = BuildObject["parts"sv].AsObjectView();
if (!PartsObject)
{
@@ -1251,7 +1255,8 @@ namespace {
OutBlockDescriptions,
OutRemoteContent.ChunkedContent.ChunkHashes,
OutRemoteContent.ChunkedContent.ChunkRawSizes,
- OutRemoteContent.ChunkedContent.ChunkOrders);
+ OutRemoteContent.ChunkedContent.ChunkOrders,
+ DoExtraContentVerify);
if (!IncludeWildcards.empty() || !ExcludeWildcards.empty())
{
@@ -1272,7 +1277,7 @@ namespace {
}
#if ZEN_BUILD_DEBUG
- ValidateChunkedFolderContent(OutRemoteContent, OutBlockDescriptions, OutLooseChunkHashes);
+ ValidateChunkedFolderContent(OutRemoteContent, OutBlockDescriptions, OutLooseChunkHashes, IncludeWildcards, ExcludeWildcards);
#endif // ZEN_BUILD_DEBUG
};
@@ -1364,276 +1369,60 @@ namespace {
return RemoteContent;
}
- ChunkedFolderContent GetLocalContent(ThreadWorkers& Workers,
- GetFolderContentStatistics& LocalFolderScanStats,
- ChunkingStatistics& ChunkingStats,
- const std::filesystem::path& Path,
- const std::filesystem::path& StateFilePath,
- ChunkingController& ChunkController,
- std::span<const std::filesystem::path> ReferencePaths,
- std::span<const std::string> IncludeWildcards,
- std::span<const std::string> ExcludeWildcards,
- FolderContent& OutLocalFolderContent)
+ std::vector<std::filesystem::path> GetNewPaths(const std::span<const std::filesystem::path> KnownPaths,
+ const std::span<const std::filesystem::path> Paths)
{
- FolderContent LocalFolderState;
- ChunkedFolderContent LocalContent;
-
- Stopwatch ReadStateTimer;
- bool HasLocalState = false;
- bool FileExists = IsFile(StateFilePath);
- if (FileExists)
+ tsl::robin_set<std::string> KnownPathsSet;
+ KnownPathsSet.reserve(KnownPaths.size());
+ for (const std::filesystem::path& LocalPath : KnownPaths)
{
- 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());
- }
+ KnownPathsSet.insert(LocalPath.generic_string());
}
+ std::vector<std::filesystem::path> NewPaths;
+ for (const std::filesystem::path& UntrackedPath : Paths)
{
- const uint32_t LocalPathCount = gsl::narrow<uint32_t>(LocalFolderState.Paths.size());
- const uint32_t RemotePathCount = gsl::narrow<uint32_t>(ReferencePaths.size());
-
- std::vector<std::filesystem::path> PathsToCheck;
- PathsToCheck.reserve(LocalPathCount + RemotePathCount);
-
- tsl::robin_set<std::string> FileSet;
- FileSet.reserve(LocalPathCount + RemotePathCount);
-
- for (const std::filesystem::path& LocalPath : LocalFolderState.Paths)
- {
- if (IncludePath(IncludeWildcards, ExcludeWildcards, LocalPath))
- {
- FileSet.insert(LocalPath.generic_string());
- PathsToCheck.push_back(LocalPath);
- }
- }
-
- for (const std::filesystem::path& RemotePath : ReferencePaths)
- {
- if (IncludePath(IncludeWildcards, ExcludeWildcards, RemotePath))
- {
- if (FileSet.insert(RemotePath.generic_string()).second)
- {
- PathsToCheck.push_back(RemotePath);
- }
- }
- }
-
+ if (!KnownPathsSet.contains(UntrackedPath.generic_string()))
{
- ProgressBar ProgressBar(ProgressMode, "Check Files");
- OutLocalFolderContent =
- GetValidFolderContent(Workers,
- LocalFolderScanStats,
- Path,
- PathsToCheck,
- [&ProgressBar, &LocalFolderScanStats](uint64_t PathCount, uint64_t CompletedPathCount) {
- std::string Details = fmt::format("{}/{} checked, {} found",
- CompletedPathCount,
- PathCount,
- LocalFolderScanStats.FoundFileCount.load());
- ProgressBar.UpdateState({.Task = "Checking files ",
- .Details = Details,
- .TotalCount = PathCount,
- .RemainingCount = PathCount - CompletedPathCount,
- .Status = ProgressBar::State::CalculateStatus(AbortFlag, PauseFlag)},
- false);
- });
- ProgressBar.Finish();
- }
- if (AbortFlag)
- {
- return {};
+ NewPaths.push_back(UntrackedPath);
}
}
+ return NewPaths;
+ }
- bool ScanContent = true;
- std::vector<uint32_t> PathIndexesOufOfDate;
- if (HasLocalState)
+ BuildSaveState GetLocalStateFromPaths(ThreadWorkers& Workers,
+ GetFolderContentStatistics& LocalFolderScanStats,
+ ChunkingStatistics& ChunkingStats,
+ const std::filesystem::path& Path,
+ ChunkingController& ChunkController,
+ std::span<const std::filesystem::path> PathsToCheck)
+ {
+ FolderContent FolderState;
+ ChunkedFolderContent ChunkedContent;
{
- if (!LocalFolderState.AreKnownFilesEqual(OutLocalFolderContent))
- {
- const size_t LocalStatePathCount = LocalFolderState.Paths.size();
- std::vector<std::filesystem::path> DeletedPaths;
- FolderContent UpdatedContent = GetUpdatedContent(LocalFolderState, OutLocalFolderContent, DeletedPaths);
- if (!DeletedPaths.empty())
- {
- LocalContent = DeletePathsFromChunkedContent(LocalContent, DeletedPaths);
- }
-
- if (!IsQuiet)
- {
- ZEN_CONSOLE("Updating state, {} local files deleted and {} local files updated out of {}",
- DeletedPaths.size(),
- UpdatedContent.Paths.size(),
- LocalStatePathCount);
- }
- if (UpdatedContent.Paths.size() > 0)
- {
- uint64_t ByteCountToScan = 0;
- for (const uint64_t RawSize : UpdatedContent.RawSizes)
- {
- ByteCountToScan += RawSize;
- }
- ProgressBar ProgressBar(ProgressMode, "Scan Files");
- FilteredRate FilteredBytesHashed;
- FilteredBytesHashed.Start();
- ChunkingStatistics LocalChunkingStats;
- ChunkedFolderContent UpdatedLocalContent = ChunkFolderContent(
- LocalChunkingStats,
- Workers.GetIOWorkerPool(),
- Path,
- UpdatedContent,
- ChunkController,
- GetUpdateDelayMS(ProgressMode),
- [&](bool IsAborted, bool IsPaused, std::ptrdiff_t) {
- FilteredBytesHashed.Update(LocalChunkingStats.BytesHashed.load());
- std::string Details = fmt::format("{}/{} ({}/{}, {}B/s) scanned, {} ({}) chunks found",
- LocalChunkingStats.FilesProcessed.load(),
- UpdatedContent.Paths.size(),
- NiceBytes(LocalChunkingStats.BytesHashed.load()),
- NiceBytes(ByteCountToScan),
- NiceNum(FilteredBytesHashed.GetCurrent()),
- LocalChunkingStats.UniqueChunksFound.load(),
- NiceBytes(LocalChunkingStats.UniqueBytesFound.load()));
- ProgressBar.UpdateState({.Task = "Scanning files ",
- .Details = Details,
- .TotalCount = ByteCountToScan,
- .RemainingCount = ByteCountToScan - LocalChunkingStats.BytesHashed.load(),
- .Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)},
- false);
- },
- AbortFlag,
- PauseFlag);
-
- ChunkingStats += LocalChunkingStats;
-
- FilteredBytesHashed.Stop();
- ProgressBar.Finish();
- if (AbortFlag)
- {
- return {};
- }
- LocalContent = MergeChunkedFolderContents(LocalContent, {{UpdatedLocalContent}});
- }
- }
- else
- {
- // Remove files from LocalContent no longer in LocalFolderState
- tsl::robin_set<std::string> LocalFolderPaths;
- LocalFolderPaths.reserve(LocalFolderState.Paths.size());
- for (const std::filesystem::path& LocalFolderPath : LocalFolderState.Paths)
- {
- LocalFolderPaths.insert(LocalFolderPath.generic_string());
- }
- std::vector<std::filesystem::path> DeletedPaths;
- for (const std::filesystem::path& LocalContentPath : LocalContent.Paths)
- {
- if (!LocalFolderPaths.contains(LocalContentPath.generic_string()))
- {
- DeletedPaths.push_back(LocalContentPath);
- }
- }
- if (!DeletedPaths.empty())
- {
- LocalContent = DeletePathsFromChunkedContent(LocalContent, DeletedPaths);
- }
- }
-
- // Check files that are present in current folder state that matches the ReferencePaths but not known to the local state
- {
- FolderContent UpdatedContent;
-
- tsl::robin_set<std::string> LocalPathIndexLookup;
- for (const std::filesystem::path& LocalPath : LocalContent.Paths)
- {
- LocalPathIndexLookup.insert(LocalPath.generic_string());
- }
-
- tsl::robin_set<std::string> RemotePathIndexLookup;
- for (const std::filesystem::path& RemotePath : ReferencePaths)
- {
- RemotePathIndexLookup.insert(RemotePath.generic_string());
- }
-
- for (uint32_t LocalFolderPathIndex = 0; LocalFolderPathIndex < OutLocalFolderContent.Paths.size(); LocalFolderPathIndex++)
- {
- const std::filesystem::path& LocalFolderPath = OutLocalFolderContent.Paths[LocalFolderPathIndex];
- const std::string GenericLocalFolderPath = LocalFolderPath.generic_string();
- if (RemotePathIndexLookup.contains(GenericLocalFolderPath))
- {
- if (!LocalPathIndexLookup.contains(GenericLocalFolderPath))
- {
- UpdatedContent.Paths.push_back(LocalFolderPath);
- UpdatedContent.RawSizes.push_back(OutLocalFolderContent.RawSizes[LocalFolderPathIndex]);
- UpdatedContent.Attributes.push_back(OutLocalFolderContent.Attributes[LocalFolderPathIndex]);
- UpdatedContent.ModificationTicks.push_back(OutLocalFolderContent.ModificationTicks[LocalFolderPathIndex]);
- }
- }
- }
-
- if (UpdatedContent.Paths.size() > 0)
- {
- uint64_t ByteCountToScan = 0;
- for (const uint64_t RawSize : UpdatedContent.RawSizes)
- {
- ByteCountToScan += RawSize;
- }
- ProgressBar ProgressBar(ProgressMode, "Scan Files");
- FilteredRate FilteredBytesHashed;
- FilteredBytesHashed.Start();
- ChunkingStatistics LocalChunkingStats;
- ChunkedFolderContent UpdatedLocalContent = ChunkFolderContent(
- LocalChunkingStats,
- Workers.GetIOWorkerPool(),
- Path,
- UpdatedContent,
- ChunkController,
- GetUpdateDelayMS(ProgressMode),
- [&](bool IsAborted, bool IsPaused, std::ptrdiff_t) {
- FilteredBytesHashed.Update(LocalChunkingStats.BytesHashed.load());
- std::string Details = fmt::format("{}/{} ({}/{}, {}B/s) scanned, {} ({}) chunks found",
- LocalChunkingStats.FilesProcessed.load(),
- UpdatedContent.Paths.size(),
- NiceBytes(LocalChunkingStats.BytesHashed.load()),
- NiceBytes(ByteCountToScan),
- NiceNum(FilteredBytesHashed.GetCurrent()),
- LocalChunkingStats.UniqueChunksFound.load(),
- NiceBytes(LocalChunkingStats.UniqueBytesFound.load()));
- ProgressBar.UpdateState({.Task = "Scanning files ",
- .Details = Details,
- .TotalCount = ByteCountToScan,
- .RemainingCount = ByteCountToScan - LocalChunkingStats.BytesHashed.load(),
- .Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)},
- false);
- },
- AbortFlag,
- PauseFlag);
- ChunkingStats += LocalChunkingStats;
- FilteredBytesHashed.Stop();
- ProgressBar.Finish();
- if (!AbortFlag)
- {
- LocalContent = MergeChunkedFolderContents(LocalContent, {{UpdatedLocalContent}});
- }
- }
- }
-
- ScanContent = false;
+ ProgressBar ProgressBar(ProgressMode, "Check Files");
+ FolderState = GetValidFolderContent(
+ Workers,
+ LocalFolderScanStats,
+ Path,
+ PathsToCheck,
+ [&ProgressBar, &LocalFolderScanStats](uint64_t PathCount, uint64_t CompletedPathCount) {
+ std::string Details =
+ fmt::format("{}/{} checked, {} found", CompletedPathCount, PathCount, LocalFolderScanStats.FoundFileCount.load());
+ ProgressBar.UpdateState({.Task = "Checking files ",
+ .Details = Details,
+ .TotalCount = PathCount,
+ .RemainingCount = PathCount - CompletedPathCount,
+ .Status = ProgressBar::State::CalculateStatus(AbortFlag, PauseFlag)},
+ false);
+ });
+ ProgressBar.Finish();
}
- if (ScanContent)
+ if (FolderState.Paths.size() > 0)
{
uint64_t ByteCountToScan = 0;
- for (const uint64_t RawSize : OutLocalFolderContent.RawSizes)
+ for (const uint64_t RawSize : FolderState.RawSizes)
{
ByteCountToScan += RawSize;
}
@@ -1641,18 +1430,18 @@ namespace {
FilteredRate FilteredBytesHashed;
FilteredBytesHashed.Start();
ChunkingStatistics LocalChunkingStats;
- LocalContent = ChunkFolderContent(
+ ChunkedContent = ChunkFolderContent(
LocalChunkingStats,
Workers.GetIOWorkerPool(),
Path,
- OutLocalFolderContent,
+ FolderState,
ChunkController,
GetUpdateDelayMS(ProgressMode),
[&](bool IsAborted, bool IsPaused, std::ptrdiff_t) {
FilteredBytesHashed.Update(LocalChunkingStats.BytesHashed.load());
std::string Details = fmt::format("{}/{} ({}/{}, {}B/s) scanned, {} ({}) chunks found",
LocalChunkingStats.FilesProcessed.load(),
- OutLocalFolderContent.Paths.size(),
+ FolderState.Paths.size(),
NiceBytes(LocalChunkingStats.BytesHashed.load()),
NiceBytes(ByteCountToScan),
NiceNum(FilteredBytesHashed.GetCurrent()),
@@ -1661,7 +1450,7 @@ namespace {
ProgressBar.UpdateState({.Task = "Scanning files ",
.Details = Details,
.TotalCount = ByteCountToScan,
- .RemainingCount = (ByteCountToScan - LocalChunkingStats.BytesHashed.load()),
+ .RemainingCount = ByteCountToScan - LocalChunkingStats.BytesHashed.load(),
.Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)},
false);
},
@@ -1670,12 +1459,161 @@ namespace {
ChunkingStats += LocalChunkingStats;
FilteredBytesHashed.Stop();
ProgressBar.Finish();
- if (AbortFlag)
+ }
+
+ return BuildSaveState{.State = BuildState{.ChunkedContent = std::move(ChunkedContent)},
+ .FolderState = FolderState,
+ .LocalPath = Path};
+ }
+
+ BuildSaveState GetLocalContent(ThreadWorkers& Workers,
+ GetFolderContentStatistics& LocalFolderScanStats,
+ ChunkingStatistics& ChunkingStats,
+ const std::filesystem::path& Path,
+ const std::filesystem::path& StateFilePath,
+ ChunkingController& ChunkController)
+ {
+ Stopwatch ReadStateTimer;
+ bool FileExists = IsFile(StateFilePath);
+ if (!FileExists)
+ {
+ ZEN_CONSOLE("No known local state file in {}, falling back to scanning", Path);
+ return {};
+ }
+
+ BuildSaveState SavedLocalState;
+ try
+ {
+ SavedLocalState = ReadBuildSaveStateFile(StateFilePath);
+ if (!IsQuiet)
+ {
+ ZEN_CONSOLE("Read local state file {} in {}", StateFilePath, NiceTimeSpanMs(ReadStateTimer.GetElapsedTimeMs()));
+ }
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_CONSOLE_WARN("Failed reading state file {}, falling back to scannning. Reason: {}", StateFilePath, Ex.what());
+ return {};
+ }
+
+ FolderContent CurrentLocalFolderState;
+ {
+ ProgressBar ProgressBar(ProgressMode, "Check Known Files");
+ CurrentLocalFolderState = GetValidFolderContent(
+ Workers,
+ LocalFolderScanStats,
+ Path,
+ SavedLocalState.FolderState.Paths,
+ [&ProgressBar, &LocalFolderScanStats](uint64_t PathCount, uint64_t CompletedPathCount) {
+ std::string Details =
+ fmt::format("{}/{} checked, {} found", CompletedPathCount, PathCount, LocalFolderScanStats.FoundFileCount.load());
+ ProgressBar.UpdateState({.Task = "Checking files ",
+ .Details = Details,
+ .TotalCount = PathCount,
+ .RemainingCount = PathCount - CompletedPathCount,
+ .Status = ProgressBar::State::CalculateStatus(AbortFlag, PauseFlag)},
+ false);
+ });
+ ProgressBar.Finish();
+ }
+ if (AbortFlag)
+ {
+ return {};
+ }
+
+ if (!SavedLocalState.FolderState.AreKnownFilesEqual(CurrentLocalFolderState))
+ {
+ const size_t LocalStatePathCount = SavedLocalState.FolderState.Paths.size();
+ std::vector<std::filesystem::path> DeletedPaths;
+ FolderContent UpdatedContent = GetUpdatedContent(SavedLocalState.FolderState, CurrentLocalFolderState, DeletedPaths);
+ if (!DeletedPaths.empty())
+ {
+ SavedLocalState.State.ChunkedContent = DeletePathsFromChunkedContent(SavedLocalState.State.ChunkedContent, DeletedPaths);
+ }
+
+ if (!IsQuiet)
+ {
+ ZEN_CONSOLE("Updating state, {} local files deleted and {} local files updated out of {}",
+ DeletedPaths.size(),
+ UpdatedContent.Paths.size(),
+ LocalStatePathCount);
+ }
+ if (UpdatedContent.Paths.size() > 0)
+ {
+ uint64_t ByteCountToScan = 0;
+ for (const uint64_t RawSize : UpdatedContent.RawSizes)
+ {
+ ByteCountToScan += RawSize;
+ }
+ ProgressBar ProgressBar(ProgressMode, "Scan Known Files");
+ FilteredRate FilteredBytesHashed;
+ FilteredBytesHashed.Start();
+ ChunkingStatistics LocalChunkingStats;
+ ChunkedFolderContent UpdatedLocalContent = ChunkFolderContent(
+ LocalChunkingStats,
+ Workers.GetIOWorkerPool(),
+ Path,
+ UpdatedContent,
+ ChunkController,
+ GetUpdateDelayMS(ProgressMode),
+ [&](bool IsAborted, bool IsPaused, std::ptrdiff_t) {
+ FilteredBytesHashed.Update(LocalChunkingStats.BytesHashed.load());
+ std::string Details = fmt::format("{}/{} ({}/{}, {}B/s) scanned, {} ({}) chunks found",
+ LocalChunkingStats.FilesProcessed.load(),
+ UpdatedContent.Paths.size(),
+ NiceBytes(LocalChunkingStats.BytesHashed.load()),
+ NiceBytes(ByteCountToScan),
+ NiceNum(FilteredBytesHashed.GetCurrent()),
+ LocalChunkingStats.UniqueChunksFound.load(),
+ NiceBytes(LocalChunkingStats.UniqueBytesFound.load()));
+ ProgressBar.UpdateState({.Task = "Scanning files ",
+ .Details = Details,
+ .TotalCount = ByteCountToScan,
+ .RemainingCount = ByteCountToScan - LocalChunkingStats.BytesHashed.load(),
+ .Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)},
+ false);
+ },
+ AbortFlag,
+ PauseFlag);
+
+ ChunkingStats += LocalChunkingStats;
+
+ FilteredBytesHashed.Stop();
+ ProgressBar.Finish();
+ if (AbortFlag)
+ {
+ return {};
+ }
+ SavedLocalState.State.ChunkedContent =
+ MergeChunkedFolderContents(SavedLocalState.State.ChunkedContent, {{UpdatedLocalContent}});
+ }
+ }
+ else
+ {
+ // Remove files from LocalContent no longer in LocalFolderState
+ tsl::robin_set<std::string> LocalFolderPaths;
+ LocalFolderPaths.reserve(SavedLocalState.FolderState.Paths.size());
+ for (const std::filesystem::path& LocalFolderPath : SavedLocalState.FolderState.Paths)
+ {
+ LocalFolderPaths.insert(LocalFolderPath.generic_string());
+ }
+ std::vector<std::filesystem::path> DeletedPaths;
+ for (const std::filesystem::path& LocalContentPath : SavedLocalState.State.ChunkedContent.Paths)
+ {
+ if (!LocalFolderPaths.contains(LocalContentPath.generic_string()))
+ {
+ DeletedPaths.push_back(LocalContentPath);
+ }
+ }
+ if (!DeletedPaths.empty())
{
- return {};
+ SavedLocalState.State.ChunkedContent = DeletePathsFromChunkedContent(SavedLocalState.State.ChunkedContent, DeletedPaths);
}
}
- return LocalContent;
+
+ SavedLocalState.FolderState = CurrentLocalFolderState;
+
+ return SavedLocalState;
}
ChunkedFolderContent ScanAndChunkFolder(
@@ -1705,17 +1643,17 @@ namespace {
return {};
}
- FolderContent _;
- ChunkedFolderContent Result = GetLocalContent(Workers,
- GetFolderContentStats,
- ChunkingStats,
- Path,
- ZenStateFilePath(Path / ZenFolderName),
- ChunkController,
- Content.Paths,
- {},
- {},
- _);
+ BuildState LocalContent =
+ GetLocalContent(Workers, GetFolderContentStats, ChunkingStats, Path, ZenStateFilePath(Path / ZenFolderName), ChunkController)
+ .State;
+
+ std::vector<std::filesystem::path> UntrackedPaths = GetNewPaths(LocalContent.ChunkedContent.Paths, Content.Paths);
+
+ BuildState UntrackedLocalContent =
+ GetLocalStateFromPaths(Workers, GetFolderContentStats, ChunkingStats, Path, ChunkController, UntrackedPaths).State;
+
+ ChunkedFolderContent Result = MergeChunkedFolderContents(LocalContent.ChunkedContent,
+ std::vector<ChunkedFolderContent>{UntrackedLocalContent.ChunkedContent});
const uint64_t TotalRawSize = std::accumulate(Result.RawSizes.begin(), Result.RawSizes.end(), std::uint64_t(0));
const uint64_t ChunkedRawSize =
@@ -1751,6 +1689,7 @@ namespace {
std::vector<std::string> ExcludeWildcards;
uint64_t MaximumInMemoryPayloadSize = 512u * 1024u;
bool PopulateCache = true;
+ bool AppendNewContent = false;
};
void DownloadFolder(ThreadWorkers& Workers,
@@ -1790,8 +1729,10 @@ namespace {
std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u;
+ CbObject BuildObject = GetBuild(*Storage.BuildStorage, BuildId);
+
std::vector<std::pair<Oid, std::string>> AllBuildParts =
- ResolveBuildPartNames(*Storage.BuildStorage, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize);
+ ResolveBuildPartNames(BuildObject, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize);
std::vector<ChunkedFolderContent> PartContents;
@@ -1811,12 +1752,15 @@ namespace {
PartContents,
BlockDescriptions,
LooseChunkHashes);
-
+#if ZEN_BUILD_DEBUG
+ ValidateChunkedFolderContent(RemoteContent, BlockDescriptions, LooseChunkHashes, {}, {});
+#endif // ZEN_BUILD_DEBUG
const std::uint64_t LargeAttachmentSize = Options.AllowMultiparts ? PreferredMultipartChunkSize * 4u : (std::uint64_t)-1;
GetFolderContentStatistics LocalFolderScanStats;
ChunkingStatistics ChunkingStats;
- ChunkedFolderContent LocalContent;
- FolderContent LocalFolderContent;
+
+ BuildSaveState LocalState;
+
if (!Options.PrimeCacheOnly)
{
if (IsDir(Path))
@@ -1827,18 +1771,52 @@ namespace {
ChunkController = CreateStandardChunkingController(StandardChunkingControllerSettings{});
}
- LocalContent = GetLocalContent(Workers,
- LocalFolderScanStats,
- ChunkingStats,
- Path,
- ZenStateFilePath(Options.ZenFolderPath),
- *ChunkController,
- RemoteContent.Paths,
- Options.IncludeWildcards,
- Options.ExcludeWildcards,
- LocalFolderContent);
+ LocalState = GetLocalContent(Workers,
+ LocalFolderScanStats,
+ ChunkingStats,
+ Path,
+ ZenStateFilePath(Path / ZenFolderName),
+ *ChunkController);
+
+ std::vector<std::filesystem::path> UntrackedPaths = GetNewPaths(LocalState.State.ChunkedContent.Paths, RemoteContent.Paths);
+
+ BuildSaveState UntrackedLocalContent =
+ GetLocalStateFromPaths(Workers, LocalFolderScanStats, ChunkingStats, Path, *ChunkController, UntrackedPaths);
+
+ if (!UntrackedLocalContent.State.ChunkedContent.Paths.empty())
+ {
+ LocalState.State.ChunkedContent =
+ MergeChunkedFolderContents(LocalState.State.ChunkedContent,
+ std::vector<ChunkedFolderContent>{UntrackedLocalContent.State.ChunkedContent});
+
+ // TODO: Helper
+ LocalState.FolderState.Paths.insert(LocalState.FolderState.Paths.begin(),
+ UntrackedLocalContent.FolderState.Paths.begin(),
+ UntrackedLocalContent.FolderState.Paths.end());
+ LocalState.FolderState.RawSizes.insert(LocalState.FolderState.RawSizes.begin(),
+ UntrackedLocalContent.FolderState.RawSizes.begin(),
+ UntrackedLocalContent.FolderState.RawSizes.end());
+ LocalState.FolderState.Attributes.insert(LocalState.FolderState.Attributes.begin(),
+ UntrackedLocalContent.FolderState.Attributes.begin(),
+ UntrackedLocalContent.FolderState.Attributes.end());
+ LocalState.FolderState.ModificationTicks.insert(LocalState.FolderState.ModificationTicks.begin(),
+ UntrackedLocalContent.FolderState.ModificationTicks.begin(),
+ UntrackedLocalContent.FolderState.ModificationTicks.end());
+ }
+
+ if (Options.AppendNewContent)
+ {
+ RemoteContent = ApplyChunkedContentOverlay(LocalState.State.ChunkedContent,
+ RemoteContent,
+ Options.IncludeWildcards,
+ Options.ExcludeWildcards);
+ }
#if ZEN_BUILD_DEBUG
- ValidateChunkedFolderContent(LocalContent, {}, LocalContent.ChunkedContent.ChunkHashes);
+ ValidateChunkedFolderContent(RemoteContent,
+ BlockDescriptions,
+ LooseChunkHashes,
+ Options.IncludeWildcards,
+ Options.ExcludeWildcards);
#endif // ZEN_BUILD_DEBUG
}
else
@@ -1851,7 +1829,32 @@ namespace {
return;
}
- if (Options.EnableTargetFolderScavenging && !Options.CleanTargetFolder && CompareChunkedContent(RemoteContent, LocalContent))
+ LocalState.LocalPath = Path;
+
+ {
+ BuildsSelection::Build RemoteBuildState = {.Id = BuildId,
+ .IncludeWildcards = Options.IncludeWildcards,
+ .ExcludeWildcards = Options.ExcludeWildcards};
+ RemoteBuildState.Parts.reserve(BuildPartIds.size());
+ for (size_t PartIndex = 0; PartIndex < BuildPartIds.size(); PartIndex++)
+ {
+ RemoteBuildState.Parts.push_back(
+ {BuildsSelection::BuildPart{.Id = BuildPartIds[PartIndex],
+ .Name = PartIndex < BuildPartNames.size() ? BuildPartNames[PartIndex] : ""}});
+ }
+
+ if (Options.AppendNewContent)
+ {
+ LocalState.State.Selection.Builds.emplace_back(std::move(RemoteBuildState));
+ }
+ else
+ {
+ LocalState.State.Selection.Builds = std::vector<BuildsSelection::Build>{std::move(RemoteBuildState)};
+ }
+ }
+
+ if ((Options.EnableTargetFolderScavenging || Options.AppendNewContent) && !Options.CleanTargetFolder &&
+ CompareChunkedContent(RemoteContent, LocalState.State.ChunkedContent))
{
if (!IsQuiet)
{
@@ -1860,7 +1863,8 @@ namespace {
}
Stopwatch WriteStateTimer;
- CbObject StateObject = CreateStateObject(BuildId, AllBuildParts, PartContents, LocalFolderContent, Path);
+
+ CbObject StateObject = CreateBuildSaveStateObject(LocalState);
CreateDirectories(ZenStateFilePath(Options.ZenFolderPath).parent_path());
TemporaryFile::SafeWriteFile(ZenStateFilePath(Options.ZenFolderPath), StateObject.GetView());
if (!IsQuiet)
@@ -1868,7 +1872,11 @@ namespace {
ZEN_CONSOLE("Wrote local state in {}", NiceTimeSpanMs(WriteStateTimer.GetElapsedTimeMs()));
}
- AddDownloadedPath(Options.SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(Options.ZenFolderPath), Path);
+ AddDownloadedPath(Options.SystemRootDir,
+ BuildsDownloadInfo{.Selection = LocalState.State.Selection,
+ .LocalPath = Path,
+ .StateFilePath = ZenStateFilePath(Options.ZenFolderPath),
+ .Iso8601Date = DateTime::Now().ToIso8601()});
}
else
{
@@ -1884,9 +1892,8 @@ namespace {
{
ZEN_CONSOLE("Downloading build {}, parts:{} to '{}' ({})", BuildId, BuildPartString.ToView(), Path, NiceBytes(RawSize));
}
- FolderContent LocalFolderState;
- const ChunkedContentLookup LocalLookup = BuildChunkedContentLookup(LocalContent);
+ const ChunkedContentLookup LocalLookup = BuildChunkedContentLookup(LocalState.State.ChunkedContent);
const ChunkedContentLookup RemoteLookup = BuildChunkedContentLookup(RemoteContent);
ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Download, TaskSteps::StepCount);
@@ -1901,34 +1908,39 @@ namespace {
Workers.GetNetworkPool(),
BuildId,
Path,
- LocalContent,
+ LocalState.State.ChunkedContent,
LocalLookup,
RemoteContent,
RemoteLookup,
BlockDescriptions,
LooseChunkHashes,
- BuildsOperationUpdateFolder::Options{.IsQuiet = IsQuiet,
- .IsVerbose = IsVerbose,
- .AllowFileClone = Options.AllowFileClone,
- .UseSparseFiles = UseSparseFiles,
- .SystemRootDir = Options.SystemRootDir,
- .ZenFolderPath = Options.ZenFolderPath,
- .LargeAttachmentSize = LargeAttachmentSize,
- .PreferredMultipartChunkSize = PreferredMultipartChunkSize,
- .PartialBlockRequestMode = Options.PartialBlockRequestMode,
- .WipeTargetFolder = Options.CleanTargetFolder,
- .PrimeCacheOnly = Options.PrimeCacheOnly,
- .EnableOtherDownloadsScavenging = Options.EnableOtherDownloadsScavenging,
- .EnableTargetFolderScavenging = Options.EnableTargetFolderScavenging,
- .ValidateCompletedSequences = Options.PostDownloadVerify,
- .ExcludeFolders = DefaultExcludeFolders,
- .ExcludeExtensions = DefaultExcludeExtensions,
- .MaximumInMemoryPayloadSize = Options.MaximumInMemoryPayloadSize,
- .PopulateCache = Options.PopulateCache});
+ BuildsOperationUpdateFolder::Options{
+ .IsQuiet = IsQuiet,
+ .IsVerbose = IsVerbose,
+ .AllowFileClone = Options.AllowFileClone,
+ .UseSparseFiles = UseSparseFiles,
+ .SystemRootDir = Options.SystemRootDir,
+ .ZenFolderPath = Options.ZenFolderPath,
+ .LargeAttachmentSize = LargeAttachmentSize,
+ .PreferredMultipartChunkSize = PreferredMultipartChunkSize,
+ .PartialBlockRequestMode = Options.PartialBlockRequestMode,
+ .WipeTargetFolder = Options.CleanTargetFolder,
+ .PrimeCacheOnly = Options.PrimeCacheOnly,
+ .EnableOtherDownloadsScavenging = Options.EnableOtherDownloadsScavenging,
+ .EnableTargetFolderScavenging = Options.EnableTargetFolderScavenging || Options.AppendNewContent,
+ .ValidateCompletedSequences = Options.PostDownloadVerify,
+ .ExcludeFolders = DefaultExcludeFolders,
+ .ExcludeExtensions = DefaultExcludeExtensions,
+ .MaximumInMemoryPayloadSize = Options.MaximumInMemoryPayloadSize,
+ .PopulateCache = Options.PopulateCache});
{
ProgressBar::PushLogOperation(ProgressMode, "Download");
- auto _ = MakeGuard([]() { ProgressBar::PopLogOperation(ProgressMode); });
- Updater.Execute(LocalFolderState);
+ auto _ = MakeGuard([]() { ProgressBar::PopLogOperation(ProgressMode); });
+ FolderContent UpdatedLocalFolderState;
+ Updater.Execute(UpdatedLocalFolderState);
+
+ LocalState.State.ChunkedContent = RemoteContent;
+ LocalState.FolderState = std::move(UpdatedLocalFolderState);
}
VerifyFolderStatistics VerifyFolderStats;
@@ -1936,14 +1948,18 @@ namespace {
{
if (!Options.PrimeCacheOnly)
{
- AddDownloadedPath(Options.SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(Options.ZenFolderPath), Path);
+ AddDownloadedPath(Options.SystemRootDir,
+ BuildsDownloadInfo{.Selection = LocalState.State.Selection,
+ .LocalPath = Path,
+ .StateFilePath = ZenStateFilePath(Options.ZenFolderPath),
+ .Iso8601Date = DateTime::Now().ToIso8601()});
ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Verify, TaskSteps::StepCount);
VerifyFolder(Workers, RemoteContent, RemoteLookup, Path, Options.PostDownloadVerify, VerifyFolderStats);
Stopwatch WriteStateTimer;
- CbObject StateObject = CreateStateObject(BuildId, AllBuildParts, PartContents, LocalFolderState, Path);
+ CbObject StateObject = CreateBuildSaveStateObject(LocalState);
CreateDirectories(ZenStateFilePath(Options.ZenFolderPath).parent_path());
TemporaryFile::SafeWriteFile(ZenStateFilePath(Options.ZenFolderPath), StateObject.GetView());
@@ -2036,8 +2052,10 @@ namespace {
{
std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u;
+ CbObject BuildObject = GetBuild(*Storage.BuildStorage, BuildId);
+
std::vector<std::pair<Oid, std::string>> AllBuildParts =
- ResolveBuildPartNames(*Storage.BuildStorage, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize);
+ ResolveBuildPartNames(BuildObject, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize);
Stopwatch GetBuildPartTimer;
@@ -2452,6 +2470,20 @@ BuildsCommand::BuildsCommand()
"<allowpartialblockrequests>");
};
+ auto AddAppendNewContentOptions = [this](cxxopts::Options& Ops) {
+ Ops.add_option("",
+ "",
+ "append",
+ "Decides if the remote data should replace or append to the current local data.\n"
+ " false = the local content will be replaced by the remote content\n"
+ " auto = if no path wildcards are given the local content will be replaced, if wildcards are given the "
+ "remote data will complement the local data\n"
+ " true = the remote data will be overlayed on top of local data\n"
+ "Defaults to 'auto'.",
+ cxxopts::value(m_AppendNewContent),
+ "<append>");
+ };
+
m_Options.add_option("",
"v",
"verb",
@@ -2609,6 +2641,7 @@ BuildsCommand::BuildsCommand()
AddZenFolderOptions(m_DownloadOptions);
AddWorkerOptions(m_DownloadOptions);
AddWildcardOptions(m_DownloadOptions);
+ AddAppendNewContentOptions(m_DownloadOptions);
m_DownloadOptions.add_option("cache",
"",
@@ -2730,6 +2763,9 @@ BuildsCommand::BuildsCommand()
m_TestOptions.add_option("", "l", "local-path", "Root file system folder used as base", cxxopts::value(m_Path), "<local-path>");
AddMultipartOptions(m_TestOptions);
AddPartialBlockRequestOptions(m_TestOptions);
+ AddWildcardOptions(m_TestOptions);
+ AddAppendNewContentOptions(m_TestOptions);
+
m_TestOptions.add_option("",
"",
"enable-scavenge",
@@ -3159,7 +3195,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ForEachStrTok(Wildcard, ';', [&Wildcards](std::string_view Wildcard) {
if (!Wildcard.empty())
{
- std::string CleanWildcard(Wildcard);
+ std::string CleanWildcard(ToLower(Wildcard));
for (auto It = begin(CleanWildcard); It != end(CleanWildcard); It++)
{
if (*It == '\\')
@@ -3663,6 +3699,15 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
return Mode;
};
+ auto ParseAppendNewContent = [&]() -> EAppendNewContentMode {
+ EAppendNewContentMode Mode = AppendNewContentModeFromString(m_AppendNewContent);
+ if (Mode == EAppendNewContentMode::Invalid)
+ {
+ throw OptionParseException(fmt::format("'--append' ('{}') is invalid", m_AppendNewContent), SubOption->help());
+ }
+ return Mode;
+ };
+
if (SubOption == &m_DownloadOptions)
{
if (!IsQuiet)
@@ -3726,6 +3771,25 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::vector<std::string> BuildPartNames = ParseBuildPartNames();
EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests();
+ EAppendNewContentMode AppendNewContentMode = ParseAppendNewContent();
+
+ bool AppendNewContent = AppendNewContentMode == EAppendNewContentMode::Append;
+ if (AppendNewContentMode == EAppendNewContentMode::Auto)
+ {
+ if (IncludeWildcards.empty() && ExcludeWildcards.empty())
+ {
+ AppendNewContent = false;
+ }
+ else
+ {
+ AppendNewContent = true;
+ }
+ }
+
+ if (AppendNewContent && m_Clean)
+ {
+ throw OptionParseException("'--append' conflicts with '--clean'", SubOption->help());
+ }
DownloadFolder(Workers,
Storage,
@@ -3746,7 +3810,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
.IncludeWildcards = IncludeWildcards,
.ExcludeWildcards = ExcludeWildcards,
.MaximumInMemoryPayloadSize = GetMaxMemoryBufferSize(m_BoostWorkerMemory),
- .PopulateCache = m_UploadToZenCache});
+ .PopulateCache = m_UploadToZenCache,
+ .AppendNewContent = AppendNewContent});
if (AbortFlag)
{
@@ -3858,8 +3923,10 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u;
+ CbObject BuildObject = GetBuild(*Storage.BuildStorage, BuildId);
+
std::vector<std::pair<Oid, std::string>> AllBuildParts =
- ResolveBuildPartNames(*Storage.BuildStorage, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize);
+ ResolveBuildPartNames(BuildObject, BuildId, BuildPartIds, BuildPartNames, PreferredMultipartChunkSize);
std::vector<Oid> AllBuildPartIds;
AllBuildPartIds.reserve(AllBuildParts.size());
@@ -4160,6 +4227,10 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
const std::filesystem::path DownloadPath2 = m_Path.parent_path() / (m_BuildPartName + "_test2");
const std::filesystem::path DownloadPath3 = m_Path.parent_path() / (m_BuildPartName + "_test3");
+ CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath);
+ CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath2);
+ CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath3);
+
auto ___ = MakeGuard([&Workers, DownloadPath, DownloadPath2, DownloadPath3]() {
CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath);
CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath2);
@@ -4233,6 +4304,95 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ValidateBuildPart(Workers, *Storage.BuildStorage, BuildId, BuildPartId, m_BuildPartName);
+ if (!m_IncludeWildcard.empty() || !m_ExcludeWildcard.empty())
+ {
+ auto __ = MakeGuard([&]() { CleanAndRemoveDirectory(Workers.GetIOWorkerPool(), DownloadPath); });
+
+ ZEN_CONSOLE("\nDownload Filtered Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath);
+
+ std::vector<std::string> IncludeWildcards;
+ std::vector<std::string> ExcludeWildcards;
+ ParseFileFilters(IncludeWildcards, ExcludeWildcards);
+
+ DownloadFolder(Workers,
+ Storage,
+ BuildId,
+ {BuildPartId},
+ {},
+ DownloadPath,
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = true,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = false,
+ .AllowFileClone = m_AllowFileClone,
+ .IncludeWildcards = IncludeWildcards,
+ .ExcludeWildcards = ExcludeWildcards,
+ .AppendNewContent = false});
+ if (AbortFlag)
+ {
+ throw std::runtime_error("Test aborted. (Download build)");
+ }
+
+ ZEN_CONSOLE("\nDownload Filtered Out Remaining of Build {}, Part {} ({}) to '{}'",
+ BuildId,
+ BuildPartId,
+ m_BuildPartName,
+ DownloadPath);
+ DownloadFolder(Workers,
+ Storage,
+ BuildId,
+ {BuildPartId},
+ {},
+ DownloadPath,
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = true,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true,
+ .AllowFileClone = m_AllowFileClone,
+ .IncludeWildcards = ExcludeWildcards,
+ .ExcludeWildcards = IncludeWildcards,
+ .AppendNewContent = true});
+ if (AbortFlag)
+ {
+ throw std::runtime_error("Test aborted. (Download build)");
+ }
+
+ ZEN_CONSOLE("\nDownload Full Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath);
+ DownloadFolder(Workers,
+ Storage,
+ BuildId,
+ {BuildPartId},
+ {},
+ DownloadPath,
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = false,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true,
+ .AllowFileClone = m_AllowFileClone,
+ .IncludeWildcards = {},
+ .ExcludeWildcards = {},
+ .AppendNewContent = false});
+ if (AbortFlag)
+ {
+ throw std::runtime_error("Test aborted. (Download build)");
+ }
+ }
+
ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath);
DownloadFolder(Workers,
Storage,
diff --git a/src/zen/cmds/builds_cmd.h b/src/zen/cmds/builds_cmd.h
index ec752e581..db8826cba 100644
--- a/src/zen/cmds/builds_cmd.h
+++ b/src/zen/cmds/builds_cmd.h
@@ -67,6 +67,7 @@ private:
std::string m_BuildPartId; // Defaults to a generated id when creating part, looked up when downloading using m_BuildPartName
bool m_Clean = false;
bool m_Force = false;
+ std::string m_AppendNewContent = "auto";
uint8_t m_BlockReuseMinPercentLimit = 85;
bool m_AllowMultiparts = true;
std::string m_AllowPartialBlockRequests = "mixed";
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,
diff --git a/src/zenremotestore/chunking/chunkedcontent.cpp b/src/zenremotestore/chunking/chunkedcontent.cpp
index ac979a64b..5f1876908 100644
--- a/src/zenremotestore/chunking/chunkedcontent.cpp
+++ b/src/zenremotestore/chunking/chunkedcontent.cpp
@@ -3,6 +3,7 @@
#include <zenremotestore/chunking/chunkedcontent.h>
#include <zencore/compactbinaryutil.h>
+#include <zencore/compositebuffer.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
@@ -15,6 +16,12 @@
#include <zenremotestore/chunking/chunkingcontroller.h>
#include <zenutil/wildcard.h>
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <numeric>
+#endif // ZEN_WITH_TESTS
+
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_set.h>
#include <gsl/gsl-lite.hpp>
@@ -170,6 +177,31 @@ namespace {
std::string PathCompareString(const std::filesystem::path& Path) { return ToLower(Path.generic_string()); }
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> BuildHashLookup(std::span<const IoHash> Hashes)
+ {
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> Lookup;
+ Lookup.reserve(Hashes.size());
+ for (uint32_t Index = 0; Index < Hashes.size(); Index++)
+ {
+ bool IsNew = Lookup.insert_or_assign(Hashes[Index], Index).second;
+ ZEN_ASSERT(IsNew);
+ }
+ return Lookup;
+ }
+
+ std::vector<uint32_t> BuildChunkOrderOffset(std::span<const uint32_t> ChunkCounts)
+ {
+ std::vector<uint32_t> ChunkOffsets;
+ ChunkOffsets.reserve(ChunkCounts.size());
+ uint32_t Offset = 0;
+ for (uint32_t SequenceIndex = 0; SequenceIndex < ChunkCounts.size(); SequenceIndex++)
+ {
+ ChunkOffsets.push_back(Offset);
+ Offset += ChunkCounts[SequenceIndex];
+ }
+ return ChunkOffsets;
+ }
+
} // namespace
std::string_view FolderContentSourcePlatformNames[(size_t)SourcePlatform::_Count] = {"Windows"sv, "Linux"sv, "MacOS"sv};
@@ -539,9 +571,9 @@ SaveChunkedFolderContentToCompactBinary(const ChunkedFolderContent& Content, CbW
}
ChunkedFolderContent
-LoadChunkedFolderContentToCompactBinary(CbObjectView Input)
+LoadChunkedFolderContentFromCompactBinary(CbObjectView Input)
{
- ZEN_TRACE_CPU("LoadChunkedFolderContentToCompactBinary");
+ ZEN_TRACE_CPU("LoadChunkedFolderContentFromCompactBinary");
ChunkedFolderContent Content;
Content.Platform = FromString(Input["platform"sv].AsString(), GetSourceCurrentPlatform());
Content.Paths = compactbinary_helpers::ReadArray<std::filesystem::path>("paths"sv, Input);
@@ -836,30 +868,135 @@ CompareChunkedContent(const ChunkedFolderContent& Lhs, const ChunkedFolderConten
return true;
};
-static tsl::robin_map<IoHash, uint32_t, IoHash::Hasher>
-BuildHashLookup(std::span<const IoHash> Hashes)
+ChunkedFolderContent
+ApplyChunkedContentOverlay(const ChunkedFolderContent& Base,
+ const ChunkedFolderContent& Overlay,
+ std::span<const std::string> OverlayIncludeWildcards,
+ std::span<const std::string> OverlayExcludeWildcards)
{
- tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> Lookup;
- Lookup.reserve(Hashes.size());
- for (uint32_t Index = 0; Index < Hashes.size(); Index++)
+ ChunkedFolderContent Result = {.Platform = Base.Platform};
+
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> BaseSequenceHashToSequenceIndex =
+ BuildHashLookup(Base.ChunkedContent.SequenceRawHashes);
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> BaseChunkHashToChunkIndex = BuildHashLookup(Base.ChunkedContent.ChunkHashes);
+ std::vector<uint32_t> BaseSequenceChunkOrderOffset = BuildChunkOrderOffset(Base.ChunkedContent.ChunkCounts);
+
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> OverlaySequenceHashToSequenceIndex =
+ BuildHashLookup(Overlay.ChunkedContent.SequenceRawHashes);
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> OverlayChunkHashToChunkIndex = BuildHashLookup(Overlay.ChunkedContent.ChunkHashes);
+ std::vector<uint32_t> OverlaySequenceChunkOrderOffset = BuildChunkOrderOffset(Overlay.ChunkedContent.ChunkCounts);
+
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> ResultSequenceHashToSequenceIndex;
+ tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> ResultChunkHashToChunkIndex;
+
+ const size_t EstimatedPathCount = Max(Base.Paths.size(), Overlay.Paths.size());
+
+ Result.Attributes.reserve(EstimatedPathCount);
+ Result.Paths.reserve(EstimatedPathCount);
+ Result.RawSizes.reserve(EstimatedPathCount);
+ Result.RawHashes.reserve(EstimatedPathCount);
+
+ const size_t EstimatedSequenceCount =
+ Max(Base.ChunkedContent.SequenceRawHashes.size(), Overlay.ChunkedContent.SequenceRawHashes.size());
+ Result.ChunkedContent.SequenceRawHashes.reserve(EstimatedSequenceCount);
+
+ const size_t EstimatedChunkCount = Max(Base.ChunkedContent.ChunkHashes.size(), Overlay.ChunkedContent.ChunkHashes.size());
+ Result.ChunkedContent.ChunkHashes.reserve(EstimatedChunkCount);
+ Result.ChunkedContent.ChunkRawSizes.reserve(EstimatedChunkCount);
+
+ const size_t EstimatedChunkOrderCount = Max(Base.ChunkedContent.ChunkOrders.size(), Overlay.ChunkedContent.ChunkOrders.size());
+ Result.ChunkedContent.ChunkOrders.reserve(EstimatedChunkOrderCount);
+
+ auto AddPath = [&Result, &ResultSequenceHashToSequenceIndex, &ResultChunkHashToChunkIndex](
+ const ChunkedFolderContent& Source,
+ uint32_t SourcePathIndex,
+ const tsl::robin_map<IoHash, uint32_t, IoHash::Hasher>& SourceSequenceHashToSequenceIndex,
+ const std::vector<uint32_t>& SourceSequenceChunkOrderOffset) {
+ Result.Attributes.push_back(Source.Attributes[SourcePathIndex]);
+ Result.Paths.push_back(Source.Paths[SourcePathIndex]);
+ Result.RawSizes.push_back(Source.RawSizes[SourcePathIndex]);
+ Result.RawHashes.push_back(Source.RawHashes[SourcePathIndex]);
+ if (Source.RawSizes[SourcePathIndex] > 0)
+ {
+ if (!ResultSequenceHashToSequenceIndex.contains(Source.RawHashes[SourcePathIndex]))
+ {
+ const uint32_t ResultSequenceIndex = gsl::narrow<uint32_t>(Result.ChunkedContent.SequenceRawHashes.size());
+ ResultSequenceHashToSequenceIndex.insert_or_assign(Source.RawHashes[SourcePathIndex], ResultSequenceIndex);
+ Result.ChunkedContent.SequenceRawHashes.push_back(Source.RawHashes[SourcePathIndex]);
+ auto SourceSequenceIndexIt = SourceSequenceHashToSequenceIndex.find(Source.RawHashes[SourcePathIndex]);
+ ZEN_ASSERT(SourceSequenceIndexIt != SourceSequenceHashToSequenceIndex.end());
+ const uint32_t SourceSequenceIndex = SourceSequenceIndexIt->second;
+ const uint32_t ChunkOrderOffset = SourceSequenceChunkOrderOffset[SourceSequenceIndex];
+ const uint32_t ChunkCount = Source.ChunkedContent.ChunkCounts[SourceSequenceIndex];
+ Result.ChunkedContent.ChunkCounts.push_back(ChunkCount);
+
+ std::span<const uint32_t> SourceChunkIndexes =
+ std::span<const uint32_t>(Source.ChunkedContent.ChunkOrders).subspan(ChunkOrderOffset, ChunkCount);
+ for (uint32_t SourceChunkIndex : SourceChunkIndexes)
+ {
+ const IoHash& ChunkHash = Source.ChunkedContent.ChunkHashes[SourceChunkIndex];
+ if (auto It = ResultChunkHashToChunkIndex.find(ChunkHash); It != ResultChunkHashToChunkIndex.end())
+ {
+ const uint32_t ResultChunkIndex = It->second;
+ Result.ChunkedContent.ChunkOrders.push_back(ResultChunkIndex);
+ }
+ else
+ {
+ const uint32_t ResultChunkIndex = gsl::narrow<uint32_t>(Result.ChunkedContent.ChunkHashes.size());
+ Result.ChunkedContent.ChunkHashes.push_back(ChunkHash);
+ Result.ChunkedContent.ChunkRawSizes.push_back(Source.ChunkedContent.ChunkRawSizes[SourceChunkIndex]);
+ Result.ChunkedContent.ChunkOrders.push_back(ResultChunkIndex);
+ ResultChunkHashToChunkIndex.insert_or_assign(ChunkHash, ResultChunkIndex);
+ }
+ }
+ }
+ }
+ };
+
+ if (OverlayIncludeWildcards.empty() && OverlayExcludeWildcards.empty())
{
- Lookup.insert_or_assign(Hashes[Index], Index);
- }
- return Lookup;
-}
+ tsl::robin_set<std::string> OverlayPaths;
+ OverlayPaths.reserve(Overlay.Paths.size());
+ for (uint32_t OverlayPathIndex = 0; OverlayPathIndex < Overlay.Paths.size(); OverlayPathIndex++)
+ {
+ const std::string PathString = ToLower(Overlay.Paths[OverlayPathIndex].generic_string());
+ OverlayPaths.insert(PathString);
+ }
+ for (uint32_t BasePathIndex = 0; BasePathIndex < Base.Paths.size(); BasePathIndex++)
+ {
+ const std::string PathString = ToLower(Base.Paths[BasePathIndex].generic_string());
+ if (!OverlayPaths.contains(PathString))
+ {
+ AddPath(Base, BasePathIndex, BaseSequenceHashToSequenceIndex, BaseSequenceChunkOrderOffset);
+ }
+ }
-static std::vector<uint32_t>
-BuildChunkOrderOffset(std::span<const uint32_t> ChunkCounts)
-{
- std::vector<uint32_t> ChunkOffsets;
- ChunkOffsets.reserve(ChunkCounts.size());
- uint32_t Offset = 0;
- for (uint32_t SequenceIndex = 0; SequenceIndex < ChunkCounts.size(); SequenceIndex++)
+ for (uint32_t OverlayPathIndex = 0; OverlayPathIndex < Overlay.Paths.size(); OverlayPathIndex++)
+ {
+ AddPath(Overlay, OverlayPathIndex, OverlaySequenceHashToSequenceIndex, OverlaySequenceChunkOrderOffset);
+ }
+ }
+ else
{
- ChunkOffsets.push_back(Offset);
- Offset += ChunkCounts[SequenceIndex];
+ for (uint32_t BasePathIndex = 0; BasePathIndex < Base.Paths.size(); BasePathIndex++)
+ {
+ const std::string PathString = ToLower(Base.Paths[BasePathIndex].generic_string());
+ if (!IncludePath(OverlayIncludeWildcards, OverlayExcludeWildcards, PathString, /*CaseSensitive*/ true))
+ {
+ AddPath(Base, BasePathIndex, BaseSequenceHashToSequenceIndex, BaseSequenceChunkOrderOffset);
+ }
+ }
+
+ for (uint32_t OverlayPathIndex = 0; OverlayPathIndex < Overlay.Paths.size(); OverlayPathIndex++)
+ {
+ const std::string PathString = ToLower(Overlay.Paths[OverlayPathIndex].generic_string());
+ if (IncludePath(OverlayIncludeWildcards, OverlayExcludeWildcards, PathString, /*CaseSensitive*/ true))
+ {
+ AddPath(Overlay, OverlayPathIndex, OverlaySequenceHashToSequenceIndex, OverlaySequenceChunkOrderOffset);
+ }
+ }
}
- return ChunkOffsets;
+ return Result;
}
ChunkedFolderContent
@@ -1058,9 +1195,85 @@ BuildChunkedContentLookup(const ChunkedFolderContent& Content)
}
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,
+ bool DoExtraVerify)
+{
+ 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 (DoExtraVerify)
+ {
+ 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);
+ }
+ }
+ if (DoExtraVerify)
+ {
+ 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);
+ }
+ }
+}
+
+void
ValidateChunkedFolderContent(const ChunkedFolderContent& Content,
std::span<const ChunkBlockDescription> BlockDescriptions,
- std::span<const IoHash> LooseChunks)
+ std::span<const IoHash> LooseChunks,
+ std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards)
{
size_t TotalKnownChunkCount = LooseChunks.size();
for (const ChunkBlockDescription& BlockDescription : BlockDescriptions)
@@ -1101,6 +1314,8 @@ ValidateChunkedFolderContent(const ChunkedFolderContent& Content,
std::span<const uint32_t> ChunkIndexes =
std::span<const uint32_t>(Content.ChunkedContent.ChunkOrders).subspan(ChunkOrderOffset, ChunkCount);
+ bool VerifyIfChunkExists = IncludePath(IncludeWildcards, ExcludeWildcards, ToLower(Path.generic_string()), true);
+
IoHashStream Hasher;
uint64_t SizeSum = 0;
for (uint32_t ChunkIndex : ChunkIndexes)
@@ -1116,9 +1331,13 @@ ValidateChunkedFolderContent(const ChunkedFolderContent& Content,
{
throw std::runtime_error("Chunked folder content contains zero chunk hash");
}
- if (!KnownChunks.contains(ChunkRawHash))
+
+ if (VerifyIfChunkExists)
{
- throw std::runtime_error(fmt::format("Chunked folder content references an unknown chunk '{}'", ChunkRawHash));
+ if (!KnownChunks.contains(ChunkRawHash))
+ {
+ throw std::runtime_error(fmt::format("Chunked folder content references an unknown chunk '{}'", ChunkRawHash));
+ }
}
SizeSum += ChunkSize;
}
@@ -1186,35 +1405,41 @@ chunkedcontent_forcelink()
{
}
-namespace chunked_test_utils {
- struct ChunkedFile
+namespace chunkedcontent_testutils {
+ ChunkedFile CreateChunkedFile(FastRandom& Random, const size_t FinalSize, size_t ChunkingSize, const IoBuffer& LastUsedBuffer)
{
- IoHash RawHash;
- std::vector<IoHash> ChunkHashes;
- std::vector<uint64_t> ChunkSizes;
- std::vector<IoBuffer> Chunks;
- };
-
- ChunkedFile CreateChunkedFile(FastRandom& Random, size_t Size, size_t ChunkingSize)
- {
- size_t ChunkCount = (Size + (ChunkingSize - 1)) / ChunkingSize;
+ size_t ChunkCount = (FinalSize + (ChunkingSize - 1)) / ChunkingSize;
std::vector<IoHash> ChunkHashes;
std::vector<uint64_t> ChunkSizes;
std::vector<IoBuffer> Chunks;
ChunkHashes.reserve(ChunkCount);
ChunkSizes.reserve(ChunkCount);
+ size_t SizeLeft = FinalSize;
IoHashStream HashStream;
- while (Size > 0)
+ while (SizeLeft > 0)
{
- size_t ChunkSize = Min(Size, ChunkingSize);
- IoBuffer ChunkBuffer = CreateRandomBlob(Random, ChunkSize);
+ size_t ChunkSize = Min(SizeLeft, ChunkingSize);
+ IoBuffer ChunkBuffer;
+ if (LastUsedBuffer && FinalSize == SizeLeft)
+ {
+ ChunkSize = Min(ChunkSize, LastUsedBuffer.GetSize());
+ ChunkBuffer = IoBuffer(LastUsedBuffer, 0, ChunkSize);
+ }
+ else
+ {
+ ChunkBuffer = CreateRandomBlob(Random, ChunkSize);
+ }
HashStream.Append(ChunkBuffer);
ChunkHashes.push_back(IoHash::HashBuffer(ChunkBuffer));
ChunkSizes.push_back(ChunkSize);
Chunks.emplace_back(std::move(ChunkBuffer));
- Size -= ChunkSize;
+ SizeLeft -= ChunkSize;
}
+ ZEN_ASSERT(std::accumulate(ChunkSizes.begin(), ChunkSizes.end(), uint64_t(0)) == FinalSize);
+ ZEN_ASSERT(std::accumulate(Chunks.begin(), Chunks.end(), uint64_t(0), [](uint64_t Current, const IoBuffer& B) {
+ return Current + B.GetSize();
+ }) == FinalSize);
return ChunkedFile{.RawHash = HashStream.GetHash(),
.ChunkHashes = std::move(ChunkHashes),
.ChunkSizes = std::move(ChunkSizes),
@@ -1233,6 +1458,8 @@ namespace chunked_test_utils {
Result.RawHashes.reserve(PathAndSizes.size());
ChunkPayloads.reserve(PathAndSizes.size());
+ IoBuffer LastChunkGenerated;
+
tsl::robin_map<IoHash, uint32_t> SequenceToIndex;
tsl::robin_map<IoHash, uint32_t> ChunkToIndex;
for (size_t PathIndex = 0; PathIndex < PathAndSizes.size(); PathIndex++)
@@ -1246,7 +1473,8 @@ namespace chunked_test_utils {
if (Size > 0)
{
- ChunkedFile File = CreateChunkedFile(Random, Size, ChunkingSize);
+ ChunkedFile File = CreateChunkedFile(Random, Size, ChunkingSize, LastChunkGenerated);
+ LastChunkGenerated = File.Chunks.back();
Result.RawHashes.push_back(File.RawHash);
if (auto SequenceIt = SequenceToIndex.find(File.RawHash); SequenceIt == SequenceToIndex.end())
@@ -1259,12 +1487,13 @@ namespace chunked_test_utils {
const IoHash& ChunkHash = File.ChunkHashes[ChunkIndex];
if (auto ChunkIt = ChunkToIndex.find(ChunkHash); ChunkIt == ChunkToIndex.end())
{
- const uint32_t ChunkedContentChunkIndex = gsl::narrow<uint32_t>(Result.ChunkedContent.ChunkOrders.size());
+ const uint32_t ChunkedContentChunkIndex = gsl::narrow<uint32_t>(Result.ChunkedContent.ChunkHashes.size());
Result.ChunkedContent.ChunkOrders.push_back(gsl::narrow<uint32_t>(ChunkedContentChunkIndex));
Result.ChunkedContent.ChunkHashes.push_back(ChunkHash);
Result.ChunkedContent.ChunkRawSizes.push_back(File.ChunkSizes[ChunkIndex]);
ChunkPayloads.push_back(std::move(File.Chunks[ChunkIndex]));
+ ChunkToIndex.insert_or_assign(ChunkHash, ChunkedContentChunkIndex);
}
else
{
@@ -1281,67 +1510,7 @@ namespace chunked_test_utils {
}
return Result;
}
-# if 0
- void ValidateChunkedFolderContent(const ChunkedFolderContent& Content, std::span<const IoBuffer> Chunks)
- {
- std::vector<uint32_t> ChunkOrderOffsets = BuildChunkOrderOffset(Content.ChunkedContent.ChunkCounts);
- tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> SequenceIndexLookup = BuildHashLookup(Content.ChunkedContent.SequenceRawHashes);
- std::vector<size_t> SequenceUseCount(Content.ChunkedContent.SequenceRawHashes.size(), 0);
- std::vector<size_t> ChunkUseCount(Content.ChunkedContent.ChunkHashes.size(), 0);
- for (size_t PathIndex = 0; PathIndex < Content.Paths.size(); PathIndex++)
- {
- const std::filesystem::path& Path = Content.Paths[PathIndex];
- ZEN_ASSERT(!Path.empty());
- const uint64_t RawSize = Content.RawSizes[PathIndex];
- const IoHash RawHash = Content.RawHashes[PathIndex];
- if (RawSize > 0)
- {
- if (auto It = SequenceIndexLookup.find(RawHash); It != SequenceIndexLookup.end())
- {
- const uint32_t SourceSequenceIndex = It->second;
- SequenceUseCount[SourceSequenceIndex]++;
- const uint32_t ChunkOrderOffset = ChunkOrderOffsets[SourceSequenceIndex];
- const uint32_t ChunkCount = Content.ChunkedContent.ChunkCounts[SourceSequenceIndex];
-
- std::span<const uint32_t> ChunkIndexes =
- std::span<const uint32_t>(Content.ChunkedContent.ChunkOrders).subspan(ChunkOrderOffset, ChunkCount);
-
- IoHashStream Hasher;
- uint64_t SizeSum = 0;
- for (uint32_t ChunkIndex : ChunkIndexes)
- {
- ChunkUseCount[ChunkIndex]++;
- const IoBuffer& ChunkBuffer = Chunks[ChunkIndex];
- const uint64_t ChunkSize = Content.ChunkedContent.ChunkRawSizes[ChunkIndex];
- const IoHash& ChunkRawHash = Content.ChunkedContent.ChunkHashes[ChunkIndex];
- SizeSum += ChunkSize;
- CHECK_EQ(ChunkRawHash, IoHash::HashBuffer(ChunkBuffer));
- Hasher.Append(ChunkBuffer);
- }
- CHECK_EQ(RawHash, Hasher.GetHash());
- CHECK_EQ(SizeSum, RawSize);
- }
- else
- {
- CHECK(false);
- }
- }
- else
- {
- CHECK(RawHash == IoHash::Zero);
- }
- }
- for (uint32_t SequenceIndex = 0; SequenceIndex < SequenceUseCount.size(); SequenceIndex++)
- {
- CHECK(SequenceUseCount[SequenceIndex] > 0);
- }
- for (uint32_t ChunkIndex = 0; ChunkIndex < ChunkUseCount.size(); ChunkIndex++)
- {
- CHECK(ChunkUseCount[ChunkIndex] > 0);
- }
- }
-# endif // 0
std::vector<IoBuffer> GetChunkPayloads(std::span<const IoHash> BaseHashes,
std::span<const IoBuffer> BaseChunks,
std::span<const IoHash> OverlayHashes,
@@ -1382,41 +1551,9 @@ namespace chunked_test_utils {
return Result;
}
- bool IncludePath(std::span<const std::string> IncludeWildcards,
- std::span<const std::string> ExcludeWildcards,
- const std::filesystem::path& Path)
- {
- const std::string PathString = Path.generic_string();
- bool IncludePath = true;
- if (!IncludeWildcards.empty())
- {
- IncludePath = false;
- for (const std::string& IncludeWildcard : IncludeWildcards)
- {
- if (MatchWildcard(IncludeWildcard, PathString, /*CaseSensitive*/ false))
- {
- IncludePath = true;
- break;
- }
- }
- if (!IncludePath)
- {
- return false;
- }
- }
- for (const std::string& ExcludeWildcard : ExcludeWildcards)
- {
- if (MatchWildcard(ExcludeWildcard, PathString, /*CaseSensitive*/ false))
- {
- return false;
- }
- }
- return true;
- }
-
-} // namespace chunked_test_utils
+} // namespace chunkedcontent_testutils
-TEST_CASE("DeletePathsFromContent")
+TEST_CASE("chunkedcontent.DeletePathsFromContent")
{
FastRandom BaseRandom;
@@ -1448,8 +1585,8 @@ TEST_CASE("DeletePathsFromContent")
{BasePaths[9], BaseSizes[9]},
{BasePaths[10], BaseSizes[10]}};
- ChunkedFolderContent Base = chunked_test_utils::CreateChunkedFolderContent(BaseRandom, BasePathAndSizes, 4u * 1024u, BaseChunks);
- ValidateChunkedFolderContent(Base, {}, Base.ChunkedContent.ChunkHashes);
+ ChunkedFolderContent Base = chunkedcontent_testutils::CreateChunkedFolderContent(BaseRandom, BasePathAndSizes, 4u * 1024u, BaseChunks);
+ ValidateChunkedFolderContent(Base, {}, Base.ChunkedContent.ChunkHashes, {}, {});
tsl::robin_map<IoHash, size_t, IoHash::Hasher> BaseChunksLookup;
for (size_t Index = 0; Index < BaseChunks.size(); Index++)
@@ -1463,7 +1600,7 @@ TEST_CASE("DeletePathsFromContent")
std::vector<std::filesystem::path> DeletedPaths;
for (const std::filesystem::path& RemotePath : Base.Paths)
{
- if (!chunked_test_utils::IncludePath(IncludeWildcards, ExcludeWildcards, RemotePath))
+ if (!IncludePath(IncludeWildcards, ExcludeWildcards, ToLower(RemotePath.generic_string()), true))
{
DeletedPaths.push_back(RemotePath);
}
@@ -1477,7 +1614,161 @@ TEST_CASE("DeletePathsFromContent")
InlineRemoveUnusedHashes(FilteredChunks, FilteredContent.ChunkedContent.ChunkHashes);
- ValidateChunkedFolderContent(FilteredContent, {}, FilteredChunks);
+ ValidateChunkedFolderContent(FilteredContent, {}, FilteredChunks, {}, {});
+}
+
+TEST_CASE("chunkedcontent.ApplyChunkedContentOverlay")
+{
+ FastRandom BaseRandom;
+
+ std::vector<IoBuffer> BaseChunks;
+
+ const std::string BasePaths[11] = {{"file_1"},
+ {"file_2.exe"},
+ {"file_3.txt"},
+ {"dir_1/dir1_file_1.exe"},
+ {"dir_1/dir1_file_2.pdb"},
+ {"dir_1/dir1_file_3.txt"},
+ {"dir_2/dir2_dir1/dir2_dir1_file_1.exe"},
+ {"dir_2/dir2_dir1/dir2_dir1_file_2.pdb"},
+ {"dir_2/dir2_dir1/dir2_dir1_file_3.dll"},
+ {"dir_2/dir2_dir2/dir2_dir2_file_1.txt"},
+ {"dir_2/dir2_dir2/dir2_dir2_file_2.json"}};
+ const uint64_t BaseSizes[11] =
+ {6u * 1024u, 0, 798, 19u * 1024u, 7u * 1024u, 93, 31u * 1024u, 17u * 1024u, 13u * 1024u, 2u * 1024u, 3u * 1024u};
+
+ std::pair<const std::string, uint64_t> BasePathAndSizes[11] = {{BasePaths[0], BaseSizes[0]},
+ {BasePaths[1], BaseSizes[1]},
+ {BasePaths[2], BaseSizes[2]},
+ {BasePaths[3], BaseSizes[3]},
+ {BasePaths[4], BaseSizes[4]},
+ {BasePaths[5], BaseSizes[5]},
+ {BasePaths[6], BaseSizes[6]},
+ {BasePaths[7], BaseSizes[7]},
+ {BasePaths[8], BaseSizes[8]},
+ {BasePaths[9], BaseSizes[9]},
+ {BasePaths[10], BaseSizes[10]}};
+
+ const std::string OverlayPaths[6] = {{"file_1"},
+ {"file_4"},
+ {"dir_1/dir1_file_1.exe"},
+ {"dir_1/dir1_file_2.pdb"},
+ {"dir_2/dir2_dir1/dir2_dir1_file_1.self"},
+ {"dir_2/dir2_dir1/dir2_dir1_file_2.sym"}};
+ const uint64_t OverlaySizes[6] = {7u * 1024u, 1249, 17u * 1024u, 9u * 1024u, 0, 17u * 1024u};
+
+ std::pair<const std::string, uint64_t> OverlayPathAndSizes[6] = {{OverlayPaths[0], OverlaySizes[0]},
+ {OverlayPaths[1], OverlaySizes[1]},
+ {OverlayPaths[2], OverlaySizes[2]},
+ {OverlayPaths[3], OverlaySizes[3]},
+ {OverlayPaths[4], OverlaySizes[4]},
+ {OverlayPaths[5], OverlaySizes[5]}};
+
+ ChunkedFolderContent Base = chunkedcontent_testutils::CreateChunkedFolderContent(BaseRandom, BasePathAndSizes, 4u * 1024u, BaseChunks);
+ ValidateChunkedFolderContent(Base, {}, Base.ChunkedContent.ChunkHashes, {}, {});
+ tsl::robin_map<std::string, uint32_t> BasePathLookup = chunkedcontent_testutils::BuildPathLookup(Base.Paths);
+
+ std::vector<IoBuffer> OverlayChunks;
+ ChunkedFolderContent Overlay =
+ chunkedcontent_testutils::CreateChunkedFolderContent(BaseRandom, OverlayPathAndSizes, 4u * 1024u, OverlayChunks);
+ ValidateChunkedFolderContent(Overlay, {}, Overlay.ChunkedContent.ChunkHashes, {}, {});
+
+ tsl::robin_map<std::string, uint32_t> OverlayPathLookup = chunkedcontent_testutils::BuildPathLookup(Overlay.Paths);
+
+ auto PathMatchesBase =
+ [&](const std::string& Path, const ChunkedFolderContent& MergedContent, tsl::robin_map<std::string, uint32_t> MergedPathLookup) {
+ return MergedContent.RawHashes[MergedPathLookup.at(Path)] == Base.RawHashes[BasePathLookup.at(Path)];
+ };
+
+ auto PathMatchesOverlay =
+ [&](const std::string& Path, const ChunkedFolderContent& MergedContent, tsl::robin_map<std::string, uint32_t> MergedPathLookup) {
+ return MergedContent.RawHashes[MergedPathLookup.at(Path)] == Overlay.RawHashes[OverlayPathLookup.at(Path)];
+ };
+
+ {
+ ChunkedFolderContent AllMergedContent = ApplyChunkedContentOverlay(Base, Overlay, {}, {});
+ CHECK_EQ(AllMergedContent.Paths.size(), 14);
+
+ std::vector<IoBuffer> AllMergedChunks = chunkedcontent_testutils::GetChunkPayloads(Base.ChunkedContent.ChunkHashes,
+ BaseChunks,
+ Overlay.ChunkedContent.ChunkHashes,
+ OverlayChunks,
+ AllMergedContent.ChunkedContent.ChunkHashes);
+ ValidateChunkedFolderContent(AllMergedContent, {}, AllMergedContent.ChunkedContent.ChunkHashes, {}, {});
+
+ tsl::robin_map<std::string, uint32_t> AllMergedPathLookup = chunkedcontent_testutils::BuildPathLookup(AllMergedContent.Paths);
+ CHECK(PathMatchesBase("file_2.exe", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesBase("file_3.txt", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesBase("dir_1/dir1_file_3.txt", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir1/dir2_dir1_file_1.exe", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir1/dir2_dir1_file_2.pdb", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir1/dir2_dir1_file_3.dll", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir2/dir2_dir2_file_1.txt", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir2/dir2_dir2_file_2.json", AllMergedContent, AllMergedPathLookup));
+
+ CHECK(PathMatchesOverlay("file_1", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesOverlay("file_4", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesOverlay("dir_1/dir1_file_1.exe", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesOverlay("dir_1/dir1_file_2.pdb", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesOverlay("dir_2/dir2_dir1/dir2_dir1_file_1.self", AllMergedContent, AllMergedPathLookup));
+ CHECK(PathMatchesOverlay("dir_2/dir2_dir1/dir2_dir1_file_2.sym", AllMergedContent, AllMergedPathLookup));
+ }
+
+ {
+ ChunkedFolderContent ReplaceExecutablesContent =
+ ApplyChunkedContentOverlay(Base, Overlay, std::vector<std::string>{"*.exe", "*.self"}, {});
+ CHECK_EQ(ReplaceExecutablesContent.Paths.size(), 10);
+
+ std::vector<IoBuffer> ReplaceExecutablesChunks =
+ chunkedcontent_testutils::GetChunkPayloads(Base.ChunkedContent.ChunkHashes,
+ BaseChunks,
+ Overlay.ChunkedContent.ChunkHashes,
+ OverlayChunks,
+ ReplaceExecutablesContent.ChunkedContent.ChunkHashes);
+ ValidateChunkedFolderContent(ReplaceExecutablesContent, {}, ReplaceExecutablesContent.ChunkedContent.ChunkHashes, {}, {});
+
+ tsl::robin_map<std::string, uint32_t> ReplaceExecutablesPathLookup =
+ chunkedcontent_testutils::BuildPathLookup(ReplaceExecutablesContent.Paths);
+ CHECK(PathMatchesBase("file_1", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ CHECK(PathMatchesBase("file_3.txt", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ CHECK(PathMatchesBase("dir_1/dir1_file_2.pdb", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ CHECK(PathMatchesBase("dir_1/dir1_file_3.txt", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+
+ CHECK(PathMatchesBase("dir_2/dir2_dir1/dir2_dir1_file_2.pdb", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir1/dir2_dir1_file_3.dll", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir2/dir2_dir2_file_1.txt", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ CHECK(PathMatchesBase("dir_2/dir2_dir2/dir2_dir2_file_2.json", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+
+ CHECK(PathMatchesOverlay("dir_1/dir1_file_1.exe", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ CHECK(PathMatchesOverlay("dir_2/dir2_dir1/dir2_dir1_file_1.self", ReplaceExecutablesContent, ReplaceExecutablesPathLookup));
+ }
+
+ {
+ ChunkedFolderContent ReplaceDir1ExecutablesContent = ApplyChunkedContentOverlay(Base,
+ Overlay,
+ std::vector<std::string>{"dir_1/*.exe", "dir_2/*"},
+ std::vector<std::string>{"dir_2/*.sym"});
+ CHECK_EQ(ReplaceDir1ExecutablesContent.Paths.size(), 7);
+
+ std::vector<IoBuffer> ReplaceDir1Chunks =
+ chunkedcontent_testutils::GetChunkPayloads(Base.ChunkedContent.ChunkHashes,
+ BaseChunks,
+ Overlay.ChunkedContent.ChunkHashes,
+ OverlayChunks,
+ ReplaceDir1ExecutablesContent.ChunkedContent.ChunkHashes);
+ ValidateChunkedFolderContent(ReplaceDir1ExecutablesContent, {}, ReplaceDir1ExecutablesContent.ChunkedContent.ChunkHashes, {}, {});
+
+ tsl::robin_map<std::string, uint32_t> ReplaceDir1ExecutablesPathLookup =
+ chunkedcontent_testutils::BuildPathLookup(ReplaceDir1ExecutablesContent.Paths);
+
+ CHECK(PathMatchesBase("file_1", ReplaceDir1ExecutablesContent, ReplaceDir1ExecutablesPathLookup));
+ CHECK(PathMatchesBase("file_2.exe", ReplaceDir1ExecutablesContent, ReplaceDir1ExecutablesPathLookup));
+ CHECK(PathMatchesBase("file_3.txt", ReplaceDir1ExecutablesContent, ReplaceDir1ExecutablesPathLookup));
+ CHECK(PathMatchesBase("dir_1/dir1_file_3.txt", ReplaceDir1ExecutablesContent, ReplaceDir1ExecutablesPathLookup));
+
+ CHECK(PathMatchesOverlay("dir_1/dir1_file_1.exe", ReplaceDir1ExecutablesContent, ReplaceDir1ExecutablesPathLookup));
+ CHECK(PathMatchesOverlay("dir_2/dir2_dir1/dir2_dir1_file_1.self", ReplaceDir1ExecutablesContent, ReplaceDir1ExecutablesPathLookup));
+ }
}
#endif // ZEN_WITH_TESTS
diff --git a/src/zenremotestore/include/zenremotestore/builds/buildcontent.h b/src/zenremotestore/include/zenremotestore/builds/buildcontent.h
new file mode 100644
index 000000000..67aebeda3
--- /dev/null
+++ b/src/zenremotestore/include/zenremotestore/builds/buildcontent.h
@@ -0,0 +1,43 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zenremotestore/chunking/chunkedcontent.h>
+
+namespace zen {
+
+class CbObjectView;
+class CbObjectWriter;
+
+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);
+
+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);
+
+#if ZEN_WITH_TESTS
+void buildsavedstate_forcelink();
+#endif // ZEN_WITH_TESTS
+
+} // namespace zen
diff --git a/src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h b/src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h
index a11641b0d..f808a7a3b 100644
--- a/src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h
+++ b/src/zenremotestore/include/zenremotestore/builds/buildsavedstate.h
@@ -13,48 +13,67 @@ 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);
+struct BuildsSelection
+{
+ struct BuildPart
+ {
+ Oid Id;
+ std::string Name;
+ };
+ struct Build
+ {
+ Oid Id;
+ std::vector<BuildPart> Parts;
+ std::vector<std::string> IncludeWildcards;
+ std::vector<std::string> ExcludeWildcards;
+ };
+ std::vector<Build> Builds;
+
+ static void Write(const BuildsSelection& Selection, CbWriter& Output);
+ static BuildsSelection Read(CbObjectView& Input, std::vector<ChunkedFolderContent>* OptionalOutLegacyPartsContent);
+};
+
+struct BuildState
+{
+ BuildsSelection Selection;
+ ChunkedFolderContent ChunkedContent;
+
+ static void Write(const BuildState& Selection, CbWriter& Output);
+ static BuildState Read(CbObjectView& Input);
+};
+
+struct BuildSaveState
+{
+ BuildState State;
+ FolderContent FolderState;
+ std::filesystem::path LocalPath;
+
+ static void Write(const BuildSaveState& SaveState, CbWriter& Output);
+ static BuildSaveState Read(CbObjectView& Input);
+};
+
+struct BuildsDownloadInfo
+{
+ BuildsSelection Selection;
+ std::filesystem::path LocalPath;
+ std::filesystem::path StateFilePath;
+ std::string Iso8601Date;
+
+ static void Write(const BuildsDownloadInfo& Info, CbWriter& Output);
+ static BuildsDownloadInfo Read(CbObjectView& Input);
+};
+
+CbObject CreateBuildSaveStateObject(const BuildSaveState& State);
+BuildSaveState ReadBuildSaveStateFile(const std::filesystem::path& StateFilePath);
+
+void AddDownloadedPath(const std::filesystem::path& SystemRootDir, const BuildsDownloadInfo& Info);
+
+BuildsDownloadInfo ReadDownloadedInfoFile(const std::filesystem::path& DownloadInfoPath);
std::vector<std::filesystem::path> GetDownloadedStatePaths(const std::filesystem::path& SystemRootDir);
+#if ZEN_WITH_TESTS
+void buildsavedstate_forcelink();
+#endif // ZEN_WITH_TESTS
+
} // namespace zen
diff --git a/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h b/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h
index eba0966f7..3c4535d9c 100644
--- a/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h
+++ b/src/zenremotestore/include/zenremotestore/builds/buildstorageoperations.h
@@ -497,8 +497,9 @@ public:
struct Options
{
- bool IsQuiet = false;
- bool IsVerbose = false;
+ bool IsQuiet = false;
+ bool IsVerbose = false;
+ bool DoExtraContentValidation = false;
const uint64_t FindBlockMaxCount = 10000;
const uint8_t BlockReuseMinPercentLimit = 85;
@@ -579,27 +580,6 @@ private:
const tsl::robin_map<IoHash, uint32_t, IoHash::Hasher>& ChunkHashToLocalChunkIndex,
const std::span<const uint32_t>& LooseChunkIndexes,
const std::span<const ChunkBlockDescription>& BlockDescriptions);
- 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 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);
CompositeBuffer FetchChunk(const ChunkedFolderContent& Content,
const ChunkedContentLookup& Lookup,
diff --git a/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h b/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h
index 227d877d8..e4be7923a 100644
--- a/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h
+++ b/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h
@@ -113,7 +113,7 @@ struct ChunkedContentLookup
};
void SaveChunkedFolderContentToCompactBinary(const ChunkedFolderContent& Content, CbWriter& Output);
-ChunkedFolderContent LoadChunkedFolderContentToCompactBinary(CbObjectView Input);
+ChunkedFolderContent LoadChunkedFolderContentFromCompactBinary(CbObjectView Input);
ChunkedFolderContent MergeChunkedFolderContents(const ChunkedFolderContent& Base, std::span<const ChunkedFolderContent> Overlays);
ChunkedFolderContent DeletePathsFromChunkedContent(const ChunkedFolderContent& Base,
@@ -123,6 +123,11 @@ ChunkedFolderContent DeletePathsFromChunkedContent(const ChunkedFolderContent& B
bool CompareChunkedContent(const ChunkedFolderContent& Lhs, const ChunkedFolderContent& Rhs);
+ChunkedFolderContent ApplyChunkedContentOverlay(const ChunkedFolderContent& Base,
+ const ChunkedFolderContent& Overlay,
+ std::span<const std::string> OverlayIncludeWildcards,
+ std::span<const std::string> OverlayExcludeWildcards);
+
struct ChunkingStatistics
{
std::atomic<uint64_t> FilesProcessed = 0;
@@ -198,14 +203,44 @@ GetFirstPathIndexForRawHash(const ChunkedContentLookup& Lookup, const IoHash& Ra
struct ChunkBlockDescription;
+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,
+ bool DoExtraVerify);
+
void ValidateChunkedFolderContent(const ChunkedFolderContent& Content,
std::span<const ChunkBlockDescription> BlockDescriptions,
- std::span<const IoHash> LooseChunks);
+ std::span<const IoHash> LooseChunks,
+ std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards);
void InlineRemoveUnusedHashes(std::vector<IoHash>& InOutHashes, std::span<const IoHash> UsedHashes);
#if ZEN_WITH_TESTS
void chunkedcontent_forcelink();
+
+struct FastRandom;
+
+namespace chunkedcontent_testutils {
+ struct ChunkedFile
+ {
+ IoHash RawHash;
+ std::vector<IoHash> ChunkHashes;
+ std::vector<uint64_t> ChunkSizes;
+ std::vector<IoBuffer> Chunks;
+ };
+
+ ChunkedFile CreateChunkedFile(FastRandom& Random, size_t Size, size_t ChunkingSize);
+ ChunkedFolderContent CreateChunkedFolderContent(FastRandom& Random,
+ std::span<const std::pair<const std::string, uint64_t>> PathAndSizes,
+ uint64_t ChunkingSize,
+ std::vector<IoBuffer>& ChunkPayloads);
+} // namespace chunkedcontent_testutils
+
#endif // ZEN_WITH_TESTS
} // namespace zen
diff --git a/src/zenremotestore/zenremotestore.cpp b/src/zenremotestore/zenremotestore.cpp
index 03baf1cb8..e074455b3 100644
--- a/src/zenremotestore/zenremotestore.cpp
+++ b/src/zenremotestore/zenremotestore.cpp
@@ -2,6 +2,7 @@
#include <zenremotestore/zenremotestore.h>
+#include <zenremotestore/builds/buildsavedstate.h>
#include <zenremotestore/chunking/chunkedcontent.h>
#include <zenremotestore/chunking/chunkedfile.h>
#include <zenremotestore/projectstore/remoteprojectstore.h>
@@ -13,9 +14,11 @@ namespace zen {
void
zenremotestore_forcelinktests()
{
+ buildsavedstate_forcelink();
chunkblock_forcelink();
chunkedcontent_forcelink();
chunkedfile_forcelink();
+ chunkedcontent_forcelink();
remoteprojectstore_forcelink();
}
diff --git a/src/zenutil/include/zenutil/wildcard.h b/src/zenutil/include/zenutil/wildcard.h
index 9f402e100..db2d4bda0 100644
--- a/src/zenutil/include/zenutil/wildcard.h
+++ b/src/zenutil/include/zenutil/wildcard.h
@@ -8,6 +8,11 @@ namespace zen {
bool MatchWildcard(std::string_view Wildcard, std::string_view String, bool CaseSensitive);
+bool IncludePath(std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards,
+ const std::string& Path,
+ bool CaseSensitive);
+
void wildcard_forcelink(); // internal
} // namespace zen
diff --git a/src/zenutil/wildcard.cpp b/src/zenutil/wildcard.cpp
index d9d5b384f..7a44c0498 100644
--- a/src/zenutil/wildcard.cpp
+++ b/src/zenutil/wildcard.cpp
@@ -78,6 +78,39 @@ MatchWildcard(std::string_view Wildcard, std::string_view String, bool CaseSensi
}
}
+bool
+IncludePath(std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards,
+ const std::string& Path,
+ bool CaseSensitive)
+{
+ bool IncludePath = true;
+ if (!IncludeWildcards.empty())
+ {
+ IncludePath = false;
+ for (const std::string& IncludeWildcard : IncludeWildcards)
+ {
+ if (MatchWildcard(IncludeWildcard, Path, CaseSensitive))
+ {
+ IncludePath = true;
+ break;
+ }
+ }
+ if (!IncludePath)
+ {
+ return false;
+ }
+ }
+ for (const std::string& ExcludeWildcard : ExcludeWildcards)
+ {
+ if (MatchWildcard(ExcludeWildcard, Path, CaseSensitive))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
#if ZEN_WITH_TESTS
void