aboutsummaryrefslogtreecommitdiff
path: root/src/zenstore/workspaces.cpp
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2024-10-23 10:31:43 +0200
committerGitHub Enterprise <[email protected]>2024-10-23 10:31:43 +0200
commit530ab3394938331f224058c381a1db5d4a68e6a9 (patch)
tree5060eb394d67b7454855aed0fa8d7d3acf5f5c98 /src/zenstore/workspaces.cpp
parentfix gc date (#204) (diff)
downloadzen-530ab3394938331f224058c381a1db5d4a68e6a9.tar.xz
zen-530ab3394938331f224058c381a1db5d4a68e6a9.zip
workspace share security (#192)
- Improvement: Reworked workspace shares to be more secure. Workspaces and workspace shares can only be created using the `zen workspace` command, the http endpoint is disabled unless zenserver is started with the `--workspaces-allow-changes` option enabled. - Each workspace are now configured via a `zenworkspaceconfig.json` file in the root of each workspace - A workspace can allow shares to be created via the http interface if the workspace is created with the `--allow-share-create-from-http` option enabled - A new http endpoint at `/ws` - issuing a `Get` operation will get you a list of workspaces - A new http endpoint at `/ws/refresh` - issuing a `Get` will make zenserver scan for edits in workspaces and workspace shares
Diffstat (limited to 'src/zenstore/workspaces.cpp')
-rw-r--r--src/zenstore/workspaces.cpp1190
1 files changed, 828 insertions, 362 deletions
diff --git a/src/zenstore/workspaces.cpp b/src/zenstore/workspaces.cpp
index 4cf423d03..d30a27e33 100644
--- a/src/zenstore/workspaces.cpp
+++ b/src/zenstore/workspaces.cpp
@@ -10,6 +10,10 @@
#include <zencore/workthreadpool.h>
#include <zenutil/basicfile.h>
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <tsl/robin_set.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
#if ZEN_WITH_TESTS
# include <zencore/blake3.h>
# include <zencore/testing.h>
@@ -20,7 +24,10 @@ namespace zen {
namespace {
- std::string WorkspacesToJson(std::span<Workspaces::WorkspaceInfo> Workspaces)
+ static constexpr std::string_view WorkspacesConfigName("config.json");
+ static constexpr std::string_view WorkspaceConfigName("zenworkspaceconfig.json");
+
+ std::string WorkspacesToJson(std::span<const Workspaces::WorkspaceConfiguration> Workspaces)
{
using namespace std::literals;
@@ -28,27 +35,13 @@ namespace {
Writer.BeginArray("workspaces");
- for (const Workspaces::WorkspaceInfo& Workspace : Workspaces)
+ for (const Workspaces::WorkspaceConfiguration& Workspace : Workspaces)
{
Writer.BeginObject();
{
- Writer.AddObjectId("id"sv, Workspace.Config.Id);
- Writer.AddString("root_path"sv, reinterpret_cast<const char*>(Workspace.Config.RootPath.u8string().c_str()));
- Writer.BeginArray("shares");
- for (const Workspaces::WorkspaceShareConfiguration& Share : Workspace.Shares)
- {
- Writer.BeginObject();
- {
- Writer.AddObjectId("id"sv, Share.Id);
- Writer.AddString("share_path"sv, reinterpret_cast<const char*>(Share.SharePath.u8string().c_str()));
- if (!Share.Alias.empty())
- {
- Writer.AddString("alias"sv, Share.Alias);
- }
- }
- Writer.EndObject();
- }
- Writer.EndArray();
+ Writer.AddObjectId("id"sv, Workspace.Id);
+ Writer.AddString("root_path"sv, reinterpret_cast<const char*>(Workspace.RootPath.u8string().c_str()));
+ Writer.AddBool("allow_share_creation_from_http"sv, Workspace.AllowShareCreationFromHttp);
}
Writer.EndObject();
}
@@ -58,9 +51,7 @@ namespace {
return Json.ToString();
}
- std::vector<Workspaces::WorkspaceInfo> WorkspacesFromJson(const IoBuffer& WorkspaceJson,
- const std::function<Oid(const std::filesystem::path& Path)>& PathToIdCB,
- std::string& OutError)
+ std::vector<Workspaces::WorkspaceConfiguration> WorkspacesFromJson(const IoBuffer& WorkspaceJson, std::string& OutError)
{
using namespace std::literals;
@@ -68,37 +59,23 @@ namespace {
LoadCompactBinaryFromJson(std::string_view((const char*)(WorkspaceJson.Data()), WorkspaceJson.GetSize()), OutError);
if (OutError.empty())
{
- std::vector<Workspaces::WorkspaceInfo> Workspaces;
+ std::vector<Workspaces::WorkspaceConfiguration> Workspaces;
if (CbObjectView RootObject = RootField.AsObjectView(); RootObject)
{
for (CbFieldView WorkspaceField : RootObject["workspaces"].AsArrayView())
{
- CbObjectView Workspace = WorkspaceField.AsObjectView();
- Oid WorkspaceId = Workspace["id"sv].AsObjectId();
- std::filesystem::path RootPath = Workspace["root_path"sv].AsU8String();
+ CbObjectView Workspace = WorkspaceField.AsObjectView();
+ Oid WorkspaceId = Workspace["id"sv].AsObjectId();
+ std::filesystem::path RootPath = Workspace["root_path"sv].AsU8String();
+ bool AllowShareCreationFromHttp = Workspace["allow_share_creation_from_http"sv].AsBool();
if (WorkspaceId == Oid::Zero && !RootPath.empty())
{
- WorkspaceId = PathToIdCB(RootPath);
+ WorkspaceId = Workspaces::PathToId(RootPath);
}
if (WorkspaceId != Oid::Zero && !RootPath.empty())
{
- std::vector<Workspaces::WorkspaceShareConfiguration> Shares;
- for (CbFieldView ShareField : Workspace["shares"].AsArrayView())
- {
- CbObjectView Share = ShareField.AsObjectView();
- Oid ShareId = Share["id"sv].AsObjectId();
- std::filesystem::path SharePath = Share["share_path"sv].AsU8String();
- if (ShareId == Oid::Zero && !SharePath.empty())
- {
- ShareId = PathToIdCB(SharePath);
- }
- std::string_view Alias = Share["alias"sv].AsString();
- if (ShareId != Oid::Zero && !SharePath.empty())
- {
- Shares.push_back({.Id = ShareId, .SharePath = SharePath, .Alias = std::string(Alias)});
- }
- }
- Workspaces.push_back({.Config = {.Id = WorkspaceId, .RootPath = RootPath}, .Shares = std::move(Shares)});
+ Workspaces.push_back(
+ {.Id = WorkspaceId, .RootPath = RootPath, .AllowShareCreationFromHttp = AllowShareCreationFromHttp});
}
}
}
@@ -107,7 +84,78 @@ namespace {
return {};
}
+ std::string WorkspaceSharesToJson(std::span<const Workspaces::WorkspaceShareConfiguration> WorkspaceShares)
+ {
+ using namespace std::literals;
+
+ CbObjectWriter Writer;
+
+ Writer.BeginArray("shares");
+
+ for (const Workspaces::WorkspaceShareConfiguration& Share : WorkspaceShares)
+ {
+ Writer.BeginObject();
+ {
+ Writer.AddObjectId("id"sv, Share.Id);
+ Writer.AddString("share_path"sv, reinterpret_cast<const char*>(Share.SharePath.u8string().c_str()));
+ if (!Share.Alias.empty())
+ {
+ Writer.AddString("alias"sv, Share.Alias);
+ }
+ }
+ Writer.EndObject();
+ }
+ Writer.EndArray();
+ ExtendableStringBuilder<512> Json;
+ Writer.Save().ToJson(Json);
+ return Json.ToString();
+ }
+
+ std::vector<Workspaces::WorkspaceShareConfiguration> WorkspaceSharesFromJson(const IoBuffer& WorkspaceJson, std::string& OutError)
+ {
+ using namespace std::literals;
+
+ CbFieldIterator RootField =
+ LoadCompactBinaryFromJson(std::string_view((const char*)(WorkspaceJson.Data()), WorkspaceJson.GetSize()), OutError);
+ if (OutError.empty())
+ {
+ std::vector<Workspaces::WorkspaceShareConfiguration> Shares;
+ if (CbObjectView RootObject = RootField.AsObjectView(); RootObject)
+ {
+ for (CbFieldView ShareField : RootObject["shares"].AsArrayView())
+ {
+ CbObjectView Share = ShareField.AsObjectView();
+ Oid ShareId = Share["id"sv].AsObjectId();
+ std::filesystem::path SharePath = Share["share_path"sv].AsU8String();
+ if (ShareId == Oid::Zero && !SharePath.empty())
+ {
+ ShareId = Workspaces::PathToId(SharePath);
+ }
+ std::string_view Alias = Share["alias"sv].AsString();
+ if (ShareId != Oid::Zero && !SharePath.empty())
+ {
+ Shares.push_back({.Id = ShareId, .SharePath = SharePath, .Alias = std::string(Alias)});
+ }
+ }
+ }
+ return Shares;
+ }
+ return {};
+ }
+
+ bool IsValidSharePath(const std::filesystem::path& RootPath, const std::filesystem::path& SharePath)
+ {
+ std::filesystem::path FullPath = std::filesystem::absolute(RootPath / SharePath);
+ std::filesystem::path VerifySharePath = std::filesystem::relative(FullPath, RootPath);
+ if (VerifySharePath != SharePath || VerifySharePath.string().starts_with(".."))
+ {
+ return false;
+ }
+ return true;
+ }
+
} // namespace
+
//////////////////////////////////////////////////////////////////////////
class FolderStructure
@@ -151,16 +199,12 @@ private:
class WorkspaceShare : public RefCounted
{
public:
- WorkspaceShare(const Workspaces::WorkspaceShareConfiguration& Config,
- std::unique_ptr<FolderStructure>&& FolderStructure,
- const std::function<Oid(const std::filesystem::path& Path)>& PathToId);
+ WorkspaceShare(const Workspaces::WorkspaceShareConfiguration& Config, std::unique_ptr<FolderStructure>&& FolderStructure);
const Workspaces::WorkspaceShareConfiguration& GetConfig() const;
bool IsInitialized() const { return !!m_FolderStructure; }
- const std::function<Oid(const std::filesystem::path& Path)>& GetPathToIdFunction() const { return m_PathToid; }
-
std::filesystem::path GetAbsolutePath(const std::filesystem::path& RootPath, const Oid& ChunkId, uint64_t& OutSize) const;
const FolderStructure& GetStructure() const
@@ -170,9 +214,8 @@ public:
}
private:
- const Workspaces::WorkspaceShareConfiguration m_Config;
- std::function<Oid(const std::filesystem::path& Path)> m_PathToid;
- std::unique_ptr<FolderStructure> m_FolderStructure;
+ const Workspaces::WorkspaceShareConfiguration m_Config;
+ std::unique_ptr<FolderStructure> m_FolderStructure;
};
//////////////////////////////////////////////////////////////////////////
@@ -180,7 +223,7 @@ private:
class Workspace : public RefCounted
{
public:
- Workspace(LoggerRef& Log, const Workspaces::WorkspaceConfiguration& Config);
+ Workspace(const LoggerRef& Log, const Workspaces::WorkspaceConfiguration& Config);
const Workspaces::WorkspaceConfiguration& GetConfig() const;
std::vector<Ref<WorkspaceShare>> GetShares() const;
@@ -191,7 +234,7 @@ public:
private:
LoggerRef Log() { return m_Log; }
- LoggerRef& m_Log;
+ LoggerRef m_Log;
const Workspaces::WorkspaceConfiguration m_Config;
tsl::robin_map<Oid, Ref<WorkspaceShare>, Oid::Hasher> m_Shares;
};
@@ -211,13 +254,9 @@ FolderStructure::FolderStructure(std::vector<FileEntry>&& InEntries, std::vector
namespace {
struct FolderScanner
{
- FolderScanner(LoggerRef& Log,
- WorkerThreadPool& WorkerPool,
- const std::filesystem::path& Path,
- const std::function<Oid(const std::filesystem::path& Path)>& PathToIdCB)
+ FolderScanner(LoggerRef& Log, WorkerThreadPool& WorkerPool, const std::filesystem::path& Path)
: m_Log(Log)
, Path(Path)
- , PathToIdCB(PathToIdCB)
, WorkLatch(1)
, WorkerPool(WorkerPool)
{
@@ -226,15 +265,14 @@ namespace {
void Traverse();
void Traverse(const std::filesystem::path& RelativeRoot, const std::filesystem::path& Path);
- LoggerRef& Log() { return m_Log; }
- LoggerRef& m_Log;
- const std::filesystem::path Path;
- RwLock WorkLock;
- const std::function<Oid(const std::filesystem::path& Path)>& PathToIdCB;
- std::vector<FolderStructure::FileEntry> FoundFiles;
- std::vector<Oid> FoundFileIds;
- Latch WorkLatch;
- WorkerThreadPool& WorkerPool;
+ LoggerRef& Log() { return m_Log; }
+ LoggerRef& m_Log;
+ const std::filesystem::path Path;
+ RwLock WorkLock;
+ std::vector<FolderStructure::FileEntry> FoundFiles;
+ std::vector<Oid> FoundFileIds;
+ Latch WorkLatch;
+ WorkerThreadPool& WorkerPool;
};
struct Visitor : public FileSystemTraversal::TreeVisitor
@@ -251,7 +289,7 @@ namespace {
{
std::filesystem::path RelativePath = RelativeRoot.empty() ? File : RelativeRoot / File;
Entries.push_back(FolderStructure::FileEntry{.RelativePath = RelativePath, .Size = FileSize});
- FileIds.push_back(Data.PathToIdCB(RelativePath));
+ FileIds.push_back(Workspaces::PathToId(RelativePath));
}
virtual bool VisitDirectory(const std::filesystem::path& Parent, const path_view& DirectoryName)
@@ -298,27 +336,21 @@ namespace {
} // namespace
std::unique_ptr<FolderStructure>
-ScanFolder(LoggerRef InLog,
- const std::filesystem::path& Path,
- const std::function<Oid(const std::filesystem::path& Path)>& PathToIdCB,
- WorkerThreadPool& WorkerPool)
+ScanFolder(LoggerRef InLog, const std::filesystem::path& Path, WorkerThreadPool& WorkerPool)
{
ZEN_TRACE_CPU("workspaces::ScanFolderImpl");
auto Log = [&InLog]() { return InLog; };
- FolderScanner Data(InLog, WorkerPool, Path, PathToIdCB);
+ FolderScanner Data(InLog, WorkerPool, Path);
Data.Traverse();
return std::make_unique<FolderStructure>(std::move(Data.FoundFiles), std::move(Data.FoundFileIds));
}
////////////////////////////////////////////////////////////
-WorkspaceShare::WorkspaceShare(const Workspaces::WorkspaceShareConfiguration& Config,
- std::unique_ptr<FolderStructure>&& FolderStructure,
- const std::function<Oid(const std::filesystem::path& Path)>& PathToId)
+WorkspaceShare::WorkspaceShare(const Workspaces::WorkspaceShareConfiguration& Config, std::unique_ptr<FolderStructure>&& FolderStructure)
: m_Config(Config)
-, m_PathToid(PathToId)
, m_FolderStructure(std::move(FolderStructure))
{
}
@@ -344,7 +376,7 @@ WorkspaceShare::GetConfig() const
////////////////////////////////////////////////////////////
-Workspace::Workspace(LoggerRef& Log, const Workspaces::WorkspaceConfiguration& Config) : m_Log(Log), m_Config(Config)
+Workspace::Workspace(const LoggerRef& Log, const Workspaces::WorkspaceConfiguration& Config) : m_Log(Log), m_Config(Config)
{
}
@@ -398,184 +430,112 @@ Workspaces::~Workspaces()
{
}
-bool
-Workspaces::AddWorkspace(const WorkspaceConfiguration& Configuration)
-{
- Ref<Workspace> NewWorkspace(new Workspace(m_Log, Configuration));
-
- RwLock::ExclusiveLockScope Lock(m_Lock);
- if (m_Workspaces.contains(Configuration.Id))
- {
- return false;
- }
-
- const std::filesystem::path RootPath = Configuration.RootPath;
- if (RootPath.is_relative())
- {
- throw std::invalid_argument(fmt::format("workspace root path '{}' is not an absolute path", RootPath));
- }
-
- m_Workspaces.insert(std::make_pair(Configuration.Id, NewWorkspace));
- ZEN_INFO("Created workspace '{}' with root '{}'", Configuration.Id, Configuration.RootPath);
- return true;
-}
-
-Workspaces::WorkspaceConfiguration
-Workspaces::GetWorkspaceConfiguration(const Oid& WorkspaceId) const
+void
+Workspaces::RefreshWorkspaceShares(const Oid& WorkspaceId)
{
- RwLock::SharedLockScope Lock(m_Lock);
- Ref<Workspace> Workspace = FindWorkspace(Lock, WorkspaceId);
- if (Workspace)
- {
- return Workspace->GetConfig();
- }
- return {};
-}
+ using namespace std::literals;
-Workspaces::WorkspaceInfo
-Workspaces::GetWorkspaceInfo(const Oid& WorkspaceId) const
-{
Ref<Workspace> Workspace;
- std::vector<Ref<WorkspaceShare>> Shares;
+ tsl::robin_set<Oid, Oid::Hasher> DeletedShares;
{
RwLock::SharedLockScope Lock(m_Lock);
Workspace = FindWorkspace(Lock, WorkspaceId);
if (Workspace)
{
- Shares = Workspace->GetShares();
- }
- }
- if (!Workspace)
- {
- return {};
- }
-
- WorkspaceInfo Info = {.Config = Workspace->GetConfig()};
- Info.Shares.reserve(Shares.size());
- for (const Ref<WorkspaceShare>& Share : Shares)
- {
- Info.Shares.push_back(Share->GetConfig());
- }
- return Info;
-}
-
-bool
-Workspaces::RemoveWorkspace(const Oid& WorkspaceId)
-{
- RwLock::ExclusiveLockScope Lock(m_Lock);
- if (auto It = m_Workspaces.find(WorkspaceId); It != m_Workspaces.end())
- {
- std::vector<std::string> Aliases;
- for (const auto& AliasIt : m_ShareAliases)
- {
- if (AliasIt.second.WorkspaceId == WorkspaceId)
+ for (auto Share : Workspace->GetShares())
{
- Aliases.push_back(AliasIt.first);
+ DeletedShares.insert(Share->GetConfig().Id);
}
}
-
- for (const std::string& Alias : Aliases)
- {
- m_ShareAliases.erase(Alias);
- }
-
- m_Workspaces.erase(It);
-
- ZEN_INFO("Removed workspace '{}' and {} aliases", WorkspaceId, Aliases.size());
- return true;
}
- return false;
-}
-bool
-Workspaces::AddWorkspaceShare(const Oid& WorkspaceId,
- const WorkspaceShareConfiguration& Configuration,
- const std::function<Oid(const std::filesystem::path& Path)>& PathToIdCB)
-{
- Ref<Workspace> Workspace;
+ if (Workspace)
{
- RwLock::SharedLockScope Lock(m_Lock);
- Workspace = FindWorkspace(Lock, WorkspaceId);
- if (!Workspace)
- {
- return false;
- }
- if (Workspace->GetShare(Configuration.Id))
+ const std::filesystem::path& RootPath = Workspace->GetConfig().RootPath;
+ std::filesystem::path ConfigPath = RootPath / WorkspaceConfigName;
+ if (std::filesystem::exists(ConfigPath))
{
- return false;
- }
- }
-
- const std::filesystem::path RootPath = Workspace->GetConfig().RootPath;
- const std::filesystem::path SharePath = Configuration.SharePath;
+ std::string Error;
+ std::vector<Workspaces::WorkspaceShareConfiguration> WorkspaceShares = ReadWorkspaceConfig(m_Log, RootPath, Error);
+ if (!Error.empty())
+ {
+ ZEN_WARN("Failed to read workspace state from {}. Reason: '{}'", ConfigPath, Error);
+ }
+ else
+ {
+ for (const Workspaces::WorkspaceShareConfiguration& Configuration : WorkspaceShares)
+ {
+ const std::filesystem::path& SharePath = Configuration.SharePath;
- std::filesystem::path FullPath = std::filesystem::absolute(RootPath / SharePath);
- std::filesystem::path VerifySharePath = std::filesystem::relative(FullPath, RootPath);
- if (VerifySharePath != Configuration.SharePath || VerifySharePath.string().starts_with(".."))
- {
- throw std::invalid_argument(fmt::format("workspace share path '{}' is not valid for root path '{}'", SharePath, RootPath));
- }
+ if (std::filesystem::is_directory(RootPath / SharePath))
+ {
+ DeletedShares.erase(Configuration.Id);
- Ref<WorkspaceShare> NewShare(new WorkspaceShare(Configuration, {}, PathToIdCB));
- {
- RwLock::ExclusiveLockScope _(m_Lock);
- Workspace->SetShare(Configuration.Id, std::move(NewShare));
- if (!Configuration.Alias.empty())
- {
- m_ShareAliases.insert_or_assign(Configuration.Alias, ShareAlias{.WorkspaceId = WorkspaceId, .ShareId = Configuration.Id});
- }
- }
+ if (!IsValidSharePath(RootPath, SharePath))
+ {
+ ZEN_WARN("Skipping workspace share path '{}' as it is not valid for root path '{}'", SharePath, RootPath);
+ }
+ else
+ {
+ Ref<WorkspaceShare> NewShare(new WorkspaceShare(Configuration, {}));
- ZEN_INFO("Added workspace share '{}' in workspace '{}' with path '{}'", Configuration.Id, WorkspaceId, Configuration.SharePath);
+ RwLock::ExclusiveLockScope _(m_Lock);
+ if (Ref<WorkspaceShare> Share = Workspace->GetShare(Configuration.Id); Share)
+ {
+ if (Share->GetConfig() != Configuration)
+ {
+ if (!Share->GetConfig().Alias.empty())
+ {
+ m_ShareAliases.erase(Share->GetConfig().Alias);
+ }
+ Workspace->SetShare(Configuration.Id, std::move(NewShare));
+ }
+ }
+ else
+ {
+ Workspace->SetShare(Configuration.Id, std::move(NewShare));
+ if (!Configuration.Alias.empty())
+ {
+ m_ShareAliases.insert_or_assign(Configuration.Alias,
+ ShareAlias{.WorkspaceId = WorkspaceId, .ShareId = Configuration.Id});
+ }
+ }
+ }
+ }
+ else
+ {
+ ZEN_INFO("Skipping workspace share path '{}' that does not exist in root path '{}'", SharePath, RootPath);
+ }
+ }
+ }
+ if (!DeletedShares.empty())
+ {
+ RwLock::ExclusiveLockScope _(m_Lock);
+ for (const Oid& ShareId : DeletedShares)
+ {
+ Ref<WorkspaceShare> ExistingShare = Workspace->GetShare(ShareId);
+ if (ExistingShare)
+ {
+ std::string Alias = ExistingShare->GetConfig().Alias;
+ if (!Alias.empty())
+ {
+ if (auto AliasIt = m_ShareAliases.find(Alias); AliasIt != m_ShareAliases.end())
+ {
+ if (AliasIt->second.WorkspaceId == Workspace->GetConfig().Id && AliasIt->second.ShareId == ShareId)
+ {
+ m_ShareAliases.erase(Alias);
+ }
+ }
+ }
- return true;
-}
+ Workspace->SetShare(ShareId, {});
-Workspaces::WorkspaceShareConfiguration
-Workspaces::GetWorkspaceShareConfiguration(const Oid& WorkspaceId, const Oid& ShareId) const
-{
- RwLock::SharedLockScope Lock(m_Lock);
- Ref<Workspace> Workspace = FindWorkspace(Lock, WorkspaceId);
- if (Workspace)
- {
- Ref<WorkspaceShare> Share = Workspace->GetShare(ShareId);
- if (Share)
- {
- return Share->GetConfig();
+ ZEN_DEBUG("Removed workspace share '{}' in workspace '{}'", ShareId, WorkspaceId);
+ }
+ }
+ }
}
}
- return {};
-}
-
-bool
-Workspaces::RemoveWorkspaceShare(const Oid& WorkspaceId, const Oid& ShareId)
-{
- Ref<Workspace> Workspace;
- {
- RwLock::SharedLockScope Lock(m_Lock);
- Workspace = FindWorkspace(Lock, WorkspaceId);
- if (!Workspace)
- {
- return false;
- }
- }
- RwLock::ExclusiveLockScope _(m_Lock);
- Ref<WorkspaceShare> ExistingShare = Workspace->GetShare(ShareId);
- if (!ExistingShare)
- {
- return false;
- }
-
- std::string Alias = ExistingShare->GetConfig().Alias;
- if (!Alias.empty())
- {
- m_ShareAliases.erase(Alias);
- }
-
- Workspace->SetShare(ShareId, {});
-
- ZEN_INFO("Removed workspace share '{}' in workspace '{}'", ShareId, WorkspaceId);
- return true;
}
std::optional<std::vector<Workspaces::ShareFile>>
@@ -682,72 +642,148 @@ Workspaces::GetWorkspaceShareChunks(const Oid& WorkspaceId,
return Chunks;
}
-void
-Workspaces::WriteState(const std::filesystem::path& WorkspaceStatePath)
+std::vector<Oid>
+Workspaces::GetWorkspaces() const
{
- using namespace std::literals;
+ std::vector<Oid> Workspaces;
+ RwLock::SharedLockScope Lock(m_Lock);
+ for (auto It : m_Workspaces)
+ {
+ Workspaces.push_back(It.first);
+ }
+ return Workspaces;
+}
- ZEN_INFO("Writing workspaces state to {}", WorkspaceStatePath);
+std::optional<Workspaces::WorkspaceConfiguration>
+Workspaces::GetWorkspaceConfiguration(const Oid& WorkspaceId) const
+{
+ Ref<Workspace> Workspace;
+ {
+ RwLock::SharedLockScope Lock(m_Lock);
+ Workspace = FindWorkspace(Lock, WorkspaceId);
+ }
+ if (Workspace)
+ {
+ return Workspace->GetConfig();
+ }
+ return {};
+}
- CreateDirectories(WorkspaceStatePath);
+std::optional<std::vector<Oid>>
+Workspaces::GetWorkspaceShares(const Oid& WorkspaceId) const
+{
+ RwLock::SharedLockScope Lock(m_Lock);
+ Ref<Workspace> Workspace = FindWorkspace(Lock, WorkspaceId);
+ if (Workspace)
+ {
+ std::vector<Oid> Shares;
+ for (auto Share : Workspace->GetShares())
+ {
+ Shares.push_back(Share->GetConfig().Id);
+ }
+ return Shares;
+ }
+ return {};
+}
- std::vector<WorkspaceInfo> Workspaces;
+std::optional<Workspaces::WorkspaceShareConfiguration>
+Workspaces::GetWorkspaceShareConfiguration(const Oid& WorkspaceId, const Oid& ShareId) const
+{
+ RwLock::SharedLockScope Lock(m_Lock);
+ Ref<Workspace> Workspace = FindWorkspace(Lock, WorkspaceId);
+ if (Workspace)
{
- RwLock::SharedLockScope _(m_Lock);
- for (auto WorkspaceIt : m_Workspaces)
+ Ref<WorkspaceShare> Share = Workspace->GetShare(ShareId);
+ if (Share)
{
- std::vector<Workspaces::WorkspaceShareConfiguration> Shares;
- for (auto ShareIt : WorkspaceIt.second->GetShares())
- {
- Shares.push_back(ShareIt->GetConfig());
- }
- Workspaces.push_back({.Config = WorkspaceIt.second->GetConfig(), .Shares = std::move(Shares)});
+ return Share->GetConfig();
}
}
- std::string ConfigJson = WorkspacesToJson(Workspaces);
- TemporaryFile::SafeWriteFile(WorkspaceStatePath / "config.json", MemoryView(ConfigJson.data(), ConfigJson.size()));
+ return {};
}
void
-Workspaces::ReadState(const std::filesystem::path& WorkspaceStatePath,
- const std::function<Oid(const std::filesystem::path& Path)>& PathToIdCB)
+Workspaces::RefreshState(const std::filesystem::path& WorkspaceStatePath)
{
using namespace std::literals;
- std::string Error;
- std::vector<Workspaces::WorkspaceInfo> Workspaces =
- WorkspacesFromJson(IoBufferBuilder::MakeFromFile(WorkspaceStatePath / "config.json"sv), PathToIdCB, Error);
+ std::string Error;
+ std::vector<Workspaces::WorkspaceConfiguration> Workspaces = ReadConfig(Log(), WorkspaceStatePath, Error);
+
if (!Error.empty())
{
- ZEN_WARN("Failed to read workspace state from {}. Reason: '{}'", WorkspaceStatePath / "config.json"sv, Error);
+ ZEN_WARN("Failed to read workspaces state from {}. Reason: '{}'", WorkspaceStatePath, Error);
}
else
{
- for (const Workspaces::WorkspaceInfo& Workspace : Workspaces)
+ tsl::robin_set<Oid, Oid::Hasher> DeletedWorkspaces;
{
+ RwLock::SharedLockScope Lock(m_Lock);
+ for (const auto& Workspace : m_Workspaces)
+ {
+ DeletedWorkspaces.insert(Workspace.second->GetConfig().Id);
+ }
+ }
+
+ for (const Workspaces::WorkspaceConfiguration& Configuration : Workspaces)
+ {
+ DeletedWorkspaces.erase(Configuration.Id);
try
{
- if (AddWorkspace(Workspace.Config))
+ const std::filesystem::path& RootPath = Configuration.RootPath;
+ if (RootPath.is_relative())
+ {
+ throw std::invalid_argument(fmt::format("workspace root path '{}' is not an absolute path", RootPath));
+ }
+ else
{
- for (const Workspaces::WorkspaceShareConfiguration& Share : Workspace.Shares)
+ Ref<Workspace> NewWorkspace(new Workspace(m_Log, Configuration));
{
- try
+ RwLock::ExclusiveLockScope Lock(m_Lock);
+ if (auto It = m_Workspaces.find(Configuration.Id); It != m_Workspaces.end())
{
- (void)AddWorkspaceShare(Workspace.Config.Id, Share, PathToIdCB);
+ Ref<Workspace> Workspace(It->second);
+ if (Workspace->GetConfig() != Configuration)
+ {
+ RemoveWorkspace(Lock, Configuration.Id);
+ m_Workspaces.insert(std::make_pair(Configuration.Id, std::move(NewWorkspace)));
+ ZEN_DEBUG("Replaced workspace '{}' with root '{}'", Configuration.Id, Configuration.RootPath);
+ }
}
- catch (const std::exception& Ex)
+ else
{
- ZEN_WARN("Failed adding workspace share '{}' for workspace '{}'. Reason: '{}'",
- Workspace.Config.Id,
- Share.Id,
- Ex.what());
+ m_Workspaces.insert(std::make_pair(Configuration.Id, std::move(NewWorkspace)));
+ ZEN_DEBUG("Created workspace '{}' with root '{}'", Configuration.Id, Configuration.RootPath);
}
}
+
+ try
+ {
+ RefreshWorkspaceShares(Configuration.Id);
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_WARN("Failed refreshing workspace shares for workspace '{}'. Reason: '{}'", Configuration.Id, Ex.what());
+ }
}
}
catch (const std::exception& Ex)
{
- ZEN_WARN("Failed adding workspace '{}'. Reason: '{}'", Workspace.Config.Id, Ex.what());
+ ZEN_WARN("Failed adding workspace '{}'. Reason: '{}'", Configuration.Id, Ex.what());
+ }
+ }
+ {
+ RwLock::ExclusiveLockScope Lock(m_Lock);
+ for (const Oid& WorkspaceId : DeletedWorkspaces)
+ {
+ try
+ {
+ RemoveWorkspace(Lock, WorkspaceId);
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_WARN("Failed removing workspace '{}'. Reason: '{}'", WorkspaceId, Ex.what());
+ }
}
}
}
@@ -764,6 +800,425 @@ Workspaces::GetShareAlias(std::string_view Alias) const
return {};
}
+std::vector<Workspaces::WorkspaceConfiguration>
+Workspaces::ReadConfig(const LoggerRef& InLog, const std::filesystem::path& WorkspaceStatePath, std::string& OutError)
+{
+ auto Log = [&InLog]() { return InLog; };
+
+ using namespace std::literals;
+
+ ZEN_DEBUG("Reading workspaces state from {}", WorkspaceStatePath);
+
+ const std::filesystem::path ConfigPath = WorkspaceStatePath / WorkspacesConfigName;
+ if (std::filesystem::exists(ConfigPath))
+ {
+ std::vector<Workspaces::WorkspaceConfiguration> Workspaces =
+ WorkspacesFromJson(IoBufferBuilder::MakeFromFile(ConfigPath), OutError);
+ if (OutError.empty())
+ {
+ return Workspaces;
+ }
+ }
+ return {};
+}
+
+void
+Workspaces::WriteConfig(const LoggerRef& InLog,
+ const std::filesystem::path& WorkspaceStatePath,
+ const std::vector<WorkspaceConfiguration>& WorkspaceConfigurations)
+{
+ auto Log = [&InLog]() { return InLog; };
+
+ using namespace std::literals;
+
+ ZEN_DEBUG("Writing workspaces state to {}", WorkspaceStatePath);
+
+ CreateDirectories(WorkspaceStatePath);
+
+ std::string ConfigJson = WorkspacesToJson(WorkspaceConfigurations);
+ TemporaryFile::SafeWriteFile(WorkspaceStatePath / WorkspacesConfigName, MemoryView(ConfigJson.data(), ConfigJson.size()));
+}
+
+std::vector<Workspaces::WorkspaceShareConfiguration>
+Workspaces::ReadWorkspaceConfig(const LoggerRef& InLog, const std::filesystem::path& WorkspaceRoot, std::string& OutError)
+{
+ auto Log = [&InLog]() { return InLog; };
+
+ using namespace std::literals;
+
+ ZEN_DEBUG("Reading workspace state from {}", WorkspaceRoot);
+
+ std::filesystem::path ConfigPath = WorkspaceRoot / WorkspaceConfigName;
+ if (std::filesystem::exists(ConfigPath))
+ {
+ std::vector<Workspaces::WorkspaceShareConfiguration> WorkspaceShares =
+ WorkspaceSharesFromJson(IoBufferBuilder::MakeFromFile(ConfigPath), OutError);
+ if (OutError.empty())
+ {
+ return WorkspaceShares;
+ }
+ }
+ return {};
+}
+
+void
+Workspaces::WriteWorkspaceConfig(const LoggerRef& InLog,
+ const std::filesystem::path& WorkspaceRoot,
+ const std::vector<WorkspaceShareConfiguration>& WorkspaceShareConfigurations)
+{
+ auto Log = [&InLog]() { return InLog; };
+
+ using namespace std::literals;
+
+ ZEN_DEBUG("Writing workspace state to {}", WorkspaceRoot);
+
+ std::string ConfigJson = WorkspaceSharesToJson(WorkspaceShareConfigurations);
+ TemporaryFile::SafeWriteFile(WorkspaceRoot / WorkspaceConfigName, MemoryView(ConfigJson.data(), ConfigJson.size()));
+}
+
+bool
+Workspaces::AddWorkspace(const LoggerRef& Log, const std::filesystem::path& WorkspaceStatePath, const WorkspaceConfiguration& Configuration)
+{
+ if (Configuration.Id == Oid::Zero)
+ {
+ throw std::invalid_argument(
+ fmt::format("invalid workspace id {} for workspace with root '{}'", Configuration.Id, Configuration.RootPath));
+ }
+ if (Configuration.RootPath.is_relative())
+ {
+ throw std::invalid_argument(fmt::format("invalid root path '{}' for workspace {}", Configuration.RootPath, Configuration.Id));
+ }
+ std::string Error;
+ std::vector<WorkspaceConfiguration> WorkspaceConfigurations = ReadConfig(Log, WorkspaceStatePath, Error);
+ if (!Error.empty())
+ {
+ throw std::invalid_argument(
+ fmt::format("failed to read workspaces configuration from '{}'. Reason: '{}'", WorkspaceStatePath, Error));
+ }
+
+ if (auto It =
+ std::find_if(WorkspaceConfigurations.begin(),
+ WorkspaceConfigurations.end(),
+ [RootPath = Configuration.RootPath](const WorkspaceConfiguration& Config) { return Config.RootPath == RootPath; });
+ It != WorkspaceConfigurations.end())
+ {
+ if (It->Id != Configuration.Id)
+ {
+ throw std::invalid_argument(fmt::format("root path '{}' is already used in workspace {}", Configuration.RootPath, It->Id));
+ }
+ }
+
+ if (auto It = std::find_if(WorkspaceConfigurations.begin(),
+ WorkspaceConfigurations.end(),
+ [Id = Configuration.Id](const WorkspaceConfiguration& Config) { return Config.Id == Id; });
+ It != WorkspaceConfigurations.end())
+ {
+ *It = Configuration;
+ WriteConfig(Log, WorkspaceStatePath, WorkspaceConfigurations);
+ return false;
+ }
+ else
+ {
+ WorkspaceConfigurations.push_back(Configuration);
+ WriteConfig(Log, WorkspaceStatePath, WorkspaceConfigurations);
+ return true;
+ }
+}
+
+bool
+Workspaces::RemoveWorkspace(const LoggerRef& Log, const std::filesystem::path& WorkspaceStatePath, const Oid& WorkspaceId)
+{
+ std::string Error;
+ std::vector<WorkspaceConfiguration> WorkspaceConfigurations = ReadConfig(Log, WorkspaceStatePath, Error);
+ if (!Error.empty())
+ {
+ throw std::invalid_argument(
+ fmt::format("failed to read workspaces configuration from '{}'. Reason: '{}'", WorkspaceStatePath, Error));
+ }
+ if (auto It = std::find_if(WorkspaceConfigurations.begin(),
+ WorkspaceConfigurations.end(),
+ [WorkspaceId](const WorkspaceConfiguration& Config) { return Config.Id == WorkspaceId; });
+ It != WorkspaceConfigurations.end())
+ {
+ WorkspaceConfigurations.erase(It);
+ WriteConfig(Log, WorkspaceStatePath, WorkspaceConfigurations);
+ return true;
+ }
+ return false;
+}
+
+bool
+Workspaces::AddWorkspaceShare(const LoggerRef& Log,
+ const std::filesystem::path& WorkspaceRoot,
+ const WorkspaceShareConfiguration& Configuration)
+{
+ if (Configuration.Id == Oid::Zero)
+ {
+ throw std::invalid_argument(
+ fmt::format("invalid workspace share id {} for workspace with root '{}'", Configuration.Id, Configuration.SharePath));
+ }
+ if (!IsValidSharePath(WorkspaceRoot, Configuration.SharePath))
+ {
+ throw std::invalid_argument(
+ fmt::format("workspace share path '{}' is not a sub-path of workspace path '{}'", Configuration.SharePath, WorkspaceRoot));
+ }
+ if (!std::filesystem::is_directory(WorkspaceRoot / Configuration.SharePath))
+ {
+ throw std::invalid_argument(
+ fmt::format("workspace share path '{}' does not exist in workspace path '{}'", Configuration.SharePath, WorkspaceRoot));
+ }
+ if (!AsciiSet::HasOnly(Configuration.Alias, ValidAliasCharactersSet))
+ {
+ throw std::invalid_argument(
+ fmt::format("invalid workspace share alias '{}' for workspace share {}", Configuration.Alias, Configuration.Id));
+ }
+
+ std::string Error;
+ std::vector<WorkspaceShareConfiguration> WorkspaceShareConfigurations = ReadWorkspaceConfig(Log, WorkspaceRoot, Error);
+ if (!Error.empty())
+ {
+ throw std::invalid_argument(fmt::format("failed to read workspace configuration from '{}'. Reason: '{}'", WorkspaceRoot, Error));
+ }
+
+ if (auto It = std::find_if(
+ WorkspaceShareConfigurations.begin(),
+ WorkspaceShareConfigurations.end(),
+ [SharePath = Configuration.SharePath](const WorkspaceShareConfiguration& Config) { return Config.SharePath == SharePath; });
+ It != WorkspaceShareConfigurations.end())
+ {
+ if (It->Id != Configuration.Id)
+ {
+ throw std::invalid_argument(
+ fmt::format("share path '{}' is already used in workspace as share {}", Configuration.SharePath, It->Id));
+ }
+ }
+
+ if (auto It = std::find_if(WorkspaceShareConfigurations.begin(),
+ WorkspaceShareConfigurations.end(),
+ [Id = Configuration.Id](const WorkspaceShareConfiguration& Config) { return Config.Id == Id; });
+ It != WorkspaceShareConfigurations.end())
+ {
+ if (*It == Configuration)
+ {
+ return false;
+ }
+ *It = Configuration;
+ }
+ else
+ {
+ WorkspaceShareConfigurations.push_back(Configuration);
+ }
+ WriteWorkspaceConfig(Log, WorkspaceRoot, WorkspaceShareConfigurations);
+ return true;
+}
+
+bool
+Workspaces::RemoveWorkspaceShare(const LoggerRef& Log, const std::filesystem::path& WorkspaceRoot, const Oid& WorkspaceShareId)
+{
+ std::string Error;
+ std::vector<WorkspaceShareConfiguration> WorkspaceShareConfigurations = ReadWorkspaceConfig(Log, WorkspaceRoot, Error);
+ if (!Error.empty())
+ {
+ throw std::invalid_argument(fmt::format("failed to read workspace configuration from '{}'. Reason: '{}'", WorkspaceRoot, Error));
+ }
+ if (auto It = std::find_if(WorkspaceShareConfigurations.begin(),
+ WorkspaceShareConfigurations.end(),
+ [WorkspaceShareId](const WorkspaceShareConfiguration& Config) { return Config.Id == WorkspaceShareId; });
+ It != WorkspaceShareConfigurations.end())
+ {
+ WorkspaceShareConfigurations.erase(It);
+ WriteWorkspaceConfig(Log, WorkspaceRoot, WorkspaceShareConfigurations);
+ return true;
+ }
+ return false;
+}
+
+Workspaces::WorkspaceConfiguration
+Workspaces::FindWorkspace(const LoggerRef& InLog, const std::filesystem::path& WorkspaceStatePath, const Oid& WorkspaceId)
+{
+ auto Log = [&InLog]() { return InLog; };
+
+ std::string Error;
+ std::vector<WorkspaceConfiguration> Workspaces = ReadConfig(InLog, WorkspaceStatePath, Error);
+ if (!Error.empty())
+ {
+ throw std::invalid_argument(
+ fmt::format("failed to read workspaces configuration from '{}'. Reason: '{}'", WorkspaceStatePath, Error));
+ }
+
+ for (const WorkspaceConfiguration& WorkspaceConfig : Workspaces)
+ {
+ if (WorkspaceConfig.Id == WorkspaceId)
+ {
+ return WorkspaceConfig;
+ }
+ }
+ return {};
+}
+
+Workspaces::WorkspaceConfiguration
+Workspaces::FindWorkspace(const LoggerRef& InLog,
+ const std::filesystem::path& WorkspaceStatePath,
+ const std::filesystem::path& WorkspaceRoot)
+{
+ auto Log = [&InLog]() { return InLog; };
+
+ std::string Error;
+ std::vector<WorkspaceConfiguration> Workspaces = ReadConfig(InLog, WorkspaceStatePath, Error);
+ if (!Error.empty())
+ {
+ throw std::invalid_argument(
+ fmt::format("failed to read workspaces configuration from '{}'. Reason: '{}'", WorkspaceStatePath, Error));
+ }
+
+ for (const WorkspaceConfiguration& WorkspaceConfig : Workspaces)
+ {
+ if (WorkspaceConfig.RootPath == WorkspaceRoot)
+ {
+ return WorkspaceConfig;
+ }
+ }
+ return {};
+}
+
+Workspaces::WorkspaceShareConfiguration
+Workspaces::FindWorkspaceShare(const LoggerRef& InLog,
+ const std::filesystem::path& WorkspaceStatePath,
+ std::string_view ShareAlias,
+ WorkspaceConfiguration& OutWorkspace)
+{
+ auto Log = [&InLog]() { return InLog; };
+
+ std::string Error;
+ std::vector<WorkspaceConfiguration> Workspaces = ReadConfig(InLog, WorkspaceStatePath, Error);
+ if (!Error.empty())
+ {
+ throw std::invalid_argument(
+ fmt::format("failed to read workspaces configuration from '{}'. Reason: '{}'", WorkspaceStatePath, Error));
+ }
+
+ for (const WorkspaceConfiguration& WorkspaceConfig : Workspaces)
+ {
+ std::vector<WorkspaceShareConfiguration> Shares = ReadWorkspaceConfig(InLog, WorkspaceConfig.RootPath, Error);
+ if (!Error.empty())
+ {
+ ZEN_WARN("Invalid workspace config in workspace root '{}'", WorkspaceConfig.RootPath);
+ }
+ else
+ {
+ for (const WorkspaceShareConfiguration& ShareConfig : Shares)
+ {
+ if (ShareConfig.Alias == ShareAlias)
+ {
+ OutWorkspace = WorkspaceConfig;
+ return ShareConfig;
+ }
+ }
+ }
+ }
+ return {};
+}
+
+Workspaces::WorkspaceShareConfiguration
+Workspaces::FindWorkspaceShare(const LoggerRef& InLog,
+ const std::filesystem::path& WorkspaceStatePath,
+ const Oid& WorkspaceId,
+ const Oid& WorkspaceShareId)
+{
+ WorkspaceConfiguration Workspace = FindWorkspace(InLog, WorkspaceStatePath, WorkspaceId);
+ if (Workspace.Id == Oid::Zero)
+ {
+ return {};
+ }
+ return FindWorkspaceShare(InLog, Workspace.RootPath, WorkspaceShareId);
+}
+
+Workspaces::WorkspaceShareConfiguration
+Workspaces::FindWorkspaceShare(const LoggerRef& InLog, const std::filesystem::path& WorkspaceRoot, const Oid& WorkspaceShareId)
+{
+ auto Log = [&InLog]() { return InLog; };
+ std::string Error;
+ std::vector<WorkspaceShareConfiguration> Shares = ReadWorkspaceConfig(InLog, WorkspaceRoot, Error);
+ if (!Error.empty())
+ {
+ ZEN_WARN("Invalid workspace config in workspace root '{}'", WorkspaceRoot);
+ }
+ else
+ {
+ for (const WorkspaceShareConfiguration& ShareConfig : Shares)
+ {
+ if (ShareConfig.Id == WorkspaceShareId)
+ {
+ return ShareConfig;
+ }
+ }
+ }
+ return {};
+}
+
+Workspaces::WorkspaceShareConfiguration
+Workspaces::FindWorkspaceShare(const LoggerRef& InLog, const std::filesystem::path& WorkspaceRoot, const std::filesystem::path& SharePath)
+{
+ auto Log = [&InLog]() { return InLog; };
+ std::string Error;
+ std::vector<WorkspaceShareConfiguration> Shares = ReadWorkspaceConfig(InLog, WorkspaceRoot, Error);
+ if (!Error.empty())
+ {
+ ZEN_WARN("Invalid workspace config in workspace root '{}'", WorkspaceRoot);
+ }
+ else
+ {
+ for (const WorkspaceShareConfiguration& ShareConfig : Shares)
+ {
+ if (ShareConfig.SharePath == SharePath)
+ {
+ return ShareConfig;
+ }
+ }
+ }
+ return {};
+}
+
+Oid
+Workspaces::PathToId(const std::filesystem::path& Path)
+{
+ std::string PathBuffer = reinterpret_cast<const char*>(Path.generic_u8string().c_str());
+ if (PathBuffer.ends_with('/'))
+ {
+ PathBuffer.pop_back();
+ }
+ BLAKE3 Hash = BLAKE3::HashMemory(PathBuffer.data(), PathBuffer.size());
+ Hash.Hash[11] = 7; // FIoChunkType::ExternalFile
+ return Oid::FromMemory(Hash.Hash);
+}
+
+bool
+Workspaces::RemoveWorkspace(RwLock::ExclusiveLockScope&, const Oid& WorkspaceId)
+{
+ if (auto It = m_Workspaces.find(WorkspaceId); It != m_Workspaces.end())
+ {
+ std::vector<std::string> Aliases;
+ for (const auto& AliasIt : m_ShareAliases)
+ {
+ if (AliasIt.second.WorkspaceId == WorkspaceId)
+ {
+ Aliases.push_back(AliasIt.first);
+ }
+ }
+
+ for (const std::string& Alias : Aliases)
+ {
+ m_ShareAliases.erase(Alias);
+ }
+
+ m_Workspaces.erase(It);
+
+ ZEN_DEBUG("Removed workspace '{}' and {} aliases", WorkspaceId, Aliases.size());
+ return true;
+ }
+ return false;
+}
+
std::pair<Ref<Workspace>, Ref<WorkspaceShare>>
Workspaces::FindWorkspaceShare(const Oid& WorkspaceId, const Oid& ShareId, bool ForceRefresh, WorkerThreadPool& WorkerPool)
{
@@ -783,37 +1238,44 @@ Workspaces::FindWorkspaceShare(const Oid& WorkspaceId, const Oid& ShareId, bool
}
}
- if (ForceRefresh || !Share->IsInitialized())
+ const Workspaces::WorkspaceConfiguration& WorkspaceConfig = Workspace->GetConfig();
+ const Workspaces::WorkspaceShareConfiguration& ShareConfig = Share->GetConfig();
+ std::filesystem::path FullSharePath = WorkspaceConfig.RootPath / ShareConfig.SharePath;
+ if (std::filesystem::is_directory(FullSharePath))
{
- Workspaces::WorkspaceShareConfiguration Config = Share->GetConfig();
- std::filesystem::path RootPath = Workspace->GetConfig().RootPath;
- std::function<Oid(const std::filesystem::path& Path)> PathToIdCB = Share->GetPathToIdFunction();
- std::unique_ptr<FolderStructure> NewStructure = ScanFolder(Log(), RootPath / Config.SharePath, PathToIdCB, WorkerPool);
- if (NewStructure)
+ if (ForceRefresh || !Share->IsInitialized())
{
- Share = Ref<WorkspaceShare>(new WorkspaceShare(Config, std::move(NewStructure), std::move(PathToIdCB)));
+ std::unique_ptr<FolderStructure> NewStructure = ScanFolder(Log(), FullSharePath, WorkerPool);
+ if (NewStructure)
{
- RwLock::ExclusiveLockScope _(m_Lock);
- Workspace->SetShare(ShareId, Ref<WorkspaceShare>(Share));
+ Share = Ref<WorkspaceShare>(new WorkspaceShare(ShareConfig, std::move(NewStructure)));
+ {
+ RwLock::ExclusiveLockScope _(m_Lock);
+ Workspace->SetShare(ShareId, Ref<WorkspaceShare>(Share));
+ }
}
- }
- else
- {
- if (!Share->IsInitialized())
+ else
{
ZEN_WARN("Failed to scan folder {} for share {} in workspace {}, treating it as an empty share",
- WorkspaceId,
+ FullSharePath,
ShareId,
- RootPath / Config.SharePath);
- Share = Ref<WorkspaceShare>(new WorkspaceShare(Config, std::move(NewStructure), std::move(PathToIdCB)));
+ WorkspaceId);
+ Share = Ref<WorkspaceShare>(new WorkspaceShare(ShareConfig, std::make_unique<FolderStructure>()));
{
RwLock::ExclusiveLockScope _(m_Lock);
Workspace->SetShare(ShareId, Ref<WorkspaceShare>(Share));
}
}
}
+ return {std::move(Workspace), std::move(Share)};
+ }
+ else
+ {
+ ZEN_WARN("Folder {} for share {} in workspace {} does not exist, removing the share", FullSharePath, ShareId, WorkspaceId);
+ RwLock::ExclusiveLockScope _(m_Lock);
+ Workspace->SetShare(ShareId, {});
+ return {};
}
- return {std::move(Workspace), std::move(Share)};
}
Ref<Workspace>
@@ -831,13 +1293,9 @@ Workspaces::FindWorkspace(const RwLock::SharedLockScope&, const Oid& WorkspaceId
#if ZEN_WITH_TESTS
namespace {
- Oid PathToId(const std::filesystem::path& Path)
- {
- return Oid::FromMemory(BLAKE3::HashMemory((const void*)Path.string().data(), Path.string().size()).Hash);
- }
-
std::vector<std::pair<std::filesystem::path, IoBuffer>> GenerateFolderContent(const std::filesystem::path& RootPath)
{
+ CreateDirectories(RootPath);
std::vector<std::pair<std::filesystem::path, IoBuffer>> Result;
Result.push_back(std::make_pair(RootPath / "root_blob_1.bin", CreateRandomBlob(4122)));
Result.push_back(std::make_pair(RootPath / "root_blob_2.bin", CreateRandomBlob(2122)));
@@ -869,6 +1327,7 @@ namespace {
std::vector<std::pair<std::filesystem::path, IoBuffer>> GenerateFolderContent2(const std::filesystem::path& RootPath)
{
+ CreateDirectories(RootPath);
std::vector<std::pair<std::filesystem::path, IoBuffer>> Result;
Result.push_back(std::make_pair(RootPath / "root_blob_3.bin", CreateRandomBlob(312)));
std::filesystem::path FirstFolder(RootPath / "first_folder");
@@ -898,11 +1357,7 @@ TEST_CASE("workspaces.scanfolder")
std::filesystem::path RootPath = TempDir.Path();
(void)GenerateFolderContent(RootPath);
- std::unique_ptr<FolderStructure> Structure = ScanFolder(
- logging::Default(),
- RootPath,
- [](const std::filesystem::path& Path) { return PathToId(Path); },
- WorkerPool);
+ std::unique_ptr<FolderStructure> Structure = ScanFolder(logging::Default(), RootPath, WorkerPool);
CHECK(Structure);
Structure->IterateEntries([&](const Oid& Id, const FolderStructure::FileEntry& Entry) {
@@ -924,25 +1379,25 @@ TEST_CASE("workspace.share.paths")
WorkerThreadPool WorkerPool(std::thread::hardware_concurrency());
ScopedTemporaryDirectory TempDir;
- std::filesystem::path RootPath = TempDir.Path();
+ std::filesystem::path RootPath = TempDir.Path() / "workspace";
std::vector<std::pair<std::filesystem::path, IoBuffer>> Content = GenerateFolderContent(RootPath);
- Workspaces WS;
- CHECK(WS.AddWorkspace({PathToId(RootPath), RootPath}));
- CHECK(WS.AddWorkspaceShare(PathToId(RootPath),
- {PathToId("second_folder/child_in_second"), "second_folder/child_in_second"},
- [](const std::filesystem::path& Path) { return PathToId(Path); }));
- CHECK_THROWS(WS.AddWorkspaceShare(PathToId(RootPath),
- {PathToId("../second_folder"), "../second_folder"},
- [](const std::filesystem::path& Path) { return PathToId(Path); }));
- CHECK(WS.AddWorkspaceShare(PathToId(RootPath), {PathToId("."), "."}, [](const std::filesystem::path& Path) { return PathToId(Path); }));
- CHECK_THROWS(
- WS.AddWorkspaceShare(PathToId(RootPath), {PathToId(".."), ".."}, [](const std::filesystem::path& Path) { return PathToId(Path); }));
- CHECK_THROWS(WS.AddWorkspaceShare(PathToId(RootPath),
- {PathToId("second_folder/../second_folder/../.."), "second_folder/../second_folder/../.."},
- [](const std::filesystem::path& Path) { return PathToId(Path); }));
- CHECK_THROWS(WS.AddWorkspaceShare(PathToId(RootPath), {PathToId(RootPath), RootPath}, [](const std::filesystem::path& Path) {
- return PathToId(Path);
- }));
+ CHECK(Workspaces::AddWorkspace(Log(),
+ TempDir.Path(),
+ Workspaces::WorkspaceConfiguration{.Id = Workspaces::PathToId(RootPath), .RootPath = RootPath}));
+ CHECK(!Workspaces::AddWorkspace(Log(),
+ TempDir.Path(),
+ Workspaces::WorkspaceConfiguration{.Id = Workspaces::PathToId(RootPath), .RootPath = RootPath}));
+ CHECK(Workspaces::AddWorkspaceShare(Log(),
+ RootPath,
+ {Workspaces::PathToId("second_folder/child_in_second"), "second_folder/child_in_second"}));
+ CHECK(!Workspaces::AddWorkspaceShare(Log(),
+ RootPath,
+ {Workspaces::PathToId("second_folder/child_in_second"), "second_folder/child_in_second"}));
+ CHECK_THROWS(Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId("../second_folder"), "../second_folder"}));
+ CHECK(Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId("."), "."}));
+ CHECK(!Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId("."), "."}));
+ CHECK_THROWS(Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId(".."), ".."}));
+ CHECK_THROWS(Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId(RootPath), RootPath}));
}
TEST_CASE("workspace.share.basic")
@@ -952,26 +1407,30 @@ TEST_CASE("workspace.share.basic")
WorkerThreadPool WorkerPool(std::thread::hardware_concurrency());
ScopedTemporaryDirectory TempDir;
- std::filesystem::path RootPath = TempDir.Path();
+ std::filesystem::path RootPath = TempDir.Path() / "workspace";
std::vector<std::pair<std::filesystem::path, IoBuffer>> Content = GenerateFolderContent(RootPath);
+ Workspaces::AddWorkspace(Log(),
+ TempDir.Path(),
+ Workspaces::WorkspaceConfiguration{.Id = Workspaces::PathToId(RootPath), .RootPath = RootPath});
+ Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId("second_folder"), "second_folder"});
+
Workspaces WS;
- CHECK(WS.AddWorkspace({PathToId(RootPath), RootPath}));
- CHECK(WS.AddWorkspaceShare(PathToId(RootPath), {PathToId("second_folder"), "second_folder"}, [](const std::filesystem::path& Path) {
- return PathToId(Path);
- }));
+ WS.RefreshState(TempDir.Path());
+
std::filesystem::path SharePath = RootPath / "second_folder";
std::vector<std::filesystem::path> Paths = {{std::filesystem::relative(Content[4].first, SharePath)},
{std::filesystem::relative(Content[6].first, SharePath)},
{std::filesystem::relative(Content[7].first, SharePath)},
{"the_file_that_is_not_there.txt"}};
- std::vector<IoBuffer> Chunks = WS.GetWorkspaceShareChunks(PathToId(RootPath),
- PathToId("second_folder"),
- std::vector<Workspaces::ChunkRequest>{{.ChunkId = PathToId(Paths[0])},
- {.ChunkId = PathToId(Paths[1])},
- {.ChunkId = PathToId(Paths[2])},
- {.ChunkId = PathToId(Paths[3])}},
- WorkerPool);
+ std::vector<IoBuffer> Chunks =
+ WS.GetWorkspaceShareChunks(Workspaces::PathToId(RootPath),
+ Workspaces::PathToId("second_folder"),
+ std::vector<Workspaces::ChunkRequest>{{.ChunkId = Workspaces::PathToId(Paths[0])},
+ {.ChunkId = Workspaces::PathToId(Paths[1])},
+ {.ChunkId = Workspaces::PathToId(Paths[2])},
+ {.ChunkId = Workspaces::PathToId(Paths[3])}},
+ WorkerPool);
CHECK(Chunks.size() == 4);
CHECK(Chunks[0].GetView().EqualBytes(Content[4].second.GetView()));
CHECK(Chunks[1].GetView().EqualBytes(Content[6].second.GetView()));
@@ -983,53 +1442,56 @@ TEST_CASE("workspace.share.basic")
{std::filesystem::relative(Content2[3].first, SharePath)}};
std::vector<IoBuffer> Chunks2 = WS.GetWorkspaceShareChunks(
- PathToId(RootPath),
- PathToId("second_folder"),
- std::vector<Workspaces::ChunkRequest>{{.ChunkId = PathToId(Paths2[0])}, {.ChunkId = PathToId(Paths2[1])}},
+ Workspaces::PathToId(RootPath),
+ Workspaces::PathToId("second_folder"),
+ std::vector<Workspaces::ChunkRequest>{{.ChunkId = Workspaces::PathToId(Paths2[0])}, {.ChunkId = Workspaces::PathToId(Paths2[1])}},
WorkerPool);
CHECK(Chunks2.size() == 2);
CHECK(Chunks2[0].GetSize() == 0);
CHECK(Chunks2[1].GetSize() == 0);
std::optional<std::vector<Workspaces::ShareFile>> Files =
- WS.GetWorkspaceShareFiles(PathToId(RootPath), PathToId("second_folder"), true, WorkerPool);
+ WS.GetWorkspaceShareFiles(Workspaces::PathToId(RootPath), Workspaces::PathToId("second_folder"), true, WorkerPool);
CHECK(Files.has_value());
CHECK(Files.value().size() == 6);
Chunks2 = WS.GetWorkspaceShareChunks(
- PathToId(RootPath),
- PathToId("second_folder"),
- std::vector<Workspaces::ChunkRequest>{{.ChunkId = PathToId(Paths2[0])}, {.ChunkId = PathToId(Paths2[1])}},
+ Workspaces::PathToId(RootPath),
+ Workspaces::PathToId("second_folder"),
+ std::vector<Workspaces::ChunkRequest>{{.ChunkId = Workspaces::PathToId(Paths2[0])}, {.ChunkId = Workspaces::PathToId(Paths2[1])}},
WorkerPool);
CHECK(Chunks2.size() == 2);
CHECK(Chunks2[0].GetView().EqualBytes(Content2[2].second.GetView()));
CHECK(Chunks2[1].GetView().EqualBytes(Content2[3].second.GetView()));
- Workspaces::ShareFile Entry =
- WS.GetWorkspaceShareChunkInfo(PathToId(RootPath), PathToId("second_folder"), PathToId(Paths2[1]), WorkerPool);
- CHECK(Entry.Id == PathToId(Paths2[1]));
+ Workspaces::ShareFile Entry = WS.GetWorkspaceShareChunkInfo(Workspaces::PathToId(RootPath),
+ Workspaces::PathToId("second_folder"),
+ Workspaces::PathToId(Paths2[1]),
+ WorkerPool);
+ CHECK(Entry.Id == Workspaces::PathToId(Paths2[1]));
CHECK(!Entry.RelativePath.empty());
CHECK(Entry.Size == Content2[3].second.GetSize());
- Files = WS.GetWorkspaceShareFiles(PathToId(RootPath), PathToId("second_folder"), false, WorkerPool);
+ Files = WS.GetWorkspaceShareFiles(Workspaces::PathToId(RootPath), Workspaces::PathToId("second_folder"), false, WorkerPool);
CHECK(Files.has_value());
CHECK(Files.value().size() == 6);
- CHECK(WS.RemoveWorkspaceShare(PathToId(RootPath), PathToId("second_folder")));
- CHECK(!WS.RemoveWorkspaceShare(PathToId(RootPath), PathToId("second_folder")));
+ CHECK(Workspaces::RemoveWorkspaceShare(Log(), RootPath, Workspaces::PathToId("second_folder")));
+ CHECK(!Workspaces::RemoveWorkspaceShare(Log(), RootPath, Workspaces::PathToId("second_folder")));
- Files = WS.GetWorkspaceShareFiles(PathToId(RootPath), PathToId("second_folder"), false, WorkerPool);
+ WS.RefreshState(TempDir.Path());
+ Files = WS.GetWorkspaceShareFiles(Workspaces::PathToId(RootPath), Workspaces::PathToId("second_folder"), false, WorkerPool);
CHECK(!Files.has_value());
Chunks2 = WS.GetWorkspaceShareChunks(
- PathToId(RootPath),
- PathToId("second_folder"),
- std::vector<Workspaces::ChunkRequest>{{.ChunkId = PathToId(Paths2[0])}, {.ChunkId = PathToId(Paths2[1])}},
+ Workspaces::PathToId(RootPath),
+ Workspaces::PathToId("second_folder"),
+ std::vector<Workspaces::ChunkRequest>{{.ChunkId = Workspaces::PathToId(Paths2[0])}, {.ChunkId = Workspaces::PathToId(Paths2[1])}},
WorkerPool);
CHECK(Chunks2.empty());
- CHECK(WS.RemoveWorkspace(PathToId(RootPath)));
- CHECK(!WS.RemoveWorkspace(PathToId(RootPath)));
+ CHECK(Workspaces::RemoveWorkspace(Log(), TempDir.Path(), Workspaces::PathToId(RootPath)));
+ CHECK(!Workspaces::RemoveWorkspace(Log(), TempDir.Path(), Workspaces::PathToId(RootPath)));
}
TEST_CASE("workspace.share.alias")
@@ -1039,14 +1501,14 @@ TEST_CASE("workspace.share.alias")
WorkerThreadPool WorkerPool(std::thread::hardware_concurrency());
ScopedTemporaryDirectory TempDir;
- std::filesystem::path RootPath = TempDir.Path();
+ std::filesystem::path RootPath = TempDir.Path() / "workspace";
std::vector<std::pair<std::filesystem::path, IoBuffer>> Content = GenerateFolderContent(RootPath);
+ CHECK(Workspaces::AddWorkspace(Log(), TempDir.Path(), {Workspaces::PathToId(RootPath), RootPath}));
+ CHECK(Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId("second_folder"), "second_folder", "my_share"}));
+
Workspaces WS;
- CHECK(WS.AddWorkspace({PathToId(RootPath), RootPath}));
- CHECK(WS.AddWorkspaceShare(PathToId(RootPath),
- {PathToId("second_folder"), "second_folder", "my_share"},
- [](const std::filesystem::path& Path) { return PathToId(Path); }));
+ WS.RefreshState(TempDir.Path());
std::optional<Workspaces::ShareAlias> Alias = WS.GetShareAlias("my_share");
CHECK(Alias.has_value());
@@ -1057,33 +1519,37 @@ TEST_CASE("workspace.share.alias")
{std::filesystem::relative(Content[6].first, SharePath)},
{std::filesystem::relative(Content[7].first, SharePath)},
{"the_file_that_is_not_there.txt"}};
- std::vector<IoBuffer> Chunks = WS.GetWorkspaceShareChunks(Alias->WorkspaceId,
- Alias->ShareId,
- std::vector<Workspaces::ChunkRequest>{{.ChunkId = PathToId(Paths[0])},
- {.ChunkId = PathToId(Paths[1])},
- {.ChunkId = PathToId(Paths[2])},
- {.ChunkId = PathToId(Paths[3])}},
- WorkerPool);
+ std::vector<IoBuffer> Chunks =
+ WS.GetWorkspaceShareChunks(Alias->WorkspaceId,
+ Alias->ShareId,
+ std::vector<Workspaces::ChunkRequest>{{.ChunkId = Workspaces::PathToId(Paths[0])},
+ {.ChunkId = Workspaces::PathToId(Paths[1])},
+ {.ChunkId = Workspaces::PathToId(Paths[2])},
+ {.ChunkId = Workspaces::PathToId(Paths[3])}},
+ WorkerPool);
CHECK(Chunks.size() == 4);
CHECK(Chunks[0].GetView().EqualBytes(Content[4].second.GetView()));
CHECK(Chunks[1].GetView().EqualBytes(Content[6].second.GetView()));
CHECK(Chunks[2].GetView().EqualBytes(Content[7].second.GetView()));
CHECK(Chunks[3].GetSize() == 0);
- CHECK(WS.RemoveWorkspaceShare(Alias->WorkspaceId, Alias->ShareId));
+ CHECK(Workspaces::RemoveWorkspaceShare(Log(), RootPath, Alias->ShareId));
+ WS.RefreshState(TempDir.Path());
CHECK(!WS.GetShareAlias("my_share").has_value());
- CHECK(WS.AddWorkspaceShare(PathToId(RootPath),
- {PathToId("second_folder"), "second_folder", "my_share"},
- [](const std::filesystem::path& Path) { return PathToId(Path); }));
+ CHECK(Workspaces::AddWorkspaceShare(Log(), RootPath, {Workspaces::PathToId("second_folder"), "second_folder", "my_share"}));
+ WS.RefreshState(TempDir.Path());
+
CHECK(WS.GetShareAlias("my_share").has_value());
- CHECK(WS.RemoveWorkspace(PathToId(RootPath)));
- CHECK(!WS.RemoveWorkspace(PathToId(RootPath)));
+ CHECK(Workspaces::RemoveWorkspace(Log(), TempDir.Path(), Workspaces::PathToId(RootPath)));
+ CHECK(!Workspaces::RemoveWorkspace(Log(), TempDir.Path(), Workspaces::PathToId(RootPath)));
+ WS.RefreshState(TempDir.Path());
CHECK(!WS.GetShareAlias("my_share").has_value());
}
+
#endif
void