isolate vfs's and improve documentation
This commit is contained in:
48
lib/vfs/vfs_nested/README.md
Normal file
48
lib/vfs/vfs_nested/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Nested Filesystem Implementation (vfs_nested)
|
||||
|
||||
A virtual filesystem implementation that allows mounting multiple VFS implementations at different path prefixes, creating a unified filesystem view.
|
||||
|
||||
## Features
|
||||
|
||||
- Mount multiple VFS implementations
|
||||
- Path-based routing to appropriate implementations
|
||||
- Transparent operation across mounted filesystems
|
||||
- Hierarchical organization
|
||||
- Cross-implementation file operations
|
||||
- Virtual root directory showing mount points
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Structure
|
||||
```
|
||||
vfs_nested/
|
||||
├── vfsnested.v # Core implementation
|
||||
└── nested_test.v # Implementation tests
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
- `NestedVFS`: Main implementation struct that manages mounted filesystems
|
||||
- `RootEntry`: Special entry type representing the root directory
|
||||
- `MountEntry`: Special entry type representing mounted filesystem points
|
||||
|
||||
## Usage
|
||||
|
||||
```v
|
||||
import vfs
|
||||
import vfs_nested
|
||||
|
||||
fn main() ! {
|
||||
mut nested := vfs_nested.new()
|
||||
mut local_fs := vfs.new_vfs('local', '/tmp/local')!
|
||||
nested.add_vfs('/local', local_fs)!
|
||||
nested.file_create('/local/test.txt')!
|
||||
}
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Cannot rename/move files across different implementations
|
||||
- Symlinks must be contained within a single implementation
|
||||
- No atomic operations across implementations
|
||||
- Mount points are fixed after creation
|
||||
85
lib/vfs/vfs_nested/nested_test.v
Normal file
85
lib/vfs/vfs_nested/nested_test.v
Normal file
@@ -0,0 +1,85 @@
|
||||
module vfsnested
|
||||
|
||||
import freeflowuniverse.herolib.vfs.vfs_local
|
||||
import os
|
||||
|
||||
fn test_nested() ! {
|
||||
println('Testing Nested VFS...')
|
||||
|
||||
// Create root directories for test VFS instances
|
||||
os.mkdir_all('/tmp/test_nested_vfs/vfs1') or { panic(err) }
|
||||
os.mkdir_all('/tmp/test_nested_vfs/vfs2') or { panic(err) }
|
||||
os.mkdir_all('/tmp/test_nested_vfs/vfs3') or { panic(err) }
|
||||
|
||||
// Create VFS instances
|
||||
mut vfs1 := vfs_local.new_local_vfs('/tmp/test_nested_vfs/vfs1') or { panic(err) }
|
||||
mut vfs2 := vfs_local.new_local_vfs('/tmp/test_nested_vfs/vfs2') or { panic(err) }
|
||||
mut vfs3 := vfs_local.new_local_vfs('/tmp/test_nested_vfs/vfs3') or { panic(err) }
|
||||
|
||||
// Create nested VFS
|
||||
mut nested_vfs := new()
|
||||
|
||||
// Add VFS instances at different paths
|
||||
nested_vfs.add_vfs('/data', vfs1) or { panic(err) }
|
||||
nested_vfs.add_vfs('/config', vfs2) or { panic(err) }
|
||||
nested_vfs.add_vfs('/data/backup', vfs3) or { panic(err) } // Nested under /data
|
||||
|
||||
println('\nTesting file operations...')
|
||||
|
||||
// Create and write to files in different VFS instances
|
||||
nested_vfs.file_create('/data/test.txt') or { panic(err) }
|
||||
nested_vfs.file_write('/data/test.txt', 'Hello from VFS1'.bytes()) or { panic(err) }
|
||||
|
||||
nested_vfs.file_create('/config/settings.txt') or { panic(err) }
|
||||
nested_vfs.file_write('/config/settings.txt', 'Hello from VFS2'.bytes()) or { panic(err) }
|
||||
|
||||
nested_vfs.file_create('/data/backup/archive.txt') or { panic(err) }
|
||||
nested_vfs.file_write('/data/backup/archive.txt', 'Hello from VFS3'.bytes()) or { panic(err) }
|
||||
|
||||
// Read and verify file contents
|
||||
data1 := nested_vfs.file_read('/data/test.txt') or { panic(err) }
|
||||
println('Content from /data/test.txt: ${data1.bytestr()}')
|
||||
|
||||
data2 := nested_vfs.file_read('/config/settings.txt') or { panic(err) }
|
||||
println('Content from /config/settings.txt: ${data2.bytestr()}')
|
||||
|
||||
data3 := nested_vfs.file_read('/data/backup/archive.txt') or { panic(err) }
|
||||
println('Content from /data/backup/archive.txt: ${data3.bytestr()}')
|
||||
|
||||
println('\nTesting directory operations...')
|
||||
|
||||
// List root directory
|
||||
println('Root directory contents:')
|
||||
root_entries := nested_vfs.dir_list('/') or { panic(err) }
|
||||
for entry in root_entries {
|
||||
meta := entry.get_metadata()
|
||||
println('- ${meta.name} (${meta.file_type})')
|
||||
}
|
||||
|
||||
// Create and list directories
|
||||
nested_vfs.dir_create('/data/subdir') or { panic(err) }
|
||||
nested_vfs.file_create('/data/subdir/file.txt') or { panic(err) }
|
||||
nested_vfs.file_write('/data/subdir/file.txt', 'Nested file content'.bytes()) or { panic(err) }
|
||||
|
||||
println('\nListing /data directory:')
|
||||
data_entries := nested_vfs.dir_list('/data') or { panic(err) }
|
||||
for entry in data_entries {
|
||||
meta := entry.get_metadata()
|
||||
println('- ${meta.name} (${meta.file_type})')
|
||||
}
|
||||
|
||||
println('\nTesting cross-VFS operations...')
|
||||
|
||||
// Copy file between different VFS instances
|
||||
nested_vfs.copy('/data/test.txt', '/config/test_copy.txt') or { panic(err) }
|
||||
copy_data := nested_vfs.file_read('/config/test_copy.txt') or { panic(err) }
|
||||
println('Copied file content: ${copy_data.bytestr()}')
|
||||
|
||||
println('\nCleanup...')
|
||||
|
||||
// Cleanup
|
||||
nested_vfs.destroy() or { panic(err) }
|
||||
os.rmdir_all('/tmp/test_nested_vfs') or { panic(err) }
|
||||
|
||||
println('Test completed successfully!')
|
||||
}
|
||||
252
lib/vfs/vfs_nested/vfsnested.v
Normal file
252
lib/vfs/vfs_nested/vfsnested.v
Normal file
@@ -0,0 +1,252 @@
|
||||
module vfsnested
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
|
||||
// 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..]
|
||||
if relative_path.starts_with('/') {
|
||||
return self.vfs_map[prefix], relative_path[1..]
|
||||
}
|
||||
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: ''
|
||||
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)!
|
||||
return impl.file_create(rel_path)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) file_read(path string) ![]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)!
|
||||
return impl.dir_create(rel_path)
|
||||
}
|
||||
|
||||
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
|
||||
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)!
|
||||
return impl.dir_list(rel_path)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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()
|
||||
}
|
||||
mut impl, rel_path := self.find_vfs(path)!
|
||||
return impl.get(rel_path)
|
||||
}
|
||||
|
||||
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)!
|
||||
return renamed_file
|
||||
}
|
||||
|
||||
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 {
|
||||
return src_impl.copy(src_rel_path, dst_rel_path)
|
||||
}
|
||||
|
||||
// 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)!
|
||||
return new_file
|
||||
}
|
||||
|
||||
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)!
|
||||
return src_impl.move(src_rel_path, dst_rel_path)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) link_create(target_path string, link_path string) !vfs.FSEntry {
|
||||
mut impl, rel_path := self.find_vfs(link_path)!
|
||||
return impl.link_create(target_path, rel_path)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user