464 lines
11 KiB
V
464 lines
11 KiB
V
module vfs_nested
|
|
|
|
import incubaid.herolib.vfs
|
|
import time
|
|
|
|
// NestedVFS represents a VFS that can contain multiple nested VFS instances
|
|
pub struct NestedVFS {
|
|
mut:
|
|
vfs_map map[string]vfs.VFSImplementation @[skip] // Map of path prefixes to VFS implementations
|
|
}
|
|
|
|
// new creates a new NestedVFS instance
|
|
pub fn new() &NestedVFS {
|
|
return &NestedVFS{
|
|
vfs_map: map[string]vfs.VFSImplementation{}
|
|
}
|
|
}
|
|
|
|
// add_vfs adds a new VFS implementation at the specified path prefix
|
|
pub fn (mut self NestedVFS) add_vfs(prefix string, impl vfs.VFSImplementation) ! {
|
|
if prefix in self.vfs_map {
|
|
return error('VFS already exists at prefix: ${prefix}')
|
|
}
|
|
self.vfs_map[prefix] = impl
|
|
}
|
|
|
|
// find_vfs finds the appropriate VFS implementation for a given path
|
|
fn (self &NestedVFS) find_vfs(path string) !(vfs.VFSImplementation, string) {
|
|
if path == '' || path == '/' {
|
|
return self, '/'
|
|
}
|
|
|
|
// Sort prefixes by length (longest first) to match most specific path
|
|
mut prefixes := self.vfs_map.keys()
|
|
prefixes.sort(a.len > b.len)
|
|
|
|
for prefix in prefixes {
|
|
if path.starts_with(prefix) {
|
|
relative_path := path[prefix.len..]
|
|
return self.vfs_map[prefix], relative_path
|
|
}
|
|
}
|
|
return error('No VFS found for path: ${path}')
|
|
}
|
|
|
|
// Implementation of VFSImplementation interface
|
|
pub fn (mut self NestedVFS) root_get() !vfs.FSEntry {
|
|
// Return a special root entry that represents the nested VFS
|
|
return &RootEntry{
|
|
metadata: vfs.Metadata{
|
|
id: 0
|
|
name: ''
|
|
path: '/'
|
|
file_type: .directory
|
|
size: 0
|
|
created_at: 0
|
|
modified_at: 0
|
|
accessed_at: 0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) delete(path string) ! {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
return impl.delete(rel_path)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) link_delete(path string) ! {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
return impl.link_delete(rel_path)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) file_create(path string) !vfs.FSEntry {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
sub_entry := impl.file_create(rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(sub_entry, prefix)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) file_read(path string) ![]u8 {
|
|
// Special handling for macOS resource fork files (._*)
|
|
if path.starts_with('/._') || path.contains('/._') {
|
|
// Return empty data for resource fork files
|
|
return []u8{}
|
|
}
|
|
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
return impl.file_read(rel_path)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) file_write(path string, data []u8) ! {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
return impl.file_write(rel_path, data)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) file_delete(path string) ! {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
return impl.file_delete(rel_path)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) dir_create(path string) !vfs.FSEntry {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
sub_entry := impl.dir_create(rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(sub_entry, prefix)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) dir_list(path string) ![]vfs.FSEntry {
|
|
// Special case for root directory
|
|
if path == '' || path == '/' {
|
|
mut entries := []vfs.FSEntry{}
|
|
for prefix, mut impl in self.vfs_map {
|
|
root := impl.root_get() or { continue }
|
|
entries << &MountEntry{
|
|
metadata: vfs.Metadata{
|
|
id: 0
|
|
name: prefix
|
|
path: prefix
|
|
file_type: .directory
|
|
size: 0
|
|
created_at: 0
|
|
modified_at: 0
|
|
accessed_at: 0
|
|
}
|
|
impl: impl
|
|
}
|
|
}
|
|
return entries
|
|
}
|
|
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
sub_entries := impl.dir_list(rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
// Convert all entries to nested entries
|
|
mut entries := []vfs.FSEntry{}
|
|
for sub_entry in sub_entries {
|
|
entries << self.nester_entry(sub_entry, prefix)
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) dir_delete(path string) ! {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
return impl.dir_delete(rel_path)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) exists(path string) bool {
|
|
// QUESTION: should root be nestervfs's own?
|
|
if path == '' || path == '/' {
|
|
return true
|
|
}
|
|
|
|
// // Special handling for macOS resource fork files (._*)
|
|
// if path.starts_with('/._') || path.contains('/._') {
|
|
// return true // Pretend these files exist for WebDAV Class 2 compatibility
|
|
// }
|
|
|
|
mut impl, rel_path := self.find_vfs(path) or { return false }
|
|
return impl.exists(rel_path)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) get(path string) !vfs.FSEntry {
|
|
if path == '' || path == '/' {
|
|
return self.root_get()
|
|
}
|
|
|
|
// // Special handling for macOS resource fork files (._*)
|
|
// if path.starts_with('/._') || path.contains('/._') {
|
|
// // Extract the filename from the path
|
|
// filename := path.all_after_last('/')
|
|
|
|
// // Create a dummy resource fork entry
|
|
// return &ResourceForkEntry{
|
|
// metadata: vfs.Metadata{
|
|
// id: 0
|
|
// name: filename
|
|
// file_type: .file
|
|
// size: 0
|
|
// created_at: time.now().unix()
|
|
// modified_at: time.now().unix()
|
|
// accessed_at: time.now().unix()
|
|
// }
|
|
// path: path
|
|
// }
|
|
// }
|
|
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
|
|
// now must convert entry of nested fvs to entry of nester
|
|
sub_entry := impl.get(rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(sub_entry, prefix)
|
|
}
|
|
|
|
// nester_entry converts an FSEntry from a sub VFS to an FSEntry for the nester VFS
|
|
// by prefixing the nested VFS's path onto the FSEntry's path
|
|
fn (self &NestedVFS) nester_entry(entry vfs.FSEntry, prefix string) vfs.FSEntry {
|
|
return &NestedEntry{
|
|
original: entry
|
|
prefix: prefix
|
|
}
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) rename(old_path string, new_path string) !vfs.FSEntry {
|
|
mut old_impl, old_rel_path := self.find_vfs(old_path)!
|
|
mut new_impl, new_rel_path := self.find_vfs(new_path)!
|
|
|
|
if old_impl != new_impl {
|
|
return error('Cannot rename across different VFS implementations')
|
|
}
|
|
|
|
renamed_file := old_impl.rename(old_rel_path, new_rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == old_impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(renamed_file, prefix)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) copy(src_path string, dst_path string) !vfs.FSEntry {
|
|
mut src_impl, src_rel_path := self.find_vfs(src_path)!
|
|
mut dst_impl, dst_rel_path := self.find_vfs(dst_path)!
|
|
|
|
if src_impl == dst_impl {
|
|
copied_file := src_impl.copy(src_rel_path, dst_rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == src_impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(copied_file, prefix)
|
|
}
|
|
|
|
// Copy across different VFS implementations
|
|
// TODO: Q: What if it's not file? What if it's a symlink or directory?
|
|
data := src_impl.file_read(src_rel_path)!
|
|
new_file := dst_impl.file_create(dst_rel_path)!
|
|
dst_impl.file_write(dst_rel_path, data)!
|
|
|
|
// Find the prefix for the destination VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == dst_impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(new_file, prefix)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) move(src_path string, dst_path string) !vfs.FSEntry {
|
|
mut src_impl, src_rel_path := self.find_vfs(src_path)!
|
|
_, dst_rel_path := self.find_vfs(dst_path)!
|
|
moved_file := src_impl.move(src_rel_path, dst_rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == src_impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(moved_file, prefix)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) link_create(target_path string, link_path string) !vfs.FSEntry {
|
|
mut impl, rel_path := self.find_vfs(link_path)!
|
|
link_entry := impl.link_create(target_path, rel_path)!
|
|
|
|
// Find the prefix for this VFS implementation
|
|
mut prefix := ''
|
|
for p, v in self.vfs_map {
|
|
if v == impl {
|
|
prefix = p
|
|
break
|
|
}
|
|
}
|
|
|
|
return self.nester_entry(link_entry, prefix)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) link_read(path string) !string {
|
|
mut impl, rel_path := self.find_vfs(path)!
|
|
return impl.link_read(rel_path)
|
|
}
|
|
|
|
pub fn (mut self NestedVFS) destroy() ! {
|
|
for _, mut impl in self.vfs_map {
|
|
impl.destroy()!
|
|
}
|
|
}
|
|
|
|
// Special entry types for the nested VFS
|
|
struct RootEntry {
|
|
metadata vfs.Metadata
|
|
}
|
|
|
|
fn (e &RootEntry) get_metadata() vfs.Metadata {
|
|
return e.metadata
|
|
}
|
|
|
|
fn (e &RootEntry) get_path() string {
|
|
return '/'
|
|
}
|
|
|
|
// is_dir returns true if the entry is a directory
|
|
pub fn (self &RootEntry) is_dir() bool {
|
|
return self.metadata.file_type == .directory
|
|
}
|
|
|
|
// is_file returns true if the entry is a file
|
|
pub fn (self &RootEntry) is_file() bool {
|
|
return self.metadata.file_type == .file
|
|
}
|
|
|
|
// is_symlink returns true if the entry is a symlink
|
|
pub fn (self &RootEntry) is_symlink() bool {
|
|
return self.metadata.file_type == .symlink
|
|
}
|
|
|
|
pub struct MountEntry {
|
|
pub mut:
|
|
metadata vfs.Metadata
|
|
impl vfs.VFSImplementation
|
|
}
|
|
|
|
fn (e &MountEntry) get_metadata() vfs.Metadata {
|
|
return e.metadata
|
|
}
|
|
|
|
fn (e &MountEntry) get_path() string {
|
|
return '/${e.metadata.name.trim_left('/')}'
|
|
}
|
|
|
|
// is_dir returns true if the entry is a directory
|
|
pub fn (self &MountEntry) is_dir() bool {
|
|
return self.metadata.file_type == .directory
|
|
}
|
|
|
|
// is_file returns true if the entry is a file
|
|
pub fn (self &MountEntry) is_file() bool {
|
|
return self.metadata.file_type == .file
|
|
}
|
|
|
|
// is_symlink returns true if the entry is a symlink
|
|
pub fn (self &MountEntry) is_symlink() bool {
|
|
return self.metadata.file_type == .symlink
|
|
}
|
|
|
|
// NestedEntry wraps an FSEntry from a sub VFS and prefixes its path
|
|
pub struct NestedEntry {
|
|
pub mut:
|
|
original vfs.FSEntry
|
|
prefix string
|
|
}
|
|
|
|
fn (e &NestedEntry) get_metadata() vfs.Metadata {
|
|
return e.original.get_metadata()
|
|
}
|
|
|
|
fn (e &NestedEntry) get_path() string {
|
|
original_path := e.original.get_path()
|
|
if original_path == '/' {
|
|
return e.prefix
|
|
}
|
|
return e.prefix + '/${original_path.trim_string_left('/')}'
|
|
}
|
|
|
|
// is_dir returns true if the entry is a directory
|
|
pub fn (self &NestedEntry) is_dir() bool {
|
|
return self.original.is_dir()
|
|
}
|
|
|
|
// is_file returns true if the entry is a file
|
|
pub fn (self &NestedEntry) is_file() bool {
|
|
return self.original.is_file()
|
|
}
|
|
|
|
// is_symlink returns true if the entry is a symlink
|
|
pub fn (self &NestedEntry) is_symlink() bool {
|
|
return self.original.is_symlink()
|
|
}
|
|
|
|
// // ResourceForkEntry represents a macOS resource fork file (._*)
|
|
// pub struct ResourceForkEntry {
|
|
// pub mut:
|
|
// metadata vfs.Metadata
|
|
// path string
|
|
// }
|
|
|
|
// fn (e &ResourceForkEntry) get_metadata() vfs.Metadata {
|
|
// return e.metadata
|
|
// }
|
|
|
|
// fn (e &ResourceForkEntry) get_path() string {
|
|
// return e.path
|
|
// }
|
|
|
|
// // is_dir returns true if the entry is a directory
|
|
// pub fn (self &ResourceForkEntry) is_dir() bool {
|
|
// return false
|
|
// }
|
|
|
|
// // is_file returns true if the entry is a file
|
|
// pub fn (self &ResourceForkEntry) is_file() bool {
|
|
// return true
|
|
// }
|
|
|
|
// // is_symlink returns true if the entry is a symlink
|
|
// pub fn (self &ResourceForkEntry) is_symlink() bool {
|
|
// return false
|
|
// }
|