Files
herolib/lib/vfs/vfsnested/vfsnested.v
Mahmoud Emad c0b57e2a01 feat: Add file move operation
- Added `move` operation to `Directory` to rename files and
  directories within the same directory.  This improves
  file management capabilities.
- Updated `VFS` interface to include `move` function with
  FSEntry return type for consistency. This allows for
  retrieving metadata of the moved file/directory.
- Implemented `move` operation for `LocalVFS`, `OurDBVFS`, and
  `NestedVFS`.  This provides consistent file move
  functionality across different VFS implementations.
- Added tests for the new move functionality in
  `vfsourdb_test.v`. This ensures the correct behavior of the
  new feature.
2025-02-23 22:26:05 +02:00

249 lines
6.6 KiB
V

module vfsnested
import freeflowuniverse.herolib.vfs.vfscore
// NestedVFS represents a VFS that can contain multiple nested VFS instances
pub struct NestedVFS {
mut:
vfs_map map[string]vfscore.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]vfscore.VFSImplementation{}
}
}
// add_vfs adds a new VFS implementation at the specified path prefix
pub fn (mut self NestedVFS) add_vfs(prefix string, impl vfscore.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) !(vfscore.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() !vfscore.FSEntry {
// Return a special root entry that represents the nested VFS
return &RootEntry{
metadata: vfscore.Metadata{
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) !vfscore.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) !vfscore.FSEntry {
mut impl, rel_path := self.find_vfs(path)!
return impl.dir_create(rel_path)
}
pub fn (mut self NestedVFS) dir_list(path string) ![]vfscore.FSEntry {
// Special case for root directory
if path == '' || path == '/' {
mut entries := []vfscore.FSEntry{}
for prefix, mut impl in self.vfs_map {
root := impl.root_get() or { continue }
entries << &MountEntry{
metadata: vfscore.Metadata{
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) !vfscore.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) ! {
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')
}
return old_impl.rename(old_rel_path, new_rel_path)
}
pub fn (mut self NestedVFS) copy(src_path string, dst_path string) ! {
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)!
dst_impl.file_create(dst_rel_path)!
return dst_impl.file_write(dst_rel_path, data)
}
pub fn (mut self NestedVFS) move(src_path string, dst_path string) !vfscore.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) !vfscore.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 vfscore.Metadata
}
fn (e &RootEntry) get_metadata() vfscore.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 vfscore.Metadata
impl vfscore.VFSImplementation
}
fn (e &MountEntry) get_metadata() vfscore.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
}