aboutsummaryrefslogtreecommitdiff
path: root/src/zenstore/include
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2025-10-02 16:35:22 +0200
committerGitHub Enterprise <[email protected]>2025-10-02 16:35:22 +0200
commit289379b55d19e08c47afb54a363fda9478623b9d (patch)
treeda2eb6aa2f95833c0c7063c6f71dc9576512d7c1 /src/zenstore/include
parentfix for RPC replay issue (wrong content-type) (#536) (diff)
downloadzen-289379b55d19e08c47afb54a363fda9478623b9d.tar.xz
zen-289379b55d19e08c47afb54a363fda9478623b9d.zip
move projectstore to zenstore (#541)
Diffstat (limited to 'src/zenstore/include')
-rw-r--r--src/zenstore/include/zenstore/oplogreferencedset.h46
-rw-r--r--src/zenstore/include/zenstore/projectstore.h583
2 files changed, 629 insertions, 0 deletions
diff --git a/src/zenstore/include/zenstore/oplogreferencedset.h b/src/zenstore/include/zenstore/oplogreferencedset.h
new file mode 100644
index 000000000..dcc156060
--- /dev/null
+++ b/src/zenstore/include/zenstore/oplogreferencedset.h
@@ -0,0 +1,46 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/uid.h>
+
+#include <optional>
+#include <string_view>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <tsl/robin_set.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+class IoBuffer;
+
+/**
+ * @brief Records which keys in the oplog (entry["op"]["key"]) are in the ReferencedSet reported by the client that uploaded the oplog.
+ *
+ * An oplog can contain ops from an earlier incremental cook result that are no longer referenced in the most recent cook;
+ * the OplogReferencedSet allows clients that need to view only referenced-by-head-cook entries to trim the oplog down to
+ * those entries.
+ *
+ * Keys are case-sensitive; client must ensure that capitalization matches between the ReferencedSet and the oplog keys.
+ */
+class OplogReferencedSet
+{
+public:
+ inline bool Contains(const Oid& OplogId) const { return Set.contains(OplogId); }
+ static inline bool IsNonPackage(std::string_view OplogKey)
+ {
+ // A referencedset always includes all non-package keys
+ return OplogKey.empty() || !OplogKey.starts_with('/');
+ }
+ void Clear();
+
+ static std::optional<OplogReferencedSet> LoadFromChunk(const IoBuffer& ChunkData);
+
+ static constexpr std::string_view ReferencedSetOplogKey = "ReferencedSet";
+
+private:
+ tsl::robin_set<Oid, Oid::Hasher> Set;
+};
+
+} // namespace zen
diff --git a/src/zenstore/include/zenstore/projectstore.h b/src/zenstore/include/zenstore/projectstore.h
new file mode 100644
index 000000000..258be5930
--- /dev/null
+++ b/src/zenstore/include/zenstore/projectstore.h
@@ -0,0 +1,583 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinarypackage.h>
+#include <zencore/compositebuffer.h>
+#include <zencore/uid.h>
+#include <zencore/xxhash.h>
+#include <zenstore/gc.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <tsl/robin_map.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+#include <map>
+#include <variant>
+
+namespace zen {
+
+class CidStore;
+class AuthMgr;
+class ScrubContext;
+
+/** Project Store
+
+ A project store consists of a number of Projects.
+
+ Each project contains a number of oplogs (short for "operation log"). UE uses
+ one oplog per target platform to store the output of the cook process.
+
+ An oplog consists of a sequence of "op" entries. Each entry is a structured object
+ containing references to attachments. Attachments are typically the serialized
+ package data split into separate chunks for bulk data, exports and header
+ information.
+ */
+class ProjectStore : public RefCounted, public GcStorage, public GcReferencer, public GcReferenceLocker
+{
+ struct OplogStorage;
+
+public:
+ struct Configuration
+ {
+ };
+
+ ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, const Configuration& Config);
+ ~ProjectStore();
+
+ struct LogSequenceNumber
+ {
+ uint32_t Number = 0u;
+
+ operator bool() const { return Number != 0u; };
+ LogSequenceNumber() = default;
+ explicit LogSequenceNumber(size_t InNumber) : Number(uint32_t(InNumber)) {}
+ operator size_t() const { return Number; };
+ inline auto operator<=>(const LogSequenceNumber& Other) const = default;
+
+ struct Hasher
+ {
+ size_t operator()(const LogSequenceNumber& v) const { return std::hash<uint32_t>()(v.Number); }
+ };
+ };
+
+ template<class V>
+ using LsnMap = tsl::robin_map<LogSequenceNumber, V, LogSequenceNumber::Hasher>;
+
+ struct OplogEntryAddress
+ {
+ uint32_t Offset; // note: Multiple of m_OpsAlign!
+ uint32_t Size;
+ };
+
+ struct OplogEntry
+ {
+ LogSequenceNumber OpLsn;
+ OplogEntryAddress OpCoreAddress;
+ uint32_t OpCoreHash; // Used as checksum
+ Oid OpKeyHash;
+ uint32_t Reserved;
+
+ inline bool IsTombstone() const { return OpCoreAddress.Offset == 0 && OpCoreAddress.Size == 0 && OpLsn.Number; }
+ inline void MakeTombstone()
+ {
+ OpLsn = {};
+ OpCoreAddress.Offset = OpCoreAddress.Size = OpCoreHash = Reserved = 0;
+ }
+ };
+
+ static_assert(IsPow2(sizeof(OplogEntry)));
+
+ struct Oplog : public RefCounted
+ {
+ enum class EMode
+ {
+ kBasicReadOnly,
+ kFull
+ };
+
+ Oplog(const LoggerRef& Log,
+ std::string_view ProjectIdentifier,
+ std::string_view Id,
+ CidStore& Store,
+ const std::filesystem::path& BasePath,
+ const std::filesystem::path& MarkerPath,
+ EMode State);
+ ~Oplog();
+
+ [[nodiscard]] static bool ExistsAt(const std::filesystem::path& BasePath);
+ bool Exists() const;
+
+ void Read();
+ void Write();
+ void Update(const std::filesystem::path& MarkerPath);
+ bool Reset();
+ bool CanUnload();
+
+ struct ChunkInfo
+ {
+ Oid ChunkId;
+ uint64_t ChunkSize;
+ };
+
+ struct Paging
+ {
+ int32_t Start = -1;
+ int32_t Count = -1;
+ };
+
+ std::vector<ChunkInfo> GetAllChunksInfo(const std::filesystem::path& ProjectRootDir);
+ void IterateChunkMap(std::function<void(const Oid&, const IoHash& Hash)>&& Fn);
+ void IterateFileMap(std::function<void(const Oid&, const std::string_view& ServerPath, const std::string_view& ClientPath)>&& Fn);
+ void IterateOplog(std::function<void(CbObjectView)>&& Fn, const Paging& EntryPaging);
+ void IterateOplogWithKey(std::function<void(LogSequenceNumber, const Oid&, CbObjectView)>&& Fn);
+ void IterateOplogWithKey(std::function<void(LogSequenceNumber, const Oid&, CbObjectView)>&& Fn, const Paging& EntryPaging);
+ void IterateOplogLocked(std::function<void(CbObjectView)>&& Fn, const Paging& EntryPaging);
+ size_t GetOplogEntryCount() const;
+
+ std::optional<CbObject> GetOpByKey(const Oid& Key);
+ std::optional<CbObject> GetOpByIndex(LogSequenceNumber Index);
+ LogSequenceNumber GetOpIndexByKey(const Oid& Key);
+
+ IoBuffer FindChunk(const std::filesystem::path& ProjectRootDir, const Oid& ChunkId, uint64_t* OptOutModificationTag);
+ IoBuffer GetChunkByRawHash(const IoHash& RawHash);
+ bool IterateChunks(std::span<IoHash> RawHashes,
+ bool IncludeModTag,
+ const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback,
+ WorkerThreadPool* OptionalWorkerPool,
+ uint64_t LargeSizeLimit);
+ bool IterateChunks(const std::filesystem::path& ProjectRootDir,
+ std::span<Oid> ChunkIds,
+ bool IncludeModTag,
+ const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback,
+ WorkerThreadPool* OptionalWorkerPool,
+ uint64_t LargeSizeLimit);
+
+ /** Persist a new oplog entry
+ *
+ * Returns the oplog LSN assigned to the new entry, or an invalid number if the entry is rejected
+ */
+ LogSequenceNumber AppendNewOplogEntry(CbPackage Op);
+ LogSequenceNumber AppendNewOplogEntry(CbObjectView Core);
+ std::vector<LogSequenceNumber> AppendNewOplogEntries(std::span<CbObjectView> Cores);
+
+ const std::string& OplogId() const { return m_OplogId; }
+
+ const std::filesystem::path& TempPath() const { return m_TempPath; }
+ const std::filesystem::path& MarkerPath() const { return m_MarkerPath; }
+
+ LoggerRef Log() const { return m_Log; }
+ void Flush();
+ void Scrub(ScrubContext& Ctx);
+ static uint64_t TotalSize(const std::filesystem::path& BasePath);
+ uint64_t TotalSize() const;
+
+ std::size_t OplogCount() const
+ {
+ RwLock::SharedLockScope _(m_OplogLock);
+ return m_OpToPayloadOffsetMap.size();
+ }
+
+ void ResetState();
+ bool PrepareForDelete(std::filesystem::path& OutRemoveDirectory);
+
+ void EnableUpdateCapture();
+ void DisableUpdateCapture();
+ void CaptureAddedAttachments(std::span<const IoHash> AttachmentHashes);
+ std::vector<IoHash> GetCapturedAttachmentsLocked();
+ std::vector<IoHash> CheckPendingChunkReferences(std::span<const IoHash> ChunkHashes, const GcClock::Duration& RetainTime);
+ void RemovePendingChunkReferences(std::span<const IoHash> ChunkHashes);
+ std::vector<IoHash> GetPendingChunkReferencesLocked();
+
+ RwLock::SharedLockScope GetGcReferencerLock() { return RwLock::SharedLockScope(m_OplogLock); }
+
+ uint32_t GetUnusedSpacePercent() const;
+ void Compact(bool DryRun, bool RetainLSNs, std::string_view LogPrefix);
+
+ void GetAttachmentsLocked(std::vector<IoHash>& OutAttachments, bool StoreMetaDataOnDisk);
+
+ std::string_view GetOuterProjectIdentifier() const { return m_OuterProjectId; }
+ void CompactIfUnusedExceeds(bool DryRun, uint32_t CompactUnusedThreshold, std::string_view LogPrefix);
+
+ static std::optional<CbObject> ReadStateFile(const std::filesystem::path& BasePath, std::function<LoggerRef()>&& Log);
+
+ struct ChunkMapping
+ {
+ Oid Id;
+ IoHash Hash;
+ };
+
+ struct FileMapping
+ {
+ Oid Id;
+ IoHash Hash; // This is either zero or a cid
+ std::string ServerPath; // If Hash is valid then this should be empty
+ std::string ClientPath;
+ };
+
+ struct ValidationResult
+ {
+ uint32_t OpCount = 0;
+ LogSequenceNumber LSNLow;
+ LogSequenceNumber LSNHigh;
+ std::vector<std::pair<Oid, FileMapping>> MissingFiles;
+ std::vector<std::pair<Oid, ChunkMapping>> MissingChunks;
+ std::vector<std::pair<Oid, ChunkMapping>> MissingMetas;
+ std::vector<std::pair<Oid, IoHash>> MissingAttachments;
+ std::vector<std::pair<Oid, std::string>> OpKeys;
+
+ bool IsEmpty() const
+ {
+ return MissingFiles.empty() && MissingChunks.empty() && MissingMetas.empty() && MissingAttachments.empty();
+ }
+ };
+
+ ValidationResult Validate(const std::filesystem::path& ProjectRootDir,
+ std::atomic_bool& IsCancelledFlag,
+ WorkerThreadPool* OptionalWorkerPool);
+
+ private:
+ struct FileMapEntry
+ {
+ std::string ServerPath;
+ std::string ClientPath;
+ };
+
+ template<class V>
+ using OidMap = tsl::robin_map<Oid, V, Oid::Hasher>;
+
+ LoggerRef m_Log;
+ const std::string m_OuterProjectId;
+ const std::string m_OplogId;
+ CidStore& m_CidStore;
+ const std::filesystem::path m_BasePath;
+ std::filesystem::path m_MarkerPath;
+ const std::filesystem::path m_TempPath;
+ const std::filesystem::path m_MetaPath;
+
+ const EMode m_Mode;
+
+ mutable RwLock m_OplogLock;
+ OidMap<IoHash> m_ChunkMap; // output data chunk id -> CAS address
+ OidMap<IoHash> m_MetaMap; // meta chunk id -> CAS address
+ OidMap<FileMapEntry> m_FileMap; // file id -> file map entry
+ int32_t m_ManifestVersion; // File system manifest version
+
+ struct PayloadIndex
+ {
+ uint32_t Index = std::numeric_limits<uint32_t>::max();
+
+ operator bool() const { return Index != std::numeric_limits<uint32_t>::max(); };
+ PayloadIndex() = default;
+ explicit PayloadIndex(size_t InIndex) : Index(uint32_t(InIndex)) {}
+ operator size_t() const { return Index; };
+ inline auto operator<=>(const PayloadIndex& Other) const = default;
+
+ struct Hasher
+ {
+ size_t operator()(const PayloadIndex& v) const { return std::hash<uint32_t>()(v.Index); }
+ };
+ };
+
+ struct OplogPayload
+ {
+ LogSequenceNumber Lsn;
+ OplogEntryAddress Address;
+ };
+
+ OidMap<PayloadIndex> m_OpToPayloadOffsetMap;
+ std::vector<OplogPayload> m_OpLogPayloads;
+ std::unique_ptr<LsnMap<PayloadIndex>> m_LsnToPayloadOffsetMap;
+
+ std::atomic<bool> m_MetaValid = false;
+
+ uint32_t m_UpdateCaptureRefCounter = 0;
+ std::unique_ptr<std::vector<Oid>> m_CapturedOps;
+ std::unique_ptr<std::vector<IoHash>> m_CapturedAttachments;
+ std::unordered_set<IoHash, IoHash::Hasher> m_PendingPrepOpAttachments;
+ GcClock::TimePoint m_PendingPrepOpAttachmentsRetainEnd;
+
+ RefPtr<OplogStorage> m_Storage;
+ uint64_t m_LogFlushPosition = 0;
+ bool m_IsLegacySnapshot = false;
+
+ RefPtr<OplogStorage> GetStorage();
+
+ /** Scan oplog and register each entry, thus updating the in-memory tracking tables
+ */
+ uint32_t GetUnusedSpacePercentLocked() const;
+ void WriteIndexSnapshot();
+ void ReadIndexSnapshot();
+ void RefreshLsnToPayloadOffsetMap(RwLock::ExclusiveLockScope&);
+
+ struct OplogEntryMapping
+ {
+ std::vector<ChunkMapping> Chunks;
+ std::vector<ChunkMapping> Meta;
+ std::vector<FileMapping> Files;
+ };
+
+ OplogEntryMapping GetMapping(CbObjectView Core);
+
+ /** Update tracking metadata for a new oplog entry
+ *
+ * This is used during replay (and gets called as part of new op append)
+ *
+ * Returns the oplog LSN assigned to the new entry, or kInvalidOp if the entry is rejected
+ */
+ LogSequenceNumber RegisterOplogEntry(RwLock::ExclusiveLockScope& OplogLock,
+ const OplogEntryMapping& OpMapping,
+ const OplogEntry& OpEntry);
+
+ void AddFileMapping(const RwLock::ExclusiveLockScope& OplogLock,
+ const Oid& FileId,
+ const IoHash& Hash,
+ std::string_view ServerPath,
+ std::string_view ClientPath);
+ void AddChunkMapping(const RwLock::ExclusiveLockScope& OplogLock, const Oid& ChunkId, const IoHash& Hash);
+ void AddMetaMapping(const RwLock::ExclusiveLockScope& OplogLock, const Oid& ChunkId, const IoHash& Hash);
+ void Compact(RwLock::ExclusiveLockScope& Lock, bool DryRun, bool RetainLSNs, std::string_view LogPrefix);
+ void IterateCapturedOpsLocked(std::function<bool(const Oid& Key, LogSequenceNumber LSN, const CbObjectView& UpdateOp)>&& Callback);
+ std::vector<PayloadIndex> GetSortedOpPayloadRangeLocked(
+ const Paging& EntryPaging,
+ tsl::robin_map<PayloadIndex, Oid, PayloadIndex::Hasher>* OutOptionalReverseKeyMap);
+
+ friend class ProjectStoreOplogReferenceChecker;
+ friend class ProjectStoreReferenceChecker;
+ friend class ProjectStoreOplogReferenceValidator;
+ friend struct OplogStorage;
+ };
+
+ struct Project : public RefCounted
+ {
+ std::string Identifier;
+ std::filesystem::path RootDir;
+ std::filesystem::path EngineRootDir;
+ std::filesystem::path ProjectRootDir;
+ std::filesystem::path ProjectFilePath;
+
+ Ref<Oplog> NewOplog(std::string_view OplogId, const std::filesystem::path& MarkerPath);
+ Ref<Oplog> OpenOplog(std::string_view OplogId, bool AllowCompact, bool VerifyPathOnDisk);
+ Ref<Oplog> ReadOplog(std::string_view OplogId);
+ bool TryUnloadOplog(std::string_view OplogId);
+ bool DeleteOplog(std::string_view OplogId);
+ bool RemoveOplog(std::string_view OplogId, std::filesystem::path& OutDeletePath);
+ void IterateOplogs(std::function<void(const RwLock::SharedLockScope&, const Oplog&)>&& Fn) const;
+ void IterateOplogs(std::function<void(const RwLock::SharedLockScope&, Oplog&)>&& Fn);
+ std::vector<std::string> ScanForOplogs() const;
+ bool IsExpired(const GcClock::TimePoint ExpireTime) const;
+ bool IsExpired(const GcClock::TimePoint ExpireTime, const ProjectStore::Oplog& Oplog) const;
+ bool IsExpired(const GcClock::TimePoint ExpireTime, std::string_view OplogId) const;
+ bool IsOplogTouchedSince(const GcClock::TimePoint TouchTime, std::string_view Oplog) const;
+ void TouchProject();
+ void TouchOplog(std::string_view Oplog);
+ GcClock::TimePoint LastOplogAccessTime(std::string_view Oplog) const;
+
+ Project(ProjectStore* PrjStore, CidStore& Store, std::filesystem::path BasePath);
+ virtual ~Project();
+
+ void Read();
+ void Write();
+ [[nodiscard]] static bool Exists(const std::filesystem::path& BasePath);
+ void Flush();
+ void Scrub(ScrubContext& Ctx);
+ LoggerRef Log() const;
+ static uint64_t TotalSize(const std::filesystem::path& BasePath);
+ uint64_t TotalSize() const;
+ bool PrepareForDelete(std::filesystem::path& OutDeletePath);
+
+ void EnableUpdateCapture();
+ void DisableUpdateCapture();
+ std::vector<std::string> GetCapturedOplogsLocked();
+
+ std::vector<RwLock::SharedLockScope> GetGcReferencerLocks();
+
+ void AddOplogToCompact(std::string_view OplogId)
+ {
+ m_OplogsToCompactLock.WithExclusiveLock([&]() { m_OplogsToCompact.insert(std::string(OplogId)); });
+ }
+ std::vector<std::string> GetOplogsToCompact()
+ {
+ std::vector<std::string> Result;
+ m_OplogsToCompactLock.WithExclusiveLock([&]() {
+ Result.reserve(m_OplogsToCompact.size());
+ Result.insert(Result.end(), m_OplogsToCompact.begin(), m_OplogsToCompact.end());
+ m_OplogsToCompact.clear();
+ });
+ return Result;
+ }
+
+ private:
+ ProjectStore* m_ProjectStore;
+ CidStore& m_CidStore;
+ mutable RwLock m_ProjectLock;
+ std::map<std::string, Ref<Oplog>> m_Oplogs;
+ std::filesystem::path m_OplogStoragePath;
+ mutable RwLock m_LastAccessTimesLock;
+ mutable tsl::robin_map<std::string, GcClock::Tick> m_LastAccessTimes;
+ uint32_t m_UpdateCaptureRefCounter = 0;
+ std::unique_ptr<std::vector<std::string>> m_CapturedOplogs;
+
+ RwLock m_OplogsToCompactLock;
+ std::unordered_set<std::string> m_OplogsToCompact;
+
+ std::filesystem::path BasePathForOplog(std::string_view OplogId) const;
+ bool IsExpired(const std::string& EntryName, const std::filesystem::path& MarkerPath, const GcClock::TimePoint ExpireTime) const;
+ void WriteAccessTimes();
+ void ReadAccessTimes();
+
+ friend class ProjectStoreOplogReferenceChecker;
+ friend class ProjectStoreReferenceChecker;
+ friend class ProjectStoreOplogReferenceValidator;
+ friend class ProjectStoreGcStoreCompactor;
+ };
+
+ Ref<Project> OpenProject(std::string_view ProjectId);
+ Ref<Project> NewProject(const std::filesystem::path& BasePath,
+ std::string_view ProjectId,
+ const std::filesystem::path& RootDir,
+ const std::filesystem::path& EngineRootDir,
+ const std::filesystem::path& ProjectRootDir,
+ const std::filesystem::path& ProjectFilePath);
+ bool UpdateProject(std::string_view ProjectId,
+ const std::filesystem::path& RootDir,
+ const std::filesystem::path& EngineRootDir,
+ const std::filesystem::path& ProjectRootDir,
+ const std::filesystem::path& ProjectFilePath);
+ bool RemoveProject(std::string_view ProjectId, std::filesystem::path& OutDeletePath);
+ bool DeleteProject(std::string_view ProjectId);
+ bool Exists(std::string_view ProjectId);
+ void Flush();
+ void DiscoverProjects();
+ void IterateProjects(std::function<void(Project& Prj)>&& Fn);
+
+ LoggerRef Log() { return m_Log; }
+ const std::filesystem::path& BasePath() const { return m_ProjectBasePath; }
+
+ // GcStorage
+ virtual void ScrubStorage(ScrubContext& Ctx) override;
+ virtual GcStorageSize StorageSize() const override;
+
+ virtual std::string GetGcName(GcCtx& Ctx) override;
+ virtual GcStoreCompactor* RemoveExpiredData(GcCtx& Ctx, GcStats& Stats) override;
+ virtual std::vector<GcReferenceChecker*> CreateReferenceCheckers(GcCtx& Ctx) override;
+ virtual std::vector<GcReferenceValidator*> CreateReferenceValidators(GcCtx& Ctx) override;
+
+ virtual std::vector<RwLock::SharedLockScope> LockState(GcCtx& Ctx) override;
+
+ CbArray GetProjectsList();
+ static CbObject GetProjectFiles(LoggerRef InLog,
+ Project& Project,
+ Oplog& Oplog,
+ const std::unordered_set<std::string>& WantedFieldNames);
+
+ static CbObject GetProjectChunkInfos(LoggerRef InLog,
+ Project& Project,
+ Oplog& Oplog,
+ const std::unordered_set<std::string>& WantedFieldNames);
+ static CbObject GetChunkInfo(LoggerRef InLog, Project& Project, Oplog& Oplog, const Oid& ChunkId);
+ struct GetChunkRangeResult
+ {
+ enum class EError : uint8_t
+ {
+ Ok,
+ NotFound,
+ NotModified,
+ MalformedContent,
+ OutOfRange
+ };
+ EError Error = EError(-1);
+ std::string ErrorDescription;
+ CompositeBuffer Chunk = CompositeBuffer();
+ IoHash RawHash = IoHash::Zero;
+ uint64_t RawSize = 0;
+ ZenContentType ContentType = ZenContentType::kUnknownContentType;
+ };
+ static GetChunkRangeResult GetChunkRange(LoggerRef InLog,
+ Project& Project,
+ Oplog& Oplog,
+ const Oid& ChunkId,
+ uint64_t Offset,
+ uint64_t Size,
+ ZenContentType AcceptType,
+ uint64_t* OptionalInOutModificationTag);
+ IoBuffer GetChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash);
+
+ IoBuffer GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const Oid& ChunkId);
+
+ IoBuffer GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const IoHash& Cid);
+
+ bool PutChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash, IoBuffer&& Chunk);
+
+ struct ChunkRequest
+ {
+ uint64_t Offset = 0;
+ uint64_t Size = (uint64_t)-1;
+ std::variant<IoHash, Oid> Id;
+ std::optional<uint64_t> ModTag;
+ bool SkipData = false;
+ };
+ struct ChunkResult
+ {
+ bool Exists = false;
+ IoBuffer ChunkBuffer;
+ uint64_t ModTag = 0;
+ };
+ std::vector<ChunkResult> GetChunks(Project& Project, Oplog& Oplog, std::span<const ChunkRequest> Requests);
+
+ std::vector<ProjectStore::ChunkRequest> ParseChunksRequests(Project& Project, Oplog& Oplog, const CbObject& Cb);
+ CbPackage WriteChunksRequestResponse(Project& Project,
+ Oplog& Oplog,
+ std::vector<ChunkRequest>&& Requests,
+ std::vector<ChunkResult>&& Results);
+
+ bool AreDiskWritesAllowed() const;
+
+ void EnableUpdateCapture();
+ void DisableUpdateCapture();
+ std::vector<std::string> GetCapturedProjectsLocked();
+
+private:
+ LoggerRef m_Log;
+ GcManager& m_Gc;
+ CidStore& m_CidStore;
+ std::filesystem::path m_ProjectBasePath;
+ const Configuration m_Config;
+ mutable RwLock m_ProjectsLock;
+ std::map<std::string, Ref<Project>> m_Projects;
+ const DiskWriteBlocker* m_DiskWriteBlocker = nullptr;
+ uint32_t m_UpdateCaptureRefCounter = 0;
+ std::unique_ptr<std::vector<std::string>> m_CapturedProjects;
+
+ std::filesystem::path BasePathForProject(std::string_view ProjectId);
+
+ friend class ProjectStoreGcStoreCompactor;
+ friend class ProjectStoreOplogReferenceChecker;
+ friend class ProjectStoreReferenceChecker;
+};
+
+Oid ComputeOpKey(const CbObjectView& Op);
+
+Oid OpKeyStringAsOid(std::string_view OpKey);
+
+template<typename T>
+Oid
+OpKeyStringAsOid(std::string_view OpKey, T& TmpBuffer)
+{
+ using namespace std::literals;
+
+ CbObjectWriter Writer;
+ Writer << "key"sv << OpKey;
+ Writer.Finalize();
+ TmpBuffer.resize(Writer.GetSaveSize());
+ MutableMemoryView SaveBuffer(MutableMemoryView(TmpBuffer.data(), TmpBuffer.size()));
+
+ const Oid OpId = ComputeOpKey(Writer.Save(SaveBuffer).AsObjectView());
+
+ return OpId;
+}
+
+void prj_forcelink();
+
+} // namespace zen