aboutsummaryrefslogtreecommitdiff
path: root/zenserver/projectstore.h
blob: 283dec3b2e68d56638250cc20b099137d3f077b1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zencore/logging.h>
#include <zencore/uid.h>
#include <zencore/xxhash.h>
#include <zenhttp/httpserver.h>
#include <zenstore/cas.h>
#include <zenstore/caslog.h>
#include <zenstore/cidstore.h>
#include <zenstore/gc.h>

#include <filesystem>
#include <map>
#include <optional>
#include <string>

ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
ZEN_THIRD_PARTY_INCLUDES_END

namespace zen {

class CbPackage;

struct OplogEntry
{
	uint32_t OpLsn;
	uint32_t OpCoreOffset;	// note: Multiple of alignment!
	uint32_t OpCoreSize;
	uint32_t OpCoreHash;  // Used as checksum
	XXH3_128 OpKeyHash;	  // XXH128_canonical_t

	inline Oid OpKeyAsOId() const
	{
		Oid Id;
		memcpy(Id.OidBits, &OpKeyHash, sizeof Id.OidBits);
		return Id;
	}
};

struct OplogEntryAddress
{
	uint64_t Offset;
	uint64_t Size;
};

static_assert(IsPow2(sizeof(OplogEntry)));

/** Project Store
 */
class ProjectStore : public RefCounted, public GcContributor
{
	struct OplogStorage;

public:
	ProjectStore(CidStore& Store, std::filesystem::path BasePath, CasGc& Gc);
	~ProjectStore();

	struct Project;

	struct Oplog
	{
		Oplog(std::string_view Id, Project* Project, CidStore& Store, std::filesystem::path BasePath);
		~Oplog();

		[[nodiscard]] static bool ExistsAt(std::filesystem::path BasePath);

		void IterateFileMap(std::function<void(const Oid&, const std::string_view& ServerPath, const std::string_view& ClientPath)>&& Fn);
		void IterateOplog(std::function<void(CbObject)>&& Fn);
		std::optional<CbObject> GetOplog(const Oid& Key);

		IoBuffer FindChunk(Oid ChunkId);

		inline static const uint32_t kInvalidOp = ~0u;

		/** Persist a new oplog entry
		 *
		 * Returns the oplog LSN assigned to the new entry, or kInvalidOp if the entry is rejected
		 */
		uint32_t AppendNewOplogEntry(CbPackage Op);

		enum UpdateType
		{
			kUpdateNewEntry,
			kUpdateReplay
		};

		/** 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
		 */
		uint32_t RegisterOplogEntry(CbObject Core, const OplogEntry& OpEntry, UpdateType TypeOfUpdate);

		/** Scan oplog and register each entry, thus updating the in-memory tracking tables
		 */
		void ReplayLog();

		const std::string& OplogId() const { return m_OplogId; }

		const std::filesystem::path& TempPath() const { return m_TempPath; }

		spdlog::logger& Log() { return m_OuterProject->Log(); }
		void			Flush();
		void			Scrub(ScrubContext& Ctx) const;
		void			GatherReferences(GcContext& GcCtx);

		std::size_t OplogCount() const { return m_LatestOpMap.size(); }

	private:
		struct FileMapEntry
		{
			std::string ServerPath;
			std::string ClientPath;
		};

		template<class V>
		using OidMap = tsl::robin_map<Oid, V, Oid::Hasher>;

		Project*			  m_OuterProject = nullptr;
		CidStore&			  m_CidStore;
		std::filesystem::path m_BasePath;
		std::filesystem::path m_TempPath;

		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
		std::map<int, OplogEntryAddress> m_OpAddressMap;	 // Index LSN -> op data in ops blob file
		OidMap<int>						 m_LatestOpMap;		 // op key -> latest op LSN for key

		RefPtr<OplogStorage> m_Storage;
		std::string			 m_OplogId;

		bool AddFileMapping(Oid FileId, IoHash Hash, std::string_view ServerPath, std::string_view ClientPath);
		void AddChunkMapping(Oid ChunkId, IoHash Hash);
		void AddMetaMapping(Oid ChunkId, IoHash Hash);
	};

