Files
FS/src/CustomFilesystem.cpp
T
2024-07-11 17:57:31 +02:00

613 lines
18 KiB
C++

#include "StdAfx.h"
#include "fs/CustomFilesystem.h"
#include "fs/WritableFolderPack.h"
#include "tl/assert.h"
//#include "tl/algorithm/find.h"
#include "fs/CopyStream.h"
#include "fs/NativeFilesystem.h"
#include "tl/unordered_set.h"
#include "tl/algorithm/find.h"
#include <tl/atomic.h>
//////////////////////////////////////////////////////////////////////////
template class tl::abs_path<tl::path_systems<fs::WindowsSystem, fs::PosixSystem, fs::UncSystem>>;
template class tl::rel_path<tl::path_systems<fs::WindowsSystem, fs::PosixSystem, fs::UncSystem>>;
namespace fs
{
//////////////////////////////////////////////////////////////////////////
namespace
{
tl::atomic<int> s_lastId(0);
}
//////////////////////////////////////////////////////////////////////////
tl::result<CustomFilesystem::PackId> CustomFilesystem::mountFront(AbsPathView mountPoint, tl::unique_ref<IPack> pack)
{
return mount(std::move(mountPoint), std::move(pack), MountPolicy::Front);
}
//////////////////////////////////////////////////////////////////////////
tl::result<CustomFilesystem::PackId> CustomFilesystem::mountBack(AbsPathView mountPoint, tl::unique_ref<IPack> pack)
{
return mount(std::move(mountPoint), std::move(pack), MountPolicy::Back);
}
//////////////////////////////////////////////////////////////////////////
cppcoro::generator<tl::unique_ref<IPack>> CustomFilesystem::unmountAll()
{
tl::fixed_vector<tl::unique_ref<IPack>, 32> packs;
packs.reserve(m_packsData.size());
for (PackData& pd: m_packsData)
packs.push_back(std::move(pd.pack));
m_packsData.clear();
for (auto& pack: packs)
co_yield std::move(pack);
}
//////////////////////////////////////////////////////////////////////////
tl::unique_ptr<IPack> CustomFilesystem::unmount(PackId packId)
{
if (const auto it = tl::find_if(m_packsData, [packId](const PackData& p) { return p.id == packId; }); it != m_packsData.end())
{
auto p = std::move(it->pack); //move it before erasing
m_packsData.erase(it);
return std::move(p);
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
OpenSourceResult CustomFilesystem::openSource(AbsPathView path, SourceFlags flags) const
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
const ExistsResult existsResult = pd.pack->exists(filePackPath);
if (existsResult == tl::success() && existsResult.value() == false)
continue;
tl::result<tl::unique_ref<ISource>, Error> result = pd.pack->openSource(filePackPath, flags);
if (result.has_value())
return std::move(result.value());
if (result.error().code != ErrorCode::NotFound)
return result;
}
}
return tl::make_error<Error>(ErrorCode::NotFound, "Path '{}' was not found", path);
}
//////////////////////////////////////////////////////////////////////////
OpenStreamSourceResult CustomFilesystem::openStreamSource(AbsPathView path, SourceFlags flags) const
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
const ExistsResult existsResult = pd.pack->exists(filePackPath);
if (existsResult == tl::success() && existsResult.value() == false)
continue;
tl::result<tl::unique_ref<IStreamSource>, Error> result = pd.pack->openStreamSource(filePackPath, flags);
if (result.has_value())
return std::move(result.value());
if (result.error().code != ErrorCode::NotFound)
return result;
}
}
return tl::make_error<Error>(ErrorCode::NotFound, "Path '{}' was not found", path);
}
//////////////////////////////////////////////////////////////////////////
OpenMapSourceResult CustomFilesystem::openMapSource(AbsPathView path, MapView mapView, SourceFlags flags) const
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
const ExistsResult existsResult = pd.pack->exists(filePackPath);
if (existsResult == tl::success() && existsResult.value() == false)
continue;
tl::result<tl::unique_ref<IMapSource>, Error> result = pd.pack->openMapSource(filePackPath, mapView, flags);
if (result.has_value())
return std::move(result.value());
if (result.error().code != ErrorCode::NotFound)
return result.error();
}
}
return tl::make_error<Error>(ErrorCode::NotFound, "Path '{}' was not found", path);
}
//////////////////////////////////////////////////////////////////////////
OpenMapSinkResult CustomFilesystem::openMapSink(AbsPathView path, Mode mode, size_t size, SinkFlags flags)
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
if (const tl::lent_ptr<IWritablePack> pack = tl::dynamic_lent_cast<IWritablePack>(pd.pack))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
OpenMapSinkResult result = pack->openMapSink(filePackPath, mode, size, flags);
if (result.has_value())
return std::move(result.value());
if (result.error().code != ErrorCode::NotFound)
return result;
}
else
return tl::make_error<Error>(ErrorCode::NotAllowed, "The filesystem is read only");
}
}
return tl::make_error<Error>(ErrorCode::NotFound, "Path '{}' was not found", path);
}
//////////////////////////////////////////////////////////////////////////
OpenSinkResult CustomFilesystem::openSink(AbsPathView path, Mode mode, SinkFlags flags)
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
if (const tl::lent_ptr<IWritablePack> pack = tl::dynamic_lent_cast<IWritablePack>(pd.pack))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
OpenSinkResult result = pack->openSink(filePackPath, mode, flags);
if (result.has_value())
return std::move(result.value());
if (result.error().code != ErrorCode::NotFound)
return result;
}
}
}
return tl::make_error<Error>(ErrorCode::NotFound, "Path '{}' was not found or all mounted packs are read only", path);
}
//////////////////////////////////////////////////////////////////////////
OpenStreamSinkResult CustomFilesystem::openStreamSink(AbsPathView path, Mode mode, SinkFlags flags)
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
if (const tl::lent_ptr<IWritablePack> pack = tl::dynamic_lent_cast<IWritablePack>(pd.pack))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
OpenStreamSinkResult result = pack->openStreamSink(filePackPath, mode, flags);
if (result.has_value())
return std::move(result.value());
if (result.error().code != ErrorCode::NotFound)
return result;
}
}
}
return tl::make_error<Error>(ErrorCode::NotFound, "Path '{}' was not found or all mounted packs are read only", path);
}
//////////////////////////////////////////////////////////////////////////
ExistsResult CustomFilesystem::exists(AbsPathView path) const
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
if (filePackPath.empty()) //This is the exact mount point of the pack, which means this folder exists
return true;
auto result = pd.pack->exists(filePackPath);
if (result.has_value() && result.value() == true)
return true;
if (result.has_error())
{
//exists should not return not found
TL_ASSERT(result.error().code != ErrorCode::NotFound);
return result;
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
IsFileResult CustomFilesystem::isFile(AbsPathView path) const
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
if (filePackPath.empty()) //This is the exact mount point of the pack, which means it is not a file
return false;
auto result = pd.pack->isFile(filePackPath);
if (result.has_value())
return result.value();
if (result.error().code != Error::code_t::NotFound)
return result;
}
}
return tl::make_error<Error>(Error::code_t::NotFound, "Path '{}' not found in the filesystem", path);
}
//////////////////////////////////////////////////////////////////////////
RenameResult CustomFilesystem::rename(AbsPathView path, AbsPathView newPath)
{
AbsPath fromPackPath;
AbsPath toPackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
if (const tl::lent_ptr<IWritablePack> pack = tl::dynamic_lent_cast<IWritablePack>(pd.pack))
{
convertToPackPath(fromPackPath, path, pd.mountPoint);
convertToPackPath(toPackPath, newPath, pd.mountPoint);
auto result = pack->rename(fromPackPath, toPackPath);
if (result.has_value())
return tl::success();
if (result.error().code != Error::code_t::NotFound)
return result;
}
}
}
return tl::make_error<Error>(Error::code_t::NotFound, "Path '{}' was not found or all mounted packs are read only", path);
}
//////////////////////////////////////////////////////////////////////////
CopyResult CustomFilesystem::copy(AbsPathView path, AbsPathView newPath)
{
OUTCOME_TRY(const auto source, openStreamSource(path, SourceFlags(SourceFlag::SequentialHint, SourceFlag::Uncached)));
OUTCOME_TRY(const auto destination, openStreamSink(newPath, Mode::CreateOrOpenAndClear));
CopyStreamResult copyResult = copyStream(*destination, *source);
if (copyResult.has_error())
return copyResult.error();
return tl::success();
}
//////////////////////////////////////////////////////////////////////////
MakeHardLinkResult CustomFilesystem::makeHardLink(AbsPathView sourcePath, AbsPathView linkPath)
{
OUTCOME_TRY(const auto sp, convertToNativePath(sourcePath));
OUTCOME_TRY(const auto lp, convertToNativePath(linkPath));
return native.makeHardLink(sp, lp);
}
//////////////////////////////////////////////////////////////////////////
MakeSoftLinkResult CustomFilesystem::makeSymLink(AbsPathView sourcePath, AbsPathView linkPath)
{
OUTCOME_TRY(const auto sp, convertToNativePath(sourcePath));
OUTCOME_TRY(const auto lp, convertToNativePath(linkPath));
return native.makeSymLink(sp, lp);
}
//////////////////////////////////////////////////////////////////////////
IsFolderResult CustomFilesystem::isFolder(AbsPathView path) const
{
AbsPath folderPackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(folderPackPath, path, pd.mountPoint);
if (folderPackPath.empty()) //This is the exact mount point of the pack, which means it is a folder
return true;
auto result = pd.pack->isFolder(folderPackPath);
if (result.has_value())
return result.value();
if (result.error().code != Error::code_t::NotFound)
return result;
}
}
return tl::make_error<Error>(Error::code_t::NotFound, "Path '{}' not found in the filesystem", path);
}
//////////////////////////////////////////////////////////////////////////
MakeFolderResult CustomFilesystem::makeFolder(AbsPathView path)
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
if (const tl::lent_ptr<IWritablePack> pack = tl::dynamic_lent_cast<IWritablePack>(pd.pack))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
MakeFolderResult result = pack->makeFolder(filePackPath);
if (result.has_value())
return result;
if (result.error().code != ErrorCode::NotFound)
return result;
}
else
return tl::make_error<Error>(ErrorCode::NotAllowed, "The filesystem is read only");
}
}
return tl::make_error<Error>(Error::code_t::SystemError, "Not implemented");
}
//////////////////////////////////////////////////////////////////////////
RemoveResult CustomFilesystem::remove(AbsPathView path)
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
if (const tl::lent_ptr<IWritablePack> pack = tl::dynamic_lent_cast<IWritablePack>(pd.pack))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
auto result = pack->remove(filePackPath);
if (result.has_value())
return tl::success();
if (result.error().code != Error::code_t::NotFound)
return result;
}
}
}
return tl::make_error<Error>(ErrorCode::NotFound, "Path '{}' was not found or all mounted packs are read only", path);
}
//////////////////////////////////////////////////////////////////////////
RemoveRecursivelyResult CustomFilesystem::removeRecursively(AbsPathView path)
{
for (const EnumerateEntry& ee : enumerate(path))
{
AbsPath p = path + ee.path;
if (ee.isFolder)
{
OUTCOME_TRY(removeRecursively(p));
}
else
{
OUTCOME_TRY(remove(p));
}
}
return tl::success();
}
//////////////////////////////////////////////////////////////////////////
ConvertToNativePathResult CustomFilesystem::convertToNativePath(AbsPathView path) const
{
AbsPath folderPackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(folderPackPath, path, pd.mountPoint);
auto result = pd.pack->convertToNativePath(folderPackPath);
if (result.has_value())
return result.value();
if (result.error().code != ErrorCode::NotFound)
return result;
}
}
return tl::make_error<Error>(Error::code_t::NotFound, "Cannot convert '{}' to a native path", path);
}
//////////////////////////////////////////////////////////////////////////
SetWriteTimeResult CustomFilesystem::setWriteTime(AbsPathView path, time_t time)
{
AbsPath filePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
if (const tl::lent_ptr<IWritablePack> pack = tl::dynamic_lent_cast<IWritablePack>(pd.pack))
{
convertToPackPath(filePackPath, path, pd.mountPoint);
SetWriteTimeResult result = pack->setWriteTime(filePackPath, time);
if (result.has_value())
return tl::success();
if (result.error().code != ErrorCode::NotFound)
return result;
}
}
}
return tl::make_error<Error>(Error::code_t::NotFound, "Path '{}' was not found or all mounted packs are read only", path);
}
//////////////////////////////////////////////////////////////////////////
GetStatResult CustomFilesystem::getStat(AbsPathView path) const
{
AbsPath folderPackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(folderPackPath, path, pd.mountPoint);
auto result = pd.pack->getStat(folderPackPath);
if (result.has_value())
return result.value();
if (result.error().code != ErrorCode::NotFound)
return result;
}
}
return tl::make_error<Error>(Error::code_t::NotFound, "File {} not found", path);
}
//////////////////////////////////////////////////////////////////////////
cppcoro::generator<EnumerateEntry> CustomFilesystem::enumerate(AbsPath path) const
{
AbsPath basePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(basePackPath, path, pd.mountPoint);
//it's possible that even if the mount point is
const IsFolderResult isFolderResult = pd.pack->isFolder(basePackPath);
if (isFolderResult.has_error() || isFolderResult.value() == false)
continue;
for (EnumerateEntry ee : pd.pack->enumerate(basePackPath))
co_yield std::move(ee);
}
}
}
//////////////////////////////////////////////////////////////////////////
cppcoro::generator<EnumerateEntry> CustomFilesystem::enumerateRecursively(AbsPath path) const
{
tl::unordered_set<RelPath> checkedFiles;
AbsPath basePackPath;
for (const auto& pd : m_packsData)
{
if (pd.mountPoint.is_prefix_of(path))
{
convertToPackPath(basePackPath, path, pd.mountPoint);
if (!basePackPath.empty())
{
const IsFolderResult isFolderResult = pd.pack->isFolder(basePackPath);
if (isFolderResult.has_error() || isFolderResult.value() == false)
continue;
}
for (EnumerateEntry ee: pd.pack->enumerateRecursively(basePackPath))
{
if (checkedFiles.insert(ee.path).second)
co_yield std::move(ee);
}
}
else if (path.is_prefix_of(pd.mountPoint))
{
const RelPathView parentPath = path.path_to(pd.mountPoint);
RelPath p = parentPath;
for (EnumerateEntry ee : pd.pack->enumerateRecursively(AbsPath(PosixRootTag::value())))
{
const size_t eePathSize = ee.path.size();
p += ee.path;
ee.path = p;
p.shrink(eePathSize);
if (checkedFiles.insert(ee.path).second)
co_yield std::move(ee);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CustomFilesystem::convertToPackPath(AbsPath& packPath, AbsPathView path, AbsPathView mountPoint) const
{
RelPathView basePath = mountPoint.prefix_path_to(path);
packPath = PosixRootTag::value();
packPath.reserve(basePath.size() + 1);
for (const tl::string& element : basePath)
packPath.push_back(element);
}
//////////////////////////////////////////////////////////////////////////
tl::result<CustomFilesystem::PackId> CustomFilesystem::mount(AbsPathView mountPoint, tl::unique_ref<IPack> pack, MountPolicy policy)
{
if (!mountPoint.is_valid())
return tl::make_generic_error("Invalid mount point");
auto isFileResult = isFile(mountPoint);
if (isFileResult.has_value() && isFileResult.value() == true)
return tl::make_generic_error("Invalid shadowing (mount point shadowing a file)");
const PackId id(++s_lastId);
PackData data(std::move(pack), id);
data.mountPoint = std::move(mountPoint);
switch (policy)
{
case MountPolicy::Front:
m_packsData.insert(m_packsData.begin(), std::move(data));
break;
case MountPolicy::Back:
m_packsData.push_back(std::move(data));
break;
default:
TL_FAIL();
break;
}
return id;
}
//////////////////////////////////////////////////////////////////////////
}