/* TessesFramework a library to make C++ easier for me, used in CrossLang: https://git.tesses.org/tesses50/crosslang Copyright (C) 2026 Mike Nolan SPDX-License-Identifier: GPL-3.0-or-later WITH TessesFramework-Exception-1.0 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TessesFramework/Filesystem/VFS.hpp" #include "TessesFramework/Filesystem/LocalFS.hpp" #include "TessesFramework/Http/HttpUtils.hpp" #include namespace Tesses::Framework::Filesystem { VFSPathEnumeratorItterator::VFSPathEnumeratorItterator() { this->enumerator = nullptr; } VFSPathEnumeratorItterator::VFSPathEnumeratorItterator( VFSPathEnumerator *enumerator) { this->enumerator = enumerator; } VFSPathEnumeratorItterator &VFSPathEnumeratorItterator::operator++(int) { enumerator->MoveNext(); return *this; } VFSPathEnumeratorItterator &VFSPathEnumeratorItterator::operator++() { enumerator->MoveNext(); return *this; } VFSPath &VFSPathEnumeratorItterator::operator*() { std::filesystem::directory_iterator i; if (enumerator != nullptr) return enumerator->Current; return this->e; } VFSPath *VFSPathEnumeratorItterator::operator->() { if (enumerator != nullptr) return &enumerator->Current; return nullptr; } bool VFSPathEnumeratorItterator::operator!=(VFSPathEnumeratorItterator right) { if (enumerator == right.enumerator) { return false; } if (right.enumerator == nullptr) { auto r = !enumerator->IsDone(); return r; } return true; } bool VFSPathEnumeratorItterator::operator==(VFSPathEnumeratorItterator right) { if (enumerator == right.enumerator) { return true; } if (right.enumerator == nullptr) return enumerator->IsDone(); return false; } VFSPathEnumerator::VFSPathEnumerator() { data = nullptr; } VFSPathEnumerator *VFSPathEnumerator::MakePointer() { VFSPathEnumerator *enumerator = new VFSPathEnumerator(); enumerator->Current = Current; enumerator->data = data; return enumerator; } VFSPathEnumerator::VFSPathEnumerator(std::function moveNext, std::function destroy) { data = std::make_shared(moveNext, destroy); } bool VFSPathEnumerator::MoveNext() { if (this->data) { auto r = data->moveNext(Current); if (!r) data->eof = true; return r; } return false; } bool VFSPathEnumerator::IsDone() { if (this->data) { return data->eof; } return true; } VFSPathEnumeratorItterator VFSPathEnumerator::begin() { MoveNext(); VFSPathEnumeratorItterator ittr(this); return ittr; } VFSPathEnumeratorItterator VFSPathEnumerator::end() { return VFSPathEnumeratorItterator(); } VFSPath operator/(VFSPath p, VFSPath p2) { return VFSPath(p, p2); } VFSPath operator/(VFSPath p, std::string p2) { return VFSPath(p, p2); } VFSPath operator/(std::string p, VFSPath p2) { return VFSPath(p, p2); } VFSPath operator+(VFSPath p, VFSPath p2) { VFSPath pout; pout.relative = p.relative; if (p.path.size() > 1) { pout.path.insert(pout.path.end(), p.path.begin(), p.path.end() - 1); } std::string mid = {}; if (!p.path.empty()) { mid.append(p.path.back()); } if (!p2.path.empty()) { mid.append(p2.path.front()); } pout.path.push_back(mid); if (p2.path.size() > 1) { pout.path.insert(pout.path.end(), p2.path.begin() + 1, p2.path.end()); } return pout; } VFSPath operator+(VFSPath p, std::string p2) { return p + VFSPath(p2); } VFSPath operator+(std::string p, VFSPath p2) { return VFSPath(p) + p2; } bool operator==(VFSPath p, VFSPath p2) { if (p.relative != p2.relative) return false; if (p.path.size() != p2.path.size()) return false; for (size_t i = 0; i < p.path.size(); i++) if (p.path[i] != p2.path[i]) return false; return true; } bool operator!=(VFSPath p, VFSPath p2) { if (p.relative != p2.relative) return true; if (p.path.size() != p2.path.size()) return true; for (size_t i = 0; i < p.path.size(); i++) if (p.path[i] != p2.path[i]) return true; return false; } bool operator==(std::string p, VFSPath p2) { return VFSPath(p) == p2; } bool operator!=(std::string p, VFSPath p2) { return VFSPath(p) != p2; } bool operator==(VFSPath p, std::string p2) { return p == VFSPath(p2); } bool operator!=(VFSPath p, std::string p2) { return p != VFSPath(p2); } VFSPath VFS::ReadLink(VFSPath path) { return VFSPath("/"); } VFSPath VFSPath::GetAbsoluteCurrentDirectory() { auto p = std::filesystem::current_path(); return LocalFS->SystemToVFSPath(p.string()); } void VFSPath::SetAbsoluteCurrentDirectory(VFSPath path) { auto res = LocalFS->VFSPathToSystem(path); std::filesystem::path mpath = res; std::filesystem::current_path(mpath); } VFSPath VFSPath::MakeAbsolute() const { return MakeAbsolute(GetAbsoluteCurrentDirectory()); } VFSPath VFSPath::MakeAbsolute(VFSPath curDir) const { if (!this->relative) return *this; VFSPath p2 = curDir / *this; return p2.CollapseRelativeParents(); } VFSPath VFSPath::MakeRelative() const { return MakeRelative(GetAbsoluteCurrentDirectory()); } FIFOCreationResult VFS::CreateFIFO(VFSPath path, uint32_t mode) { return FIFOCreationResult::Unsupported; } bool VFS::DirectoryExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsDirectory(); } return false; } bool VFS::RegularFileExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsRegularFile(); } return false; } bool VFS::SymlinkExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsSymlink(); } return false; } bool VFS::CharacterDeviceExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsCharDevice(); } return false; } bool VFS::BlockDeviceExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsBlockDevice(); } return false; } bool VFS::SocketFileExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsSocket(); } return false; } bool VFS::FIFOFileExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsFIFO(); } return false; } bool VFS::FileExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return !data.IsDirectory(); } return false; } bool VFS::SpecialFileExists(VFSPath path) { StatData data; if (this->Stat(path, data)) { return data.IsSpecial(); } return false; } VFSPath VFSPath::MakeRelative(VFSPath toMakeRelativeTo) const { if (this->relative) return *this; size_t i; size_t len = std::min(toMakeRelativeTo.path.size(), this->path.size()); for (i = 0; i < len; i++) { if (this->path[i] != toMakeRelativeTo.path[i]) break; } if (i == this->path.size() - 1 && i == toMakeRelativeTo.path.size() - 1) { std::vector paths{this->path[this->path.size() - 1]}; VFSPath path(paths); path.relative = true; return path; } std::vector parts(this->path.begin() + i, this->path.end()); if (i < toMakeRelativeTo.path.size()) { for (; i < toMakeRelativeTo.path.size(); i++) { parts.insert(parts.begin(), ".."); } } VFSPath p2(parts); p2.relative = true; return p2; } VFSPath VFSPath::CollapseRelativeParents() const { std::vector parts; for (auto item : this->path) { if (item == "..") { if (!parts.empty()) { parts.erase(parts.end() - 1); } } else if (item == ".") { // do nothing but don't emit this } else { parts.push_back(item); } } VFSPath newpath; newpath.relative = relative; newpath.path = parts; return newpath; } VFSPath VFSPath::CurrentDirectoryAsRelative() { VFSPath path; path.relative = true; return path; } std::vector VFSPath::SplitPath(std::string path) { std::vector parts; std::string curPath = {}; for (auto c : path) { if (c == '/') { if (!curPath.empty()) { parts.push_back(curPath); curPath = {}; } } #if defined(WIN32) else if (c == '\\') { if (!curPath.empty()) { parts.push_back(curPath); curPath = {}; } } #endif else { curPath.push_back(c); } } if (!curPath.empty()) { parts.push_back(curPath); curPath = {}; } return parts; } VFSPath::VFSPath() { this->relative = false; } VFSPath::VFSPath(std::vector p) { this->path = p; } VFSPath VFSPath::ParseUriPath(std::string path) { std::string builder = {}; VFSPath vpath; vpath.relative = true; if (!path.empty() && path[0] == '/') vpath.relative = false; for (auto item : path) { if (item == '/') { if (!builder.empty()) { vpath.path.push_back(builder); builder.clear(); } } else { builder.push_back(item); } } if (!builder.empty()) { vpath.path.push_back(builder); } return vpath; } bool VFSPath::HasExtension() const { if (this->path.empty()) return false; auto &str = this->path.back(); auto index = str.find_last_of('.'); if (index == std::string::npos) return false; return true; } std::string VFSPath::GetExtension() const { if (this->path.empty()) return {}; auto &str = this->path.back(); auto index = str.find_last_of('.'); if (index == std::string::npos) return {}; return str.substr(index); } void VFSPath::ChangeExtension(std::string ext) { if (this->path.empty()) return; auto &str = this->path.back(); auto index = str.find_last_of('.'); if (index != std::string::npos) { str = str.substr(0, index); } if (ext.empty()) return; if (ext[0] != '.') { str += '.'; str += ext; } else { str += ext; } } void VFSPath::RemoveExtension() { ChangeExtension({}); } VFSPath::VFSPath(std::string str) { this->path = SplitPath(str); this->relative = true; if (!str.empty()) { if (str.front() == '/') this->relative = false; #if defined(_WIN32) if (str.front() == '\\') this->relative = false; #endif if (!this->path.empty()) { auto firstPartPath = this->path.front(); if (!firstPartPath.empty() && firstPartPath.back() == ':') this->relative = false; } } } void VFS::CreateDirectory(VFSPath path) {} void VFS::DeleteDirectory(VFSPath path) {} VFSPath::VFSPath(VFSPath p1, VFSPath p2) { this->relative = p1.relative; this->path.insert(this->path.end(), p1.path.begin(), p1.path.end()); this->path.insert(this->path.end(), p2.path.begin(), p2.path.end()); } VFSPath::VFSPath(VFSPath p1, std::string subpath) : VFSPath(p1, VFSPath(subpath)) {} VFSPath VFSPath::GetParent() const { std::vector paths; if (this->path.empty()) return VFSPath(); if (!this->relative && this->path.size() == 1 && !this->path[0].empty() && this->path[0].back() == ':') return *this; paths.insert(paths.begin(), this->path.begin(), this->path.end() - 1); auto res = VFSPath(paths); res.relative = this->relative; return res; } std::string VFSPath::GetFileName() const { if (this->path.empty()) return ""; return this->path.back(); } std::string VFSPath::ToString() const { if (this->path.empty() && !this->relative) return "/"; if (!this->relative && this->path.size() == 1 && !this->path[0].empty() && this->path[0].back() == ':') return this->path[0] + "/"; bool first = true; std::string p = {}; for (auto item : this->path) { if (!(first && !item.empty() && item.back() == ':') && !(first && this->relative)) p.push_back('/'); p.append(item); first = false; } return p; } void VFS::DeleteFile(VFSPath path) {} void VFS::MoveFile(VFSPath src, VFSPath dest) { { auto srcStrm = this->OpenFile(src, "rb"); if (!srcStrm->CanRead()) return; auto destStrm = this->OpenFile(dest, "wb"); if (!destStrm->CanWrite()) return; srcStrm->CopyTo(destStrm); } VFS::DeleteFile(src); } VFS::~VFS() {} void VFS::MoveDirectory(VFSPath src, VFSPath dest) { for (auto item : EnumeratePaths(src)) { if (DirectoryExists(item)) { MoveDirectory(item, VFSPath(dest, item.GetFileName())); } else { MoveFile(item, VFSPath(dest, item.GetFileName())); } } DeleteDirectory(src); } void VFS::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile) {} void VFS::CreateHardlink(VFSPath existingFile, VFSPath newName) {} void VFS::DeleteDirectoryRecurse(VFSPath path) { if (!DirectoryExists(path)) return; for (auto item : EnumeratePaths(path)) { if (DirectoryExists(item)) { DeleteDirectoryRecurse(item); } else { DeleteFile(item); } } DeleteDirectory(path); } void VFS::GetDate(VFSPath path, Date::DateTime &lastWrite, Date::DateTime &lastAccess) { StatData data; if (Stat(path, data)) { lastWrite = data.LastModified; lastAccess = data.LastAccess; } } void VFS::SetDate(VFSPath path, Date::DateTime lastWrite, Date::DateTime lastAccess) {} bool VFS::StatVFS(VFSPath path, StatVFSData &data) { data.BlockSize = 512; data.Blocks = 10000000000000; data.BlocksFree = 10000000000000; data.BlocksAvailable = 10000000000000; data.AvailableInodes = 10000000000000; data.TotalInodes = 10000000000000; data.FreeInodes = 10000000000000; data.FragmentSize = 512; data.Id = 85138; data.Flags = 0; data.MaxNameLength = 255; return true; } void VFS::Chmod(VFSPath path, uint32_t mode) {} void VFS::Chown(VFSPath path, uint32_t uid, uint32_t gid) {} void VFS::Close() {} void VFS::Lock(VFSPath path) {} void VFS::Unlock(VFSPath path) {} std::shared_ptr VFS::CreateWatcher(std::shared_ptr vfs, VFSPath path) { return std::make_shared(vfs, path); } void FSWatcher::SetEnabled(bool enabled) { if (this->enabled == enabled) return; this->enabled = enabled; this->SetEnabledImpl(enabled); } bool FSWatcher::GetEnabled() { return this->enabled; } void FSWatcher::SetEnabledImpl(bool enabled) {} std::shared_ptr FSWatcher::GetFilesystem() { return this->vfs; } const VFSPath &FSWatcher::GetPath() { return this->path; } FSWatcher::FSWatcher(std::shared_ptr vfs, VFSPath path) : vfs(vfs), path(path) {} std::shared_ptr FSWatcher::Create(std::shared_ptr vfs, VFSPath path) { return vfs->CreateWatcher(vfs, path); } bool FSWatcherEvent::IsEvent(FSWatcherEventType e) { if (e == FSWatcherEventType::All) return this->type != FSWatcherEventType::None; if (e == FSWatcherEventType::Moved) return ((uint32_t)this->type & (uint32_t)FSWatcherEventType::Moved) == (uint32_t)FSWatcherEventType::Moved; if (e == FSWatcherEventType::Closed) return ((uint32_t)this->type & (uint32_t)FSWatcherEventType::Closed) != 0; return (uint32_t)this->type & (uint32_t)e; } std::string FSWatcherEvent::ToString() { if (IsEvent(FSWatcherEventType::Moved)) { return (this->isDir ? "Moved directory " : "Moved file ") + this->src.ToString() + " -> " + this->dest.ToString(); } else if (IsEvent(FSWatcherEventType::MoveOld)) { return (this->isDir ? "Move source directory " : "Move source file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::MoveNew)) { return (this->isDir ? "Move destination directory " : "Move destination file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::Accessed)) { return (this->isDir ? "Accessed directory " : "Accessed file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::AttributeChanged)) { return (this->isDir ? "Changed attr on directory " : "Changed attr on file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::Writen)) { return (this->isDir ? "Finished changing directory " : "Finished writing to file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::Read)) { return (this->isDir ? "Finished reading directory " : "Finished reading from file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::Created)) { return (this->isDir ? "Created directory " : "Created file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::Deleted)) { return (this->isDir ? "Deleted directory " : "Deleted file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::WatchEntryDeleted)) { return (this->isDir ? "Deleted watched directory " : "Deleted watched file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::Modified)) { return (this->isDir ? "Modified directory " : "Modified file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::WatchEntryMoved)) { return (this->isDir ? "Moved watched directory " : "Moved watched file ") + this->src.ToString(); } else if (IsEvent(FSWatcherEventType::Opened)) { return (this->isDir ? "Opened directory " : "Opened file ") + this->src.ToString(); } return ""; } } // namespace Tesses::Framework::Filesystem