	struct Project
	{
		std::string			  Identifier;
		std::filesystem::path RootDir;
		std::string			  EngineRootDir;
		std::string			  ProjectRootDir;

		Oplog* NewOplog(std::string_view OplogId);
		Oplog* OpenOplog(std::string_view OplogId);
		void   DeleteOplog(std::string_view OplogId);
		void   IterateOplogs(std::function<void(const Oplog&)>&& Fn) const;
		void   IterateOplogs(std::function<void(Oplog&)>&& Fn);
		void   DiscoverOplogs();

		Project(ProjectStore* PrjStore, CidStore& Store, std::filesystem::path BasePath);
		~Project();

		void					  Read();
		void					  Write();
		[[nodiscard]] static bool Exists(std::filesystem::path BasePath);
		void					  Flush();
		void					  Scrub(ScrubContext& Ctx);
		spdlog::logger&			  Log();
		void					  GatherReferences(GcContext& GcCtx);

	private:
		ProjectStore*				 m_ProjectStore;
		CidStore&					 m_CidStore;
		mutable RwLock				 m_ProjectLock;
		std::map<std::string, Oplog> m_Oplogs;
		std::filesystem::path		 m_OplogStoragePath;

		std::filesystem::path BasePathForOplog(std::string_view OplogId);
	};

	Oplog* OpenProjectOplog(std::string_view ProjectId, std::string_view OplogId);

	Project* OpenProject(std::string_view ProjectId);
	Project* NewProject(std::filesystem::path BasePath,
						std::string_view	  ProjectId,
						std::string_view	  RootDir,
						std::string_view	  EngineRootDir,
						std::string_view	  ProjectRootDir);
	void	 DeleteProject(std::string_view ProjectId);
	bool	 Exists(std::string_view ProjectId);
	void	 Flush();
	void	 Scrub(ScrubContext& Ctx);
	void	 DiscoverProjects();

	spdlog::logger&				 Log() { return m_Log; }
	const std::filesystem::path& BasePath() const { return m_ProjectBasePath; }

	virtual void GatherReferences(GcContext& GcCtx) override;

private:
	spdlog::logger&				   m_Log;
	CidStore&					   m_CidStore;
	std::filesystem::path		   m_ProjectBasePath;
	RwLock						   m_ProjectsLock;
	std::map<std::string, Project> m_Projects;

	std::filesystem::path BasePathForProject(std::string_view ProjectId);
};

//////////////////////////////////////////////////////////////////////////
//
//  {project}	a project identifier
//  {target}	a variation of the project, typically a build target
//  {lsn}		oplog entry sequence number
//
//  /prj/{project}
//  /prj/{project}/oplog/{target}
//  /prj/{project}/oplog/{target}/{lsn}
//
// oplog entry
//
// id: {id}
// key: {}
// meta: {}
// data: []
// refs:
//

class HttpProjectService : public HttpService
{
public:
	HttpProjectService(CidStore& Store, ProjectStore* InProjectStore);
	~HttpProjectService();

	virtual const char* BaseUri() const override;
	virtual void		HandleRequest(HttpServerRequest& Request) override;

private:
	inline spdlog::logger& Log() { return m_Log; }

	spdlog::logger&	  m_Log;
	CidStore&		  m_CidStore;
	HttpRequestRouter m_Router;
	Ref<ProjectStore> m_ProjectStore;
};

/** Project store interface for local clients
 *
 * This provides the same functionality as the HTTP interface but with
 * some optimizations which are only possible for clients running on the
 * same host as the Zen Store instance
 *
 */

class LocalProjectService : public RefCounted
{
protected:
	LocalProjectService(CasStore& CasStore, ProjectStore* Projects);
	~LocalProjectService();

public:
	static inline Ref<LocalProjectService> New(CasStore& Store, ProjectStore* Projects) { return new LocalProjectService(Store, Projects); }

private:
	struct LocalProjectImpl;

	CasStore&						  m_CasStore;
	Ref<ProjectStore>				  m_ProjectStore;
	std::unique_ptr<LocalProjectImpl> m_Impl;
};

void prj_forcelink();

}  // namespace zen