Created
July 9, 2022 01:00
-
-
Save JonathanCline/a54d65551448e132917c4a7847a7f7b7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <asx/vfs.hpp> | |
| #include <asx/fs.hpp> | |
| #include <asx/assert.hpp> | |
| #include <nlohmann/json.hpp> | |
| #include <filesystem> | |
| #include <iostream> | |
| namespace asx::vfs::impl | |
| { | |
| bool AnyFileStorage::good() const noexcept | |
| { | |
| return this->file_ != nullptr; | |
| }; | |
| void AnyFileStorage::reset() | |
| { | |
| if (this->good()) | |
| { | |
| switch (this->type()) | |
| { | |
| case FileType::directory: | |
| delete (this->get<DirectoryData>()); | |
| break; | |
| case FileType::file: | |
| delete (this->get<FileData>()); | |
| break; | |
| case FileType::null: | |
| ASX_FAIL("SHOULD NEVER HAPPEN, NULL TYPE BUT POINTER IS NOT NULL REEEE"); | |
| break; | |
| default: | |
| ASX_FAIL("unknown file type"); | |
| break; | |
| }; | |
| (void)this->release(); | |
| }; | |
| }; | |
| FileBase* AnyFileStorage::release() noexcept | |
| { | |
| const auto o = this->file_; | |
| this->file_ = nullptr; | |
| this->type_ = FileType::null; | |
| return o; | |
| }; | |
| DirectoryData* AnyFileStorage::unset_parent() | |
| { | |
| if (this->good()) | |
| { | |
| auto& _file = this->file_; | |
| auto _parent = _file->parent_; | |
| if (_parent) | |
| { | |
| _file->parent_ = nullptr; | |
| }; | |
| return _parent; | |
| }; | |
| return nullptr; | |
| }; | |
| AnyFileStorage AnyFileStorage::create(FileName _name, FileType _type, DirectoryData* _parent) | |
| { | |
| FileBase* _file = nullptr; | |
| switch (_type) | |
| { | |
| case FileType::file: | |
| _file = new FileData(_name, _parent); | |
| break; | |
| case FileType::directory: | |
| _file = new DirectoryData(_name, _parent); | |
| break; | |
| case FileType::null: | |
| return AnyFileStorage(nullptr, _type); | |
| default: | |
| ASX_FAIL("unknown file type"); | |
| break; | |
| }; | |
| ASX_CHECK(_file && "failed to create file"); | |
| return AnyFileStorage(_file, _type); | |
| }; | |
| AnyFileStorage AnyFileStorage::create_directory(FileName _name, DirectoryData* _parent) | |
| { | |
| return AnyFileStorage::create(_name, FileType::directory, _parent); | |
| }; | |
| AnyFileStorage::AnyFileStorage(std::nullptr_t) : | |
| AnyFileStorage(nullptr, FileType::null) | |
| {}; | |
| AnyFileStorage::AnyFileStorage(FileData* _data) : | |
| AnyFileStorage(_data, FileType::file) | |
| {}; | |
| AnyFileStorage::AnyFileStorage(DirectoryData* _data) : | |
| AnyFileStorage(_data, FileType::directory) | |
| {}; | |
| AnyFileStorage::AnyFileStorage(FileBase* _data, FileType _type) : | |
| file_(_data), type_(_type) | |
| { | |
| // Sanity check | |
| if (_data) | |
| { | |
| ASX_CHECK(_type != FileType::null); | |
| } | |
| else | |
| { | |
| ASX_CHECK(_type == FileType::null); | |
| }; | |
| }; | |
| AnyFileStorage::AnyFileStorage(AnyFileStorage&& other) noexcept : | |
| file_(std::exchange(other.file_, nullptr)), | |
| type_(std::exchange(other.type_, FileType::null)) | |
| {}; | |
| AnyFileStorage& AnyFileStorage::operator=(AnyFileStorage&& other) noexcept | |
| { | |
| this->reset(); | |
| this->type_ = std::exchange(other.type_, FileType::null); | |
| this->file_ = std::exchange(other.file_, nullptr); | |
| return *this; | |
| }; | |
| AnyFileStorage::~AnyFileStorage() | |
| { | |
| this->reset(); | |
| }; | |
| }; | |
| namespace asx::vfs | |
| { | |
| inline void recursive_serialize(nlohmann::json& _parent, const Path& _parentPath, const Filesystem& _filesystem) | |
| { | |
| using json = nlohmann::json; | |
| auto _contents = _filesystem.contents(_parentPath); | |
| for (auto& v : _contents) | |
| { | |
| auto _node = json::object(); | |
| _node["name"] = v; | |
| auto _path = _parentPath + v; | |
| _node["path"] = _path; | |
| if (_filesystem.is_directory(_path)) | |
| { | |
| auto nn = json::array(); | |
| recursive_serialize(nn, _path, _filesystem); | |
| _node["contents"] = nn; | |
| } | |
| else | |
| { | |
| const auto bn = _filesystem.dump(_path); | |
| _node["data"] = asx::encode_base64(bn); | |
| }; | |
| _parent.push_back(_node); | |
| }; | |
| }; | |
| std::vector<std::byte> serialize(const Filesystem& _filesystem) | |
| { | |
| using json = nlohmann::json; | |
| auto _json = json::object(); | |
| _json["name"] = "/"; | |
| _json["path"] = "/"; | |
| { | |
| auto nn = json::array(); | |
| recursive_serialize(nn, "/", _filesystem); | |
| _json["contents"] = std::move(nn); | |
| }; | |
| const auto s = _json.dump(); | |
| auto o = std::vector<std::byte>(s.size()); | |
| std::ranges::copy(s, reinterpret_cast<char*>(o.data())); | |
| return o; | |
| }; | |
| inline bool recursive_deserialize(const nlohmann::json& _json, Filesystem& _filesystem) | |
| { | |
| const Path _path = _json.at("path"); | |
| if (_json.contains("contents")) | |
| { | |
| if (_path != "/" && !_filesystem.create_directory(_path)) | |
| { | |
| return false; | |
| }; | |
| for (auto& v : _json.at("contents")) | |
| { | |
| if (!recursive_deserialize(v, _filesystem)) | |
| { | |
| return false; | |
| }; | |
| }; | |
| } | |
| else | |
| { | |
| std::string_view _dataStr = _json.at("data"); | |
| const auto bn = asx::decode_base64(_dataStr); | |
| _filesystem.upload(_path, bn); | |
| }; | |
| return true; | |
| }; | |
| bool deserialize(std::span<const std::byte> _data, Filesystem& _filesystem) | |
| { | |
| using json = nlohmann::json; | |
| auto s = std::string(_data.size(), '\0'); | |
| std::ranges::copy(_data, reinterpret_cast<std::byte*>(s.data())); | |
| auto _json = json::parse(s); | |
| return recursive_deserialize(_json, _filesystem); | |
| }; | |
| Filesystem copy_directory_as_vfs(const PathView& _pathStr) | |
| { | |
| auto _path = std::filesystem::path(_pathStr); | |
| auto _filesystem = Filesystem{}; | |
| for (auto& p : std::filesystem::recursive_directory_iterator(_path)) | |
| { | |
| auto _vPath = "/" + std::filesystem::relative(p, _path).generic_string(); | |
| if (p.is_directory()) | |
| { | |
| _filesystem.create_directories(_vPath); | |
| } | |
| else if (p.is_regular_file()) | |
| { | |
| const auto _fileData = readfile(p.path().string()); | |
| _filesystem.upload(_vPath, _fileData); | |
| }; | |
| }; | |
| return _filesystem; | |
| }; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| /** @file */ | |
| #include <asx/str.hpp> | |
| #include <asx/assert.hpp> | |
| #include <asx/concepts.hpp> | |
| #include <asx/algorithm.hpp> | |
| #include <asx/type_traits.hpp> | |
| #include <jclib/memory.h> | |
| #include <jclib/functional.h> | |
| #include <map> | |
| #include <span> | |
| #include <string> | |
| #include <vector> | |
| #include <cstddef> | |
| #include <variant> | |
| #include <optional> | |
| #include <functional> | |
| #include <unordered_map> | |
| namespace asx::vfs | |
| { | |
| using FileName = std::string; | |
| using FileNameView = std::string_view; | |
| using Path = FileName; | |
| using PathView = FileNameView; | |
| enum class FileType : uint8_t | |
| { | |
| // Invalid file / null | |
| null = 0, | |
| // Regular file | |
| file = 1, | |
| // Directory | |
| directory = 2, | |
| }; | |
| namespace impl | |
| { | |
| class FileData; | |
| class DirectoryData; | |
| template <typename T> | |
| struct filedata_type; | |
| template <> struct filedata_type<FileData> : cxconstant<FileType::file> { }; | |
| template <> struct filedata_type<DirectoryData> : cxconstant<FileType::directory> { }; | |
| struct FileBase | |
| { | |
| public: | |
| int fd() const noexcept { return this->open_fd_; }; | |
| bool has_open_fd() const { return this->fd() != 0; }; | |
| void close_fd() { this->open_fd_ = 0; }; | |
| void set_fd(int _fd) { this->open_fd_ = _fd; }; | |
| FileBase(const FileName& _name, DirectoryData* _parent = nullptr) : | |
| name_(_name), parent_(_parent) | |
| {}; | |
| FileName name_; | |
| DirectoryData* parent_ = nullptr; | |
| private: | |
| int open_fd_ = 0; | |
| }; | |
| class AnyFileStorage | |
| { | |
| public: | |
| bool good() const noexcept; | |
| explicit operator bool() const noexcept | |
| { | |
| return this->good(); | |
| }; | |
| void reset(); | |
| FileBase* release() noexcept; | |
| FileType type() const noexcept | |
| { | |
| return this->type_; | |
| }; | |
| template <jc::cx_any_of<FileData, DirectoryData> T> | |
| T* get() const & | |
| { | |
| ASX_CHECK(this->type() == filedata_type<T>::value); | |
| return static_cast<T*>(this->file_); | |
| }; | |
| auto get() && = delete; | |
| template <jc::cx_any_of<FileData, DirectoryData> T> | |
| T& as() const& | |
| { | |
| ASX_CHECK(this->good()); | |
| return *this->get<T>(); | |
| }; | |
| auto as() && = delete; | |
| /** | |
| * @brief Only call if you know what you are doing. Doesn't remove the file from the parent's | |
| * child contents. | |
| * | |
| * @return The old parent directory pointer. | |
| */ | |
| DirectoryData* unset_parent(); | |
| const FileName& name() const | |
| { | |
| return this->file_->name_; | |
| }; | |
| const DirectoryData* parent() const | |
| { | |
| return this->file_->parent_; | |
| }; | |
| static AnyFileStorage create(FileName _name, FileType _type = FileType::file, DirectoryData* _parent = nullptr); | |
| static AnyFileStorage create_directory(FileName _name, DirectoryData* _parent = nullptr); | |
| int fd() const noexcept | |
| { | |
| return this->file_->fd(); | |
| }; | |
| bool has_open_fd() const | |
| { | |
| return this->file_->has_open_fd(); | |
| }; | |
| void close_fd() | |
| { | |
| this->file_->close_fd(); | |
| }; | |
| void set_fd(int _fd) | |
| { | |
| this->file_->set_fd(_fd); | |
| }; | |
| AnyFileStorage(AnyFileStorage&& other) noexcept; | |
| AnyFileStorage& operator=(AnyFileStorage&& other) noexcept; | |
| ~AnyFileStorage(); | |
| protected: | |
| explicit AnyFileStorage(std::nullptr_t); | |
| explicit AnyFileStorage(FileData* _data); | |
| explicit AnyFileStorage(DirectoryData* _data); | |
| explicit AnyFileStorage(FileBase* _data, FileType _type); | |
| private: | |
| FileBase* file_; | |
| FileType type_; | |
| AnyFileStorage(const AnyFileStorage&) = delete; | |
| AnyFileStorage& operator=(const AnyFileStorage&) = delete; | |
| }; | |
| class FileData : public FileBase | |
| { | |
| public: | |
| ASX_DEFINE_ITERATOR_FUNCTIONS(this->data_); | |
| void clear() noexcept | |
| { | |
| this->data_.clear(); | |
| }; | |
| auto size() const | |
| { | |
| return this->data_.size(); | |
| }; | |
| size_t write(std::span<const std::byte> _data) | |
| { | |
| const auto _count = _data.size(); | |
| this->data_.insert(this->end(), _data.begin(), _data.end()); | |
| return _count; | |
| }; | |
| size_t write(std::span<const std::byte> _data, size_t _pos) | |
| { | |
| const auto _count = _data.size(); | |
| const auto _size = this->size(); | |
| auto it = this->begin() + std::min(_pos, _size); | |
| this->data_.insert(it, _data.begin(), _data.end()); | |
| return _count; | |
| }; | |
| size_t write(std::span<const char> _data) | |
| { | |
| return this->write(std::as_bytes(_data)); | |
| }; | |
| size_t write(std::span<const char> _data, size_t _pos) | |
| { | |
| return this->write(std::as_bytes(_data), _pos); | |
| }; | |
| std::span<const std::byte> read_bytes(size_t _count) const | |
| { | |
| const auto _size = this->size(); | |
| auto _end = this->begin() + std::min(_count, _size); | |
| return std::span<const std::byte>(this->begin(), _end); | |
| }; | |
| std::span<const std::byte> read_bytes(size_t _count, size_t _offset) | |
| { | |
| const auto _size = this->size(); | |
| _offset = std::min(_offset, _size); | |
| auto _begin = this->begin() + _offset; | |
| auto _end = _begin + std::min(_count, _size - _offset); | |
| return std::span<const std::byte>(_begin, _end); | |
| }; | |
| std::span<const char> read(size_t _count) const | |
| { | |
| const auto p0 = reinterpret_cast<const char*>(this->data_.data()); | |
| const auto _size = this->size(); | |
| const auto _readCount = std::min(_count, _size); | |
| const auto p1 = p0 + _readCount; | |
| return std::span<const char>(p0, p1); | |
| }; | |
| std::span<const char> read(size_t _count, size_t _offset) | |
| { | |
| _offset = std::min(_offset, this->data_.size()); | |
| const auto p0 = reinterpret_cast<const char*>(this->data_.data()) + _offset; | |
| const auto _size = this->size() - _offset; | |
| const auto _readCount = std::min(_count, _size); | |
| const auto p1 = p0 + _readCount; | |
| return std::span<const char>(p0, p1); | |
| }; | |
| using FileBase::FileBase; | |
| std::vector<std::byte> data_; | |
| }; | |
| struct DirectoryContentsHasher : public jc::operator_tag | |
| { | |
| auto operator()(const std::string& _str) const | |
| { | |
| return std::hash<std::string_view>{}(_str); | |
| }; | |
| auto operator()(const std::string_view& _str) const | |
| { | |
| return std::hash<std::string_view>{}(_str); | |
| }; | |
| }; | |
| class DirectoryData : public FileBase | |
| { | |
| private: | |
| using container_type = std::unordered_map<FileName, AnyFileStorage, | |
| jc::transparent<impl::DirectoryContentsHasher>, jc::transparent<jc::equals_t>>; | |
| public: | |
| bool create(const FileName& _name, FileType _type = FileType::file) | |
| { | |
| const auto [it, _good] = this->contents_.insert({ _name, | |
| impl::AnyFileStorage::create(_name, _type, this) }); | |
| return _good; | |
| }; | |
| void remove(const FileNameView& _name) | |
| { | |
| auto& _contents = this->contents_; | |
| if (const auto it = _contents.find(_name); it != _contents.end()) | |
| { | |
| auto& [_name, _file] = *it; | |
| if (_file.parent() == this) | |
| { | |
| _file.unset_parent(); | |
| }; | |
| _contents.erase(it); | |
| }; | |
| }; | |
| void remove_all() | |
| { | |
| for (auto it = this->contents_.begin(); it != this->contents_.end(); ++it) | |
| { | |
| auto& [_name, _file] = *it; | |
| if (_file.parent() == this) | |
| { | |
| _file.unset_parent(); | |
| }; | |
| }; | |
| this->contents_.clear(); | |
| }; | |
| AnyFileStorage* child(const FileNameView& _name) | |
| { | |
| if (const auto it = this->contents_.find(_name); it != this->contents_.end()) | |
| { | |
| return &it->second; | |
| } | |
| else | |
| { | |
| return nullptr; | |
| }; | |
| }; | |
| auto child_paths() const | |
| { | |
| return this->contents_ | std::views::keys; | |
| }; | |
| /** | |
| * @brief Gets the number of files within this directory. | |
| * @return Number of files. | |
| */ | |
| size_t child_count() const noexcept | |
| { | |
| return this->contents_.size(); | |
| }; | |
| using FileBase::FileBase; | |
| ~DirectoryData() | |
| { | |
| this->remove_all(); | |
| }; | |
| private: | |
| container_type contents_; | |
| }; | |
| }; | |
| enum class Openmode | |
| { | |
| r = 0, | |
| w = 1, | |
| }; | |
| /** | |
| * @brief Represents a virtual filesystem held in memory. | |
| */ | |
| class Filesystem | |
| { | |
| private: | |
| static impl::FileData& as_file(const impl::AnyFileStorage& _file) | |
| { | |
| return _file.as<impl::FileData>(); | |
| }; | |
| static impl::DirectoryData& as_directory(const impl::AnyFileStorage& _file) | |
| { | |
| return _file.as<impl::DirectoryData>(); | |
| }; | |
| impl::DirectoryData& root() const | |
| { | |
| return this->as_directory(this->root_); | |
| }; | |
| constexpr static auto seperator_chars_v = std::string_view("/\\"); | |
| struct PathSegmentReader | |
| { | |
| explicit operator bool() const | |
| { | |
| return this->head_ != this->path_.size(); | |
| }; | |
| PathView next() | |
| { | |
| auto& _path = this->path_; | |
| const auto _pathSize = _path.size(); | |
| auto& _head = this->head_; | |
| if (_head == _pathSize) | |
| { | |
| return {}; | |
| } | |
| else if (_head == 0 && _path.front() == '/') | |
| { | |
| // Special handling to allow the root directory "/" to be a segment | |
| const auto _segment = _path.substr(0, 1); | |
| const auto _nextHeadPos = | |
| std::min(_path.find_first_not_of(seperator_chars_v), _pathSize); | |
| this->head_ = _nextHeadPos; | |
| return _segment; | |
| }; | |
| // Find the next seperator position. | |
| const auto _nextSeperatorPos = _path.find_first_of(seperator_chars_v, _head); | |
| auto _pos = std::min(_nextSeperatorPos, _pathSize); | |
| // If this points to a directory, it should end with a "/". | |
| if (_nextSeperatorPos != _pathSize) | |
| { | |
| ++_pos; | |
| }; | |
| // Grab the segment. | |
| auto _segment = _path.substr(_head, _pos - _head); | |
| // Move the reader head. | |
| if (_pos != _pathSize) | |
| { | |
| const auto _nextHeadPos = | |
| std::min(_path.find_first_not_of(seperator_chars_v, _nextSeperatorPos), _pathSize); | |
| this->head_ = _nextHeadPos; | |
| } | |
| else | |
| { | |
| this->head_ = _pathSize; | |
| }; | |
| return _segment; | |
| }; | |
| PathSegmentReader(PathView _path) : | |
| path_(_path) | |
| {}; | |
| PathView path_; | |
| size_t head_ = 0; | |
| }; | |
| // Exists early if function returns false | |
| // _op(<segment>, <previous segments>/<segment>) -> bool | |
| // | |
| // Returns the number of segments iterated over. | |
| static size_t foreach_segment(const PathView& _path, | |
| cx_invocable_with_result<bool, std::string_view, std::string_view> auto&& _op) | |
| { | |
| size_t n = 0; | |
| if (_path.empty()) | |
| { | |
| return n; | |
| } | |
| else | |
| { | |
| auto _segmentReader = PathSegmentReader(_path); | |
| while (_segmentReader) | |
| { | |
| // Invoke function with the parsed segment. | |
| const auto _segment = _segmentReader.next(); | |
| ++n; | |
| if (!std::invoke(_op, _segment, _path.substr(0, _segmentReader.head_))) | |
| { | |
| break; | |
| }; | |
| }; | |
| return n; | |
| }; | |
| }; | |
| impl::AnyFileStorage* get(const PathView& _path) const | |
| { | |
| if (_path.empty()) | |
| { | |
| return nullptr; | |
| } | |
| else | |
| { | |
| auto* _directory = &this->root(); | |
| auto _segmentReader = PathSegmentReader(_path); | |
| while (_segmentReader) | |
| { | |
| const auto _segment = _segmentReader.next(); | |
| // If this is the first segment and it is the root directory | |
| // path ("/") then we can skip it. | |
| if (_directory == &this->root() && _segment == "/") | |
| { | |
| _directory = &this->root(); | |
| // If that is the end of the path, return the root directory | |
| if (!_segmentReader) | |
| { | |
| return &this->root_; | |
| } | |
| else | |
| { | |
| continue; | |
| }; | |
| }; | |
| auto _child = _directory->child(_segment); | |
| if (!_child) | |
| { | |
| // Child not found, invalid path | |
| return nullptr; | |
| } | |
| else | |
| { | |
| if (!_segmentReader) | |
| { | |
| // Hit the bottom, return child | |
| return _child; | |
| } | |
| else | |
| { | |
| // Keep digging | |
| if (_child->type() == FileType::directory) | |
| { | |
| _directory = &this->as_directory(*_child); | |
| } | |
| else | |
| { | |
| // Not a directory | |
| return nullptr; | |
| }; | |
| }; | |
| }; | |
| }; | |
| return nullptr; | |
| }; | |
| }; | |
| template <bool PreserveFinalSeperator = false> | |
| PathView rstrip_seperators(const PathView& _path) const | |
| { | |
| auto _result = asx::rstrip_any(_path, seperator_chars_v); | |
| // If the path given is all seperator characters, return the root path | |
| if (!_path.empty() && _result.empty()) | |
| { | |
| return _path.substr(0, 1); | |
| } | |
| else | |
| { | |
| if constexpr (PreserveFinalSeperator) | |
| { | |
| if (_path.back() == '/') | |
| { | |
| return _path.substr(0, _result.size() + 1); | |
| }; | |
| }; | |
| return _result; | |
| }; | |
| }; | |
| PathView rstrip_segment(const PathView& _path) const | |
| { | |
| // /foo//bar// -> /foo//bar | |
| // /foo -> /foo | |
| auto _cleaned = rstrip_seperators(_path); | |
| if (_cleaned.empty()) { return _cleaned; }; | |
| // /foo//bar | |
| // ^ | |
| // | | |
| // /foo | |
| // ^ | |
| // | | |
| const auto _pos = _cleaned.find_last_of(seperator_chars_v); | |
| if (_pos == _cleaned.npos && _pos != _path.size()) | |
| { | |
| return {}; | |
| }; | |
| // /foo//bar -> /foo// -> /foo | |
| // /foo -> / -> / | |
| const auto _parent = _cleaned.substr(0, _pos + 1); | |
| return _parent; | |
| }; | |
| struct FileHandle | |
| { | |
| bool eof() const | |
| { | |
| return this->head_ == as_file(*this->file_).size(); | |
| }; | |
| std::optional<std::string_view> read(const size_t _max) | |
| { | |
| if (this->mode_ != Openmode::r || !this->file_) | |
| { | |
| return std::nullopt; | |
| }; | |
| if (this->eof()) | |
| { | |
| return std::nullopt; | |
| }; | |
| auto& _file = as_file(*this->file_); | |
| const auto _data = _file.read(_max, this->head_); | |
| this->head_ += _data.size(); | |
| return _data; | |
| }; | |
| size_t write(const std::string_view _data) | |
| { | |
| if (!this->file_ || this->mode_ != Openmode::w) { return 0; }; | |
| auto& _file = as_file(*this->file_); | |
| return _file.write(_data); | |
| }; | |
| void close() | |
| { | |
| if (this->file_) | |
| { | |
| this->file_->close_fd(); | |
| this->file_ = nullptr; | |
| }; | |
| }; | |
| impl::AnyFileStorage* file_; | |
| Openmode mode_; | |
| size_t head_ = 0; | |
| }; | |
| public: | |
| FileType type(const PathView& _path) const | |
| { | |
| const auto _file = this->get(_path); | |
| if (_file) | |
| { | |
| return _file->type(); | |
| } | |
| else | |
| { | |
| return FileType::null; | |
| }; | |
| }; | |
| bool exists(const PathView& _path) const | |
| { | |
| auto _file = this->get(_path); | |
| return _file != nullptr; | |
| }; | |
| bool is_directory(const PathView& _path) const | |
| { | |
| return this->type(_path) == FileType::directory; | |
| }; | |
| bool is_regular_file(const PathView& _path) const | |
| { | |
| return this->type(_path) == FileType::file; | |
| }; | |
| PathView parent_path(const PathView& _path) const | |
| { | |
| return rstrip_segment(_path); | |
| }; | |
| PathView filename(const PathView& _path) const | |
| { | |
| const auto _cleaned = this->rstrip_seperators<true>(_path); | |
| if (_cleaned.empty()) | |
| { | |
| return PathView(); | |
| } | |
| else | |
| { | |
| auto _pos = asx::find_last_of(_cleaned, seperator_chars_v, 1); | |
| if (_pos == _cleaned.npos) | |
| { | |
| _pos = 0; | |
| } | |
| else | |
| { | |
| ++_pos; | |
| }; | |
| return _cleaned.substr(_pos, _cleaned.size() - _pos); | |
| }; | |
| }; | |
| bool create(const PathView& _path, FileType _type) | |
| { | |
| // Find the parent path to create the directory in. | |
| const auto _parentPath = this->parent_path(_path); | |
| if (_parentPath.empty() || _parentPath == _path) | |
| { | |
| // Invalid path | |
| return false; | |
| }; | |
| if (const auto _type = this->type(_parentPath); | |
| _type == FileType::null) | |
| { | |
| // Parent directory does not exist. | |
| return false; | |
| } | |
| else if (_type != FileType::directory) | |
| { | |
| // Parent path is not a directory. | |
| return false; | |
| }; | |
| // Grab the filename from the path. | |
| // /foo/bin -> bin | |
| // /foo/bin/ -> bin | |
| const auto _filename = ((_type == FileType::file)? | |
| Path(this->rstrip_seperators(this->filename(_path))) : | |
| Path(this->rstrip_seperators<true>(this->filename(_path)))); | |
| ASX_CHECK(!_filename.empty()); | |
| // Try to create the child directory | |
| auto _parent = this->get(_parentPath); | |
| ASX_CHECK(_parent); | |
| auto& _parentDir = this->as_directory(*_parent); | |
| return _parentDir.create(_filename, _type); | |
| }; | |
| bool create_directory(const PathView& _path) | |
| { | |
| return this->create(_path, FileType::directory); | |
| }; | |
| bool create_directories(const PathView& _path) | |
| { | |
| bool _good = false; | |
| const auto n = this->foreach_segment(_path, | |
| [this, &_good](const PathView& _segment, const PathView& _path) -> bool | |
| { | |
| bool _result = false; | |
| if (const auto _type = this->type(_path); _type == FileType::null) | |
| { | |
| if (this->create_directory(_path)) | |
| { | |
| _result = true; | |
| _good = _result; | |
| } | |
| else | |
| { | |
| _result = false; | |
| _good = _result; | |
| }; | |
| } | |
| else | |
| { | |
| if (_type == FileType::directory) | |
| { | |
| _result = true; | |
| } | |
| else | |
| { | |
| _result = false; | |
| _good = _result; | |
| }; | |
| }; | |
| return _result; | |
| }); | |
| return _good; | |
| }; | |
| bool remove_all(const PathView& _path) | |
| { | |
| auto _file = this->get(_path); | |
| if (!_file) { return false; }; | |
| if (_file->type() != FileType::directory) { return false; }; | |
| this->as_directory(*_file).remove_all(); | |
| return true; | |
| }; | |
| bool remove_all() | |
| { | |
| this->root().remove_all(); | |
| return true; | |
| }; | |
| std::vector<FileName> contents(const PathView& _path) const | |
| { | |
| // Grab the directory file. | |
| auto _file = this->get(_path); | |
| // Check yoself | |
| if (!_file || _file->type() != FileType::directory) { return {}; }; | |
| // View as a directory. | |
| auto& _directory = this->as_directory(*_file); | |
| // Convert into output type. | |
| auto _contents = std::vector<FileName>(_directory.child_count()); | |
| std::ranges::copy(_directory.child_paths(), _contents.begin()); | |
| return _contents; | |
| }; | |
| bool touch(const PathView& _path) | |
| { | |
| auto _parentPath = this->parent_path(_path); | |
| if (_parentPath.empty()) | |
| { | |
| _parentPath = this->root().name_; | |
| } | |
| else | |
| { | |
| if (!this->is_directory(_parentPath)) | |
| { | |
| return false; | |
| }; | |
| }; | |
| auto& _parentDirectory = this->as_directory(*this->get(_parentPath)); | |
| return _parentDirectory.create(FileName(this->filename(_path)), FileType::file); | |
| }; | |
| int open(const PathView& _path, Openmode _mode) | |
| { | |
| auto _file = this->get(_path); | |
| if (!_file) | |
| { | |
| if (_mode == Openmode::w) | |
| { | |
| if (!this->touch(_path)) | |
| { | |
| return -1; | |
| } | |
| else | |
| { | |
| _file = this->get(_path); | |
| if (!_file) | |
| { | |
| return -1; | |
| }; | |
| }; | |
| } | |
| else | |
| { | |
| return -1; | |
| }; | |
| } | |
| else if (_file->has_open_fd()) | |
| { | |
| return -1; | |
| } | |
| else if (_file->type() != FileType::file) | |
| { | |
| return -1; | |
| } | |
| else | |
| { | |
| // Clear the file contents if opening with write | |
| if (_mode == Openmode::w) | |
| { | |
| as_file(*_file).clear(); | |
| }; | |
| }; | |
| const auto _fd = ++this->fd_count_; | |
| auto _fHandle = FileHandle{}; | |
| _fHandle.file_ = _file; | |
| _fHandle.mode_ = _mode; | |
| _file->set_fd(_fd); | |
| this->fds_.insert_or_assign(_fd, _fHandle); | |
| return _fd; | |
| }; | |
| void close(int _fd) | |
| { | |
| if (const auto it = this->fds_.find(_fd); it != this->fds_.end()) | |
| { | |
| it->second.close(); | |
| this->fds_.erase(it); | |
| }; | |
| }; | |
| /** | |
| * @brief Attempts to read some data from a file. | |
| * @param _fd File handle. | |
| * @param _max Max chars to read. | |
| * @return String read from file, or null on error. | |
| */ | |
| std::optional<std::string_view> read(int _fd, const size_t _max) | |
| { | |
| auto& _fileHandles = this->fds_; | |
| if (auto it = _fileHandles.find(_fd); it != _fileHandles.end()) | |
| { | |
| // Read from the file | |
| auto& _file = it->second; | |
| return _file.read(_max); | |
| } | |
| else | |
| { | |
| // Invalid file handle | |
| return std::nullopt; | |
| }; | |
| }; | |
| /** | |
| * @brief Attempts to write some data to the file. | |
| * @param _fd File handle. | |
| * @param _data The data to write. | |
| * @return Number of bytes written, returns -1 on error. | |
| */ | |
| int write(int _fd, const std::string_view _data) | |
| { | |
| auto& _fileHandles = this->fds_; | |
| if (auto it = _fileHandles.find(_fd); it != _fileHandles.end()) | |
| { | |
| // Write to the file | |
| auto& _file = it->second; | |
| const auto _writeCount = static_cast<int>(_file.write(_data)); | |
| // Return the number of bytes written. | |
| return _writeCount; | |
| } | |
| else | |
| { | |
| // Invalid file handle | |
| return -1; | |
| }; | |
| }; | |
| std::vector<std::byte> dump(const PathView& _path) const | |
| { | |
| const auto _file = this->get(_path); | |
| if (!_file) { return {}; }; | |
| auto& f = this->as_file(*_file); | |
| return f.data_; | |
| }; | |
| void upload(const PathView& _path, std::span<const std::byte> _data) | |
| { | |
| this->create(_path, FileType::file); | |
| const auto _file = this->get(_path); | |
| if (!_file) { | |
| return; | |
| }; | |
| auto& f = this->as_file(*_file); | |
| f.data_.assign(_data.begin(), _data.end()); | |
| }; | |
| /** | |
| * @brief Resets the filesystem to only contain a root directory. | |
| */ | |
| void clear() noexcept | |
| { | |
| this->as_directory(this->root_).remove_all(); | |
| }; | |
| void close_open_handles() | |
| { | |
| for (auto& [_fd, _fh] : this->fds_) | |
| { | |
| _fh.close(); | |
| }; | |
| this->fds_.clear(); | |
| }; | |
| Filesystem() : | |
| root_(impl::AnyFileStorage::create_directory("/")) | |
| {}; | |
| private: | |
| mutable impl::AnyFileStorage root_; | |
| /** | |
| * @brief Open file handles. | |
| */ | |
| std::map<int, FileHandle> fds_; | |
| int fd_count_ = 6959; | |
| }; | |
| std::vector<std::byte> serialize(const Filesystem& _filesystem); | |
| bool deserialize(std::span<const std::byte> _data, Filesystem& _filesystem); | |
| Filesystem copy_directory_as_vfs(const PathView& _path); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment