#ifndef GUL_VIRTUAL_FILESYSTEM_H #define GUL_VIRTUAL_FILESYSTEM_H #include #include #include #include #include #include #include #include namespace gul { #define USE_UNION /** * @brief is_base_of * @param base * @param full * @return * * Returns true if base exists at the start of full. For strings, this would be equivelant too * full.starts_with(base) */ inline bool is_base_of(std::filesystem::path const & base, std::filesystem::path const & full) { auto relPath2 = full.lexically_relative(base); if(!relPath2.empty()) { if(*relPath2.begin() == "..") { return false; } } return true; } struct File { std::vector data; }; struct Directory { std::vector> descriptors; }; struct Custom { std::string type; std::any value; }; struct Mount { using host_path_type = std::filesystem::path; #ifdef USE_UNION std::vector hosts; Mount() = default; Mount(host_path_type const &r) { hosts.push_back(r); } Mount(std::vector const &r) : hosts(r) { } template void for_each(host_path_type stem, callable_t && CC) const { std::set _set; for(auto it = hosts.rbegin(); it!=hosts.rend(); ++it) { auto & host = *it; if(!std::filesystem::exists(host / stem)) continue; for(auto & A : std::filesystem::directory_iterator(host / stem)) { auto stem2 = A.path().lexically_relative(host/stem); if(_set.insert(stem2).second) // need a set so that multiple items { // dont get duplicated CC(stem2); } } } } bool is_directory(std::filesystem::path const & stem) const { for(auto it = hosts.rbegin(); it!=hosts.rend(); ++it) { if(std::filesystem::exists( *it / stem)) { return std::filesystem::is_directory(*it/stem); } } return false; } bool is_file(std::filesystem::path const & stem) const { for(auto it = hosts.rbegin(); it!=hosts.rend(); ++it) { if(std::filesystem::exists( *it / stem)) { return std::filesystem::is_regular_file(*it/stem); } } return false; } bool exists(std::filesystem::path const & stem) const { for(auto it = hosts.rbegin(); it!=hosts.rend(); ++it) { if(std::filesystem::exists( *it / stem)) { return true; } } return false; } bool file_size(std::filesystem::path const & stem) const { for(auto it = hosts.rbegin(); it!=hosts.rend(); ++it) { if(std::filesystem::exists( *it / stem)) { return std::filesystem::file_size(*it/stem); } } return false; } host_path_type host_path(std::filesystem::path const & stem) const { for(auto it = hosts.rbegin(); it!=hosts.rend(); ++it) { if(std::filesystem::exists( *it / stem)) { return *it/stem; } } if(hosts.size()) { return hosts[0] / stem; } return {}; } #else host_path_type host; bool is_directory(std::filesystem::path const & stem) const { return std::filesystem::is_directory( host / stem); } bool is_file(std::filesystem::path const & stem) const { return std::filesystem::is_regular_file( host / stem); } bool exists(std::filesystem::path const & stem) const { return std::filesystem::exists( host / stem); } auto file_size(std::filesystem::path const & stem) const { return std::filesystem::file_size(host / stem); } auto host_path(std::filesystem::path const & stem) const { return host / stem; } #endif }; using FileDescriptor = std::variant; struct VFS { using vfs_path_type = std::filesystem::path; using host_path_type = std::filesystem::path; VFS() { desc["/"] = Directory{}; } void mkdir(vfs_path_type const pf) { if(is_directory(pf.parent_path()) ) { desc[pf] = Directory{}; } else { mkdir(pf.parent_path()); mkdir(pf); } } void mount(vfs_path_type const & pf, host_path_type const & host) { desc[pf] = Mount(host); } void mount(vfs_path_type const & pf, std::vector const & host) { desc[pf] = Mount(host); } void unmount(vfs_path_type const & pf) { desc.erase(pf); } void list(vfs_path_type const & p) const { for_each(p, [](auto v) { std::cout << v << std::endl; }); } template bool open(vfs_path_type const & p, callable_t && C) const { auto hp = host_path(p); if(std::filesystem::exists(hp)) { std::ifstream in(hp); C(in); return true; } return false; } template void for_each_file(vfs_path_type const & p, callable_t && C) const { return for_each(p,C); } template void for_each_file_recursive(vfs_path_type const & root, callable_t && C) const { return for_each_file(root, [root,&C, this](auto const & _p) { if(is_directory(root / _p)) { for_each_file_recursive(root / _p, C); } else { C(root / _p); } }); } template void for_each(vfs_path_type const & p, callable_t && CC) const { auto [mnt, stem] = splitMount(p); if(mnt.empty()) { // The path, p, does not contain a mount point // check if p exists in the descriptors auto it = desc.find(p); if(it != desc.end()) { ++it; while(it != desc.end()) { if(is_base_of(p, it->first)) { auto removeBase = it->first.lexically_relative(p); if(1==std::distance(removeBase.begin(), removeBase.end())) { CC(removeBase); } } ++it; } } return; } auto & M = desc.at(mnt); if(std::holds_alternative(M)) { #ifdef USE_UNION std::get(M).for_each(stem, CC); #else auto & host = std::get(M).host; for(auto & A : std::filesystem::directory_iterator(host / stem)) { CC(A.path().lexically_relative(host / stem)); } #endif } } /** * @brief exists * @param pf * @return * * Returns true if the file exists in the VFS. */ bool exists(vfs_path_type const & pf) const { assert(pf.is_absolute()); auto it = desc.find(pf); // exists in the Virtual files if(it != desc.end()) { return true; } auto [mnt, stem] = splitMount(pf); if(!mnt.empty()) { auto & M = std::get(desc.at(mnt)); return M.exists(stem); } return false; } bool is_mount(vfs_path_type const & pf) const { auto it = desc.find(pf); if(it == desc.end()) return false; return std::holds_alternative(it->second); } /** * @brief splitMount * @param pf * @return * * Given a abs path in the VFS. return two components [mount,stem] * where mount is a path to an active mount point and stem is the * path within the mount */ std::pair splitMount(vfs_path_type const & pf) const { vfs_path_type left = pf.relative_path(); vfs_path_type right; while(!left.empty()) { // if 'left' is a mount point to the host // then check if right exists the filesystem if(is_mount(vfs_path_type("/") / left)) { return {vfs_path_type("/") / left, right}; } // remove the filename from 'left' and // append it to the front of 'right' // left = path/to/some // right = file.txt right = right.empty() ? left.filename() : (left.filename() / right); left = left.parent_path(); } return {{},pf}; } bool is_directory(vfs_path_type const & pf) const { assert(pf.is_absolute()); auto it = desc.find(pf); // exists in the Virtual files if(it != desc.end()) { return std::holds_alternative(it->second) || std::holds_alternative(it->second); } auto [mnt, stem] = splitMount(pf); if(!mnt.empty()) { auto & M = std::get(desc.at(mnt)); return M.is_directory(stem); } return false; } bool is_file(vfs_path_type const & pf) const { assert(pf.is_absolute()); auto it = desc.find(pf); // exists in the Virtual files if(it != desc.end()) { return std::holds_alternative(it->second); } auto [mnt, stem] = splitMount(pf); if(!mnt.empty()) { auto & M = std::get(desc.at(mnt)); return M.is_file(stem); } return false; } uintmax_t file_size(vfs_path_type const & pf) const { assert(pf.is_absolute()); auto it = desc.find(pf); // exists in the Virtual files if(it != desc.end()) { if(std::holds_alternative(it->second)) { return std::get(it->second).data.size(); } } auto [mnt, stem] = splitMount(pf); if(!mnt.empty()) { auto & M = std::get(desc.at(mnt)); return M.file_size(stem); } return 0; } void print() { for(auto &[a,b] : desc) { std::cout << a << std::endl; } } host_path_type host_path(vfs_path_type const pf) const { assert(pf.is_absolute()); auto it = desc.find(pf); // exists in the Virtual files if(it != desc.end()) { return {}; //if(std::holds_alternative(it->second)) //{ // auto & F = std::get(it->second); // std::istringstream() F.data //} } auto [mnt, stem] = splitMount(pf); if(!mnt.empty()) { auto & M = std::get(desc.at(mnt)); return M.host_path(stem); } return {}; } std::map desc; }; } #endif