diff --git a/lib/vfs/vfscore/README.md b/lib/vfs/README.md similarity index 100% rename from lib/vfs/vfscore/README.md rename to lib/vfs/README.md diff --git a/lib/vfs/vfscore/interface.v b/lib/vfs/interface.v similarity index 58% rename from lib/vfs/vfscore/interface.v rename to lib/vfs/interface.v index 4e7aea0e..6e5522bf 100644 --- a/lib/vfs/vfscore/interface.v +++ b/lib/vfs/interface.v @@ -1,48 +1,7 @@ -module vfscore +module vfs import time -// FileType represents the type of a filesystem entry -pub enum FileType { - file - directory - symlink -} - -// Metadata represents the common metadata for both files and directories -pub struct Metadata { -pub mut: - id u32 // name of file or directory - name string // name of file or directory - file_type FileType - size u64 - created_at i64 // unix epoch timestamp - modified_at i64 // unix epoch timestamp - accessed_at i64 // unix epoch timestamp -} - -// Get time.Time objects from epochs -pub fn (m Metadata) created_time() time.Time { - return time.unix(m.created_at) -} - -pub fn (m Metadata) modified_time() time.Time { - return time.unix(m.modified_at) -} - -pub fn (m Metadata) accessed_time() time.Time { - return time.unix(m.accessed_at) -} - -// FSEntry represents a filesystem entry (file, directory, or symlink) -pub interface FSEntry { - get_metadata() Metadata - get_path() string - is_dir() bool - is_file() bool - is_symlink() bool -} - // VFSImplementation defines the interface that all vfscore implementations must follow pub interface VFSImplementation { mut: @@ -60,6 +19,11 @@ mut: dir_list(path string) ![]FSEntry dir_delete(path string) ! + // Symlink operations + link_create(target_path string, link_path string) !FSEntry + link_read(path string) !string + link_delete(path string) ! + // Common operations exists(path string) bool get(path string) !FSEntry @@ -68,11 +32,15 @@ mut: move(src_path string, dst_path string) !FSEntry delete(path string) ! - // Symlink operations - link_create(target_path string, link_path string) !FSEntry - link_read(path string) !string - link_delete(path string) ! - // Cleanup operation destroy() ! -} \ No newline at end of file +} + +// FSEntry represents a filesystem entry (file, directory, or symlink) +pub interface FSEntry { + get_metadata() Metadata + get_path() string + is_dir() bool + is_file() bool + is_symlink() bool +} diff --git a/lib/vfs/ourdb_fs/common.v b/lib/vfs/metadata.v similarity index 57% rename from lib/vfs/ourdb_fs/common.v rename to lib/vfs/metadata.v index dac024ea..4fb8205f 100644 --- a/lib/vfs/ourdb_fs/common.v +++ b/lib/vfs/metadata.v @@ -1,19 +1,12 @@ -module ourdb_fs +module vfs import time -// FileType represents the type of a filesystem entry -pub enum FileType { - file - directory - symlink -} - // Metadata represents the common metadata for both files and directories pub struct Metadata { pub mut: - id u32 // unique identifier used as key in DB - name string // name of file or directory + id u32 @[required] // unique identifier used as key in DB + name string @[required] // name of file or directory file_type FileType size u64 created_at i64 // unix epoch timestamp @@ -24,6 +17,24 @@ pub mut: group string } +// FileType represents the type of a filesystem entry +pub enum FileType { + file + directory + symlink +} + +// mkdir creates a new directory with default permissions +pub fn new_metadata(metadata Metadata) Metadata { + current_time := time.now().unix() + return Metadata{ + ...metadata + created_at: current_time + modified_at: current_time + accessed_at: current_time + } +} + // Get time.Time objects from epochs pub fn (m Metadata) created_time() time.Time { return time.unix(m.created_at) @@ -36,3 +47,11 @@ pub fn (m Metadata) modified_time() time.Time { pub fn (m Metadata) accessed_time() time.Time { return time.unix(m.accessed_at) } + +pub fn (mut m Metadata) modified() { + m.modified_at = time.now().unix() +} + +pub fn (mut m Metadata) accessed() { + m.accessed_at = time.now().unix() +} diff --git a/lib/vfs/ourdb_fs/data.v b/lib/vfs/ourdb_fs/data.v deleted file mode 100644 index 2de227ea..00000000 --- a/lib/vfs/ourdb_fs/data.v +++ /dev/null @@ -1,10 +0,0 @@ -module ourdb_fs - -// DataBlock represents a block of file data -pub struct DataBlock { -pub mut: - id u32 // Block ID - data []u8 // Actual data content - size u32 // Size of data in bytes - next u32 // ID of next block (0 if last block) -} diff --git a/lib/vfs/ourdb_fs/directory.v b/lib/vfs/ourdb_fs/directory.v deleted file mode 100644 index b78bf37b..00000000 --- a/lib/vfs/ourdb_fs/directory.v +++ /dev/null @@ -1,530 +0,0 @@ -module ourdb_fs - -import time - -// FSEntry represents any type of filesystem entry -pub type FSEntry = Directory | File | Symlink - -// Directory represents a directory in the virtual filesystem -pub struct Directory { -pub mut: - metadata Metadata // Metadata from models_common.v - children []u32 // List of child entry IDs (instead of actual entries) - parent_id u32 // ID of parent directory (0 for root) - myvfs &OurDBFS @[str: skip] -} - -pub fn (mut self Directory) save() ! { - self.myvfs.save_entry(self)! -} - -// write creates a new file or writes to an existing file -pub fn (mut dir Directory) write(name string, content string) !&File { - mut file := &File{ - myvfs: dir.myvfs - } - mut is_new := true - - // Check if file exists - for child_id in dir.children { - mut entry := dir.myvfs.load_entry(child_id)! - if entry.metadata.name == name { - if mut entry is File { - mut d := entry - file = &d - is_new = false - break - } else { - return error('${name} exists but is not a file') - } - } - } - - if is_new { - // Create new file - current_time := time.now().unix() - file = &File{ - metadata: Metadata{ - id: dir.myvfs.get_next_id() - name: name - file_type: .file - size: u64(content.len) - created_at: current_time - modified_at: current_time - accessed_at: current_time - mode: 0o644 - owner: 'user' - group: 'user' - } - data: content - parent_id: dir.metadata.id - myvfs: dir.myvfs - } - - // Save new file to DB - dir.myvfs.save_entry(file)! - - // Update children list - dir.children << file.metadata.id - dir.myvfs.save_entry(dir)! - } else { - // Update existing file - file.write(content)! - } - - return file -} - -// read reads content from a file -pub fn (mut dir Directory) read(name string) !string { - // Find file - for child_id in dir.children { - if mut entry := dir.myvfs.load_entry(child_id) { - if entry.metadata.name == name { - if mut entry is File { - return entry.read() - } else { - return error('${name} is not a file') - } - } - } - } - return error('File ${name} not found') -} - -// str returns a formatted string of directory contents (non-recursive) -pub fn (mut dir Directory) str() string { - mut result := '${dir.metadata.name}/\n' - - for child_id in dir.children { - if entry := dir.myvfs.load_entry(child_id) { - if entry is Directory { - result += ' 📁 ${entry.metadata.name}/\n' - } else if entry is File { - result += ' 📄 ${entry.metadata.name}\n' - } else if entry is Symlink { - result += ' 🔗 ${entry.metadata.name} -> ${entry.target}\n' - } - } - } - return result -} - -// printall prints the directory structure recursively -pub fn (mut dir Directory) printall(indent string) !string { - mut result := '${indent}📁 ${dir.metadata.name}/\n' - - for child_id in dir.children { - mut entry := dir.myvfs.load_entry(child_id)! - if mut entry is Directory { - result += entry.printall(indent + ' ')! - } else if entry is File { - result += '${indent} 📄 ${entry.metadata.name}\n' - } else if mut entry is Symlink { - result += '${indent} 🔗 ${entry.metadata.name} -> ${entry.target}\n' - } - } - return result -} - -// mkdir creates a new directory with default permissions -pub fn (mut dir Directory) mkdir(name string) !&Directory { - // Check if directory already exists - for child_id in dir.children { - if entry := dir.myvfs.load_entry(child_id) { - if entry.metadata.name == name { - return error('Directory ${name} already exists') - } - } - } - - current_time := time.now().unix() - mut new_dir := Directory{ - metadata: Metadata{ - id: dir.myvfs.get_next_id() - name: name - file_type: .directory - created_at: current_time - modified_at: current_time - accessed_at: current_time - mode: 0o755 // default directory permissions - owner: 'user' // TODO: get from system - group: 'user' // TODO: get from system - } - children: []u32{} - parent_id: dir.metadata.id - myvfs: dir.myvfs - } - - // Save new directory to DB - dir.myvfs.save_entry(new_dir)! - - // Update children list - dir.children << new_dir.metadata.id - dir.myvfs.save_entry(dir)! - - return &new_dir -} - -// touch creates a new empty file with default permissions -pub fn (mut dir Directory) touch(name string) !&File { - // Check if file already exists - for child_id in dir.children { - if entry := dir.myvfs.load_entry(child_id) { - if entry.metadata.name == name { - return error('File ${name} already exists') - } - } - } - - current_time := time.now().unix() - mut new_file := File{ - metadata: Metadata{ - id: dir.myvfs.get_next_id() - name: name - file_type: .file - size: 0 - created_at: current_time - modified_at: current_time - accessed_at: current_time - mode: 0o644 // default file permissions - owner: 'user' // TODO: get from system - group: 'user' // TODO: get from system - } - data: '' // Initialize with empty content - parent_id: dir.metadata.id - myvfs: dir.myvfs - } - - // Save new file to DB - dir.myvfs.save_entry(new_file)! - - // Update children list - dir.children << new_file.metadata.id - dir.myvfs.save_entry(dir)! - - return &new_file -} - -// rm removes a file or directory by name -pub fn (mut dir Directory) rm(name string) ! { - mut found := false - mut found_id := u32(0) - mut found_idx := 0 - - for i, child_id in dir.children { - if entry := dir.myvfs.load_entry(child_id) { - if entry.metadata.name == name { - found = true - found_id = child_id - found_idx = i - if entry is Directory { - if entry.children.len > 0 { - return error('Directory not empty') - } - } - break - } - } - } - - if !found { - return error('${name} not found') - } - - // Delete entry from DB - dir.myvfs.delete_entry(found_id)! - - // Update children list - dir.children.delete(found_idx) - dir.myvfs.save_entry(dir)! -} - -pub struct MoveDirArgs { -pub mut: - src_entry_name string @[required] // source entry name - dst_entry_name string @[required] // destination entry name - dst_parent_dir &Directory @[required] // destination directory -} - -pub fn (dir_ Directory) move(args_ MoveDirArgs) !&Directory { - mut dir := dir_ - mut args := args_ - mut found := false - - for child_id in dir.children { - if mut entry := dir.myvfs.load_entry(child_id) { - if entry.metadata.name == args.src_entry_name { - if entry is File { - return error('${args.src_entry_name} is a file') - } - - if entry is Symlink { - return error('${args.src_entry_name} is a symlink') - } - - found = true - mut entry_ := entry as Directory - entry_.metadata.name = args.dst_entry_name - entry_.metadata.modified_at = time.now().unix() - entry_.parent_id = args.dst_parent_dir.metadata.id - - // Remove from old parent's children - dir.children = dir.children.filter(it != child_id) - dir.save()! - - // Recursively update all child paths in moved directory - move_children_recursive(mut entry_)! - - // Ensure no duplicate entries in dst_parent_dir - if entry_.metadata.id !in args.dst_parent_dir.children { - args.dst_parent_dir.children << entry_.metadata.id - } - - args.dst_parent_dir.myvfs.save_entry(entry_)! - args.dst_parent_dir.save()! - - return &entry_ - } - } - } - - if !found { - return error('${args.src_entry_name} not found') - } - - return error('Unexpected move failure') -} - -// Recursive function to update parent_id for all children -fn move_children_recursive(mut dir Directory) ! { - for child in dir.children { - if mut child_entry := dir.myvfs.load_entry(child) { - child_entry.parent_id = dir.metadata.id - - if child_entry is Directory { - // Recursively move subdirectories - mut child_entry_ := child_entry as Directory - move_children_recursive(mut child_entry_)! - } - - dir.myvfs.save_entry(child_entry)! - } - } -} - -pub struct CopyDirArgs { -pub mut: - src_entry_name string @[required] // source entry name - dst_entry_name string @[required] // destination entry name - dst_parent_dir &Directory @[required] // destination directory -} - -pub fn (mut dir Directory) copy(args_ CopyDirArgs) !&Directory { - mut found := false - mut args := args_ - - for child_id in dir.children { - if mut entry := dir.myvfs.load_entry(child_id) { - if entry.metadata.name == args.src_entry_name { - if entry is File { - return error('${args.src_entry_name} is a file, not a directory') - } - - if entry is Symlink { - return error('${args.src_entry_name} is a symlink, not a directory') - } - - found = true - mut src_dir := entry as Directory - - // Create a new directory with copied metadata - current_time := time.now().unix() - mut new_dir := Directory{ - metadata: Metadata{ - id: args.dst_parent_dir.myvfs.get_next_id() - name: args.dst_entry_name - file_type: .directory - created_at: current_time - modified_at: current_time - accessed_at: current_time - mode: src_dir.metadata.mode - owner: src_dir.metadata.owner - group: src_dir.metadata.group - } - children: []u32{} - parent_id: args.dst_parent_dir.metadata.id - myvfs: args.dst_parent_dir.myvfs - } - - // Recursively copy children - copy_children_recursive(mut src_dir, mut new_dir)! - - // Save new directory - args.dst_parent_dir.myvfs.save_entry(new_dir)! - args.dst_parent_dir.children << new_dir.metadata.id - args.dst_parent_dir.save()! - - return &new_dir - } - } - } - - if !found { - return error('${args.src_entry_name} not found') - } - - return error('Unexpected copy failure') -} - -fn copy_children_recursive(mut src_dir Directory, mut dst_dir Directory) ! { - for child_id in src_dir.children { - if mut entry := src_dir.myvfs.load_entry(child_id) { - current_time := time.now().unix() - - match entry { - Directory { - mut entry_ := entry as Directory - mut new_subdir := Directory{ - metadata: Metadata{ - id: dst_dir.myvfs.get_next_id() - name: entry_.metadata.name - file_type: .directory - created_at: current_time - modified_at: current_time - accessed_at: current_time - mode: entry_.metadata.mode - owner: entry_.metadata.owner - group: entry_.metadata.group - } - children: []u32{} - parent_id: dst_dir.metadata.id - myvfs: dst_dir.myvfs - } - - copy_children_recursive(mut entry_, mut new_subdir)! - dst_dir.myvfs.save_entry(new_subdir)! - dst_dir.children << new_subdir.metadata.id - } - File { - mut entry_ := entry as File - mut new_file := File{ - metadata: Metadata{ - id: dst_dir.myvfs.get_next_id() - name: entry_.metadata.name - file_type: .file - size: entry_.metadata.size - created_at: current_time - modified_at: current_time - accessed_at: current_time - mode: entry_.metadata.mode - owner: entry_.metadata.owner - group: entry_.metadata.group - } - data: entry_.data - parent_id: dst_dir.metadata.id - myvfs: dst_dir.myvfs - } - dst_dir.myvfs.save_entry(new_file)! - dst_dir.children << new_file.metadata.id - } - Symlink { - mut entry_ := entry as Symlink - mut new_symlink := Symlink{ - metadata: Metadata{ - id: dst_dir.myvfs.get_next_id() - name: entry_.metadata.name - file_type: .symlink - created_at: current_time - modified_at: current_time - accessed_at: current_time - mode: entry_.metadata.mode - owner: entry_.metadata.owner - group: entry_.metadata.group - } - target: entry_.target - parent_id: dst_dir.metadata.id - myvfs: dst_dir.myvfs - } - dst_dir.myvfs.save_entry(new_symlink)! - dst_dir.children << new_symlink.metadata.id - } - } - } - } - - dst_dir.save()! -} - -pub fn (dir Directory) rename(src_name string, dst_name string) !&Directory { - mut found := false - mut dir_ := dir - - for child_id in dir.children { - if mut entry := dir_.myvfs.load_entry(child_id) { - if entry.metadata.name == src_name { - found = true - entry.metadata.name = dst_name - entry.metadata.modified_at = time.now().unix() - dir_.myvfs.save_entry(entry)! - get_dir := entry as Directory - return &get_dir - } - } - } - - if !found { - return error('${src_name} not found') - } - - return &dir_ -} - -// get_children returns all immediate children as FSEntry objects -pub fn (mut dir Directory) children(recursive bool) ![]FSEntry { - mut entries := []FSEntry{} - for child_id in dir.children { - entry := dir.myvfs.load_entry(child_id)! - entries << entry - if recursive { - if entry is Directory { - mut d := entry - entries << d.children(true)! - } - } - } - - return entries -} - -pub fn (mut dir Directory) delete() ! { - // Delete all children first - for child_id in dir.children { - dir.myvfs.delete_entry(child_id) or {} - } - - // Clear children list - dir.children.clear() - - // Save the updated directory - dir.myvfs.save_entry(dir) or { return error('Failed to save directory: ${err}') } -} - -// add_symlink adds an existing symlink to this directory -pub fn (mut dir Directory) add_symlink(mut symlink Symlink) ! { - // Check if name already exists - for child_id in dir.children { - if entry := dir.myvfs.load_entry(child_id) { - if entry.metadata.name == symlink.metadata.name { - return error('Entry with name ${symlink.metadata.name} already exists') - } - } - } - - // Save symlink to DB - dir.myvfs.save_entry(symlink)! - - // Add to children - dir.children << symlink.metadata.id - dir.myvfs.save_entry(dir)! -} diff --git a/lib/vfs/ourdb_fs/factory.v b/lib/vfs/ourdb_fs/factory.v deleted file mode 100644 index 14bbff76..00000000 --- a/lib/vfs/ourdb_fs/factory.v +++ /dev/null @@ -1,43 +0,0 @@ -module ourdb_fs - -import freeflowuniverse.herolib.data.ourdb -import freeflowuniverse.herolib.core.pathlib - -// Factory method for creating a new OurDBFS instance -@[params] -pub struct VFSParams { -pub: - data_dir string // Directory to store OurDBFS data - metadata_dir string // Directory to store OurDBFS metadata - incremental_mode bool // Whether to enable incremental mode -} - -// Factory method for creating a new OurDBFS instance -pub fn new(params VFSParams) !&OurDBFS { - pathlib.get_dir(path: params.data_dir, create: true) or { - return error('Failed to create data directory: ${err}') - } - pathlib.get_dir(path: params.metadata_dir, create: true) or { - return error('Failed to create metadata directory: ${err}') - } - - mut db_meta := ourdb.new( - path: '${params.metadata_dir}/ourdb_fs.db_meta' - incremental_mode: params.incremental_mode - )! - mut db_data := ourdb.new( - path: '${params.data_dir}/vfs_metadata.db_meta' - incremental_mode: params.incremental_mode - )! - - mut fs := &OurDBFS{ - root_id: 1 - block_size: 1024 * 4 - data_dir: params.data_dir - metadata_dir: params.metadata_dir - db_meta: &db_meta - db_data: &db_data - } - - return fs -} diff --git a/lib/vfs/ourdb_fs/file.v b/lib/vfs/ourdb_fs/file.v deleted file mode 100644 index 803d13e2..00000000 --- a/lib/vfs/ourdb_fs/file.v +++ /dev/null @@ -1,57 +0,0 @@ -module ourdb_fs - -import time - -// File represents a file in the virtual filesystem -pub struct File { -pub mut: - metadata Metadata // Metadata from models_common.v - data string // File content stored in DB - parent_id u32 // ID of parent directory - myvfs &OurDBFS @[str: skip] -} - -pub fn (mut f File) save() ! { - f.myvfs.save_entry(f)! -} - -// write updates the file's content -pub fn (mut f File) write(content string) ! { - f.data = content - f.metadata.size = u64(content.len) - f.metadata.modified_at = time.now().unix() - - // Save updated file to DB - f.save()! -} - -// Move the file to a new location -pub fn (mut f File) move(mut new_parent Directory) !File { - f.parent_id = new_parent.metadata.id - f.save()! - return f -} - -// Copy the file to a new location -pub fn (mut f File) copy(mut new_parent Directory) !File { - mut new_file := File{ - metadata: f.metadata - data: f.data - parent_id: new_parent.metadata.id - myvfs: f.myvfs - } - new_file.save()! - return new_file -} - -// Rename the file -pub fn (mut f File) rename(name string) !File { - f.metadata.name = name - f.save()! - return f -} - -// read returns the file's content -pub fn (mut f File) read() !string { - return f.data -} diff --git a/lib/vfs/ourdb_fs/readme.md b/lib/vfs/ourdb_fs/readme.md deleted file mode 100644 index 9b17e681..00000000 --- a/lib/vfs/ourdb_fs/readme.md +++ /dev/null @@ -1,159 +0,0 @@ -# a OurDBFS: filesystem interface on top of ourbd - -The OurDBFS manages files and directories using unique identifiers (u32) as keys and binary data ([]u8) as values. - - -## Architecture - -### Storage Backend (the ourdb) - -- Uses a key-value store where keys are u32 and values are []u8 (bytes) -- Stores both metadata and file data in the same database -- Example usage of underlying database: - -```v -import crystallib.data.ourdb - -mut db_meta := ourdb.new(path:"/tmp/mydb")! - -// Store data -db_meta.set(1, 'Hello World'.bytes())! - -// Retrieve data -data := db_meta.get(1)! // Returns []u8 - -// Delete data -db_meta.delete(1)! -``` - -### Core Components - -#### 1. Common Metadata (common.v) - -All filesystem entries (files and directories) share common metadata: -```v -pub struct Metadata { - id u32 // unique identifier used as key in DB - name string // name of file or directory - file_type FileType - size u64 - created_at i64 // unix epoch timestamp - modified_at i64 // unix epoch timestamp - accessed_at i64 // unix epoch timestamp - mode u32 // file permissions - owner string - group string -} -``` - -#### 2. Files (file.v) -Files are represented as: -```v -pub struct File { - metadata Metadata // Common metadata - parent_id u32 // ID of parent directory - data_blocks []u32 // List of block IDs containing file data -} -``` - -#### 3. Directories (directory.v) -Directories are represented as: -```v -pub struct Directory { - metadata Metadata // Common metadata - parent_id u32 // ID of parent directory - children []u32 // List of child IDs (files and directories) -} -``` - -#### 4. Data Storage (data.v) -File data is stored in blocks: -```v -pub struct DataBlock { - id u32 // Block ID - data []u8 // Actual data content - size u32 // Size of data in bytes - next u32 // ID of next block (0 if last block) -} -``` - -### Features - -1. **Hierarchical Structure** - - Files and directories are organized in a tree structure - - Each entry maintains a reference to its parent directory - - Directories maintain a list of child entries - -2. **Metadata Management** - - Comprehensive metadata tracking including: - - Creation, modification, and access timestamps - - File permissions - - Owner and group information - - File size and type - -3. **File Operations** - - File creation and deletion - - Data block management for file content - - Future support for read/write operations - -4. **Directory Operations** - - Directory creation and deletion - - Listing directory contents (recursive and non-recursive) - - Child management - -### Implementation Details - -1. **File Types** -```v -pub enum FileType { - file - directory - symlink -} -``` - -2. **Data Block Management** - - File data is split into blocks - - Blocks are linked using the 'next' pointer - - Each block has a unique ID for retrieval - -3. **Directory Traversal** - - Supports both recursive and non-recursive listing - - Uses child IDs for efficient navigation - -### TODO Items - - -> TODO: what is implemented and what not? - -1. Directory Implementation - - Implement recursive listing functionality - - Proper cleanup of children during deletion - - ID generation system - -2. File Implementation - - Proper cleanup of data blocks - - Data block management system - - Read/Write operations - -3. General Improvements - - Transaction support - - Error handling - - Performance optimizations - - Concurrency support - - - - - - -use @encoder dir to see how to encode/decode - -make an efficient encoder for Directory -add a id u32 to directory this will be the key of the keyvalue stor used - -try to use as few as possible bytes when doing the encoding - -the first byte is a version nr, so we know if we change the encoding format we can still decode - -we will only store directories \ No newline at end of file diff --git a/lib/vfs/ourdb_fs/symlink.v b/lib/vfs/ourdb_fs/symlink.v deleted file mode 100644 index 292cdd53..00000000 --- a/lib/vfs/ourdb_fs/symlink.v +++ /dev/null @@ -1,35 +0,0 @@ -module ourdb_fs - -import time - -// Symlink represents a symbolic link in the virtual filesystem -pub struct Symlink { -pub mut: - metadata Metadata // Metadata from models_common.v - target string // Path that this symlink points to - parent_id u32 // ID of parent directory - myvfs &OurDBFS @[str: skip] -} - -pub fn (mut sl Symlink) save() ! { - sl.myvfs.save_entry(sl)! -} - -// update_target changes the symlink's target path -pub fn (mut sl Symlink) update_target(new_target string) ! { - sl.target = new_target - sl.metadata.modified_at = time.now().unix() - - // Save updated symlink to DB - sl.save() or { return error('Failed to update symlink target: ${err}') } -} - -// get_target returns the current target path -pub fn (mut sl Symlink) get_target() !string { - sl.metadata.accessed_at = time.now().unix() - - // Update access time in DB - sl.save() or { return error('Failed to update symlink access time: ${err}') } - - return sl.target -} diff --git a/lib/vfs/ourdb_fs/vfs.v b/lib/vfs/ourdb_fs/vfs.v deleted file mode 100644 index 0f9379f4..00000000 --- a/lib/vfs/ourdb_fs/vfs.v +++ /dev/null @@ -1,118 +0,0 @@ -module ourdb_fs - -import freeflowuniverse.herolib.data.ourdb -import time - -// OurDBFS represents the virtual filesystem -@[heap] -pub struct OurDBFS { -pub mut: - root_id u32 // ID of root directory - block_size u32 // Size of data blocks in bytes - data_dir string // Directory to store OurDBFS data - metadata_dir string // Directory where we store the metadata - db_data &ourdb.OurDB @[str: skip] // Database instance for persistent storage - db_meta &ourdb.OurDB @[str: skip] // Database instance for metadata storage - last_inserted_id u32 -} - -// Get the next ID, it should be some kind of auto-incrementing ID -pub fn (mut fs OurDBFS) get_next_id() u32 { - fs.last_inserted_id = fs.last_inserted_id + 1 - return fs.last_inserted_id -} - -// get_root returns the root directory -pub fn (mut fs OurDBFS) get_root() !&Directory { - // Try to load root directory from DB if it exists - if data := fs.db_meta.get(fs.root_id) { - mut loaded_root := decode_directory(data) or { - return error('Failed to decode root directory: ${err}') - } - loaded_root.myvfs = &fs - return &loaded_root - } - - // Create and save new root directory - mut myroot := Directory{ - metadata: Metadata{ - id: fs.get_next_id() - file_type: .directory - name: '' - created_at: time.now().unix() - modified_at: time.now().unix() - accessed_at: time.now().unix() - mode: 0o755 // default directory permissions - owner: 'user' // TODO: get from system - group: 'user' // TODO: get from system - } - parent_id: 0 - myvfs: &fs - } - myroot.save()! - fs.root_id = myroot.metadata.id - myroot.save()! - - return &myroot -} - -// load_entry loads an entry from the database by ID and sets up parent references -pub fn (mut fs OurDBFS) load_entry(id u32) !FSEntry { - if data := fs.db_meta.get(id) { - // First byte is version, second byte indicates the type - // TODO: check we dont overflow filetype (u8 in boundaries of filetype) - entry_type := unsafe { FileType(data[1]) } - - match entry_type { - .directory { - mut dir := decode_directory(data) or { - return error('Failed to decode directory: ${err}') - } - dir.myvfs = unsafe { &fs } - return dir - } - .file { - mut file := decode_file(data) or { return error('Failed to decode file: ${err}') } - file.myvfs = unsafe { &fs } - return file - } - .symlink { - mut symlink := decode_symlink(data) or { - return error('Failed to decode symlink: ${err}') - } - symlink.myvfs = unsafe { &fs } - return symlink - } - } - } - return error('Entry not found') -} - -// save_entry saves an entry to the database -pub fn (mut fs OurDBFS) save_entry(entry FSEntry) !u32 { - match entry { - Directory { - encoded := entry.encode() - return fs.db_meta.set(id: entry.metadata.id, data: encoded) or { - return error('Failed to save directory on id:${entry.metadata.id}: ${err}') - } - } - File { - encoded := entry.encode() - return fs.db_meta.set(id: entry.metadata.id, data: encoded) or { - return error('Failed to save file on id:${entry.metadata.id}: ${err}') - } - } - Symlink { - encoded := entry.encode() - return fs.db_meta.set(id: entry.metadata.id, data: encoded) or { - return error('Failed to save symlink on id:${entry.metadata.id}: ${err}') - } - } - } -} - -// delete_entry deletes an entry from the database -pub fn (mut fs OurDBFS) delete_entry(id u32) ! { - fs.db_meta.delete(id) or { return error('Failed to delete entry: ${err}') } -} diff --git a/lib/vfs/ourdb_fs/encoder.v b/lib/vfs/vfs_db/encoder.v similarity index 86% rename from lib/vfs/ourdb_fs/encoder.v rename to lib/vfs/vfs_db/encoder.v index a17c03ec..2569ada6 100644 --- a/lib/vfs/ourdb_fs/encoder.v +++ b/lib/vfs/vfs_db/encoder.v @@ -1,9 +1,10 @@ -module ourdb_fs +module vfs_db import freeflowuniverse.herolib.data.encoder +import freeflowuniverse.herolib.vfs // encode_metadata encodes the common metadata structure -fn encode_metadata(mut e encoder.Encoder, m Metadata) { +fn encode_metadata(mut e encoder.Encoder, m vfs.Metadata) { e.add_u32(m.id) e.add_string(m.name) e.add_u8(u8(m.file_type)) // FileType enum as u8 @@ -17,7 +18,7 @@ fn encode_metadata(mut e encoder.Encoder, m Metadata) { } // decode_metadata decodes the common metadata structure -fn decode_metadata(mut d encoder.Decoder) !Metadata { +fn decode_metadata(mut d encoder.Decoder) !vfs.Metadata { id := d.get_u32()! name := d.get_string()! file_type_byte := d.get_u8()! @@ -29,10 +30,10 @@ fn decode_metadata(mut d encoder.Decoder) !Metadata { owner := d.get_string()! group := d.get_string()! - return Metadata{ + return vfs.Metadata{ id: id name: name - file_type: unsafe { FileType(file_type_byte) } + file_type: unsafe { vfs.FileType(file_type_byte) } size: size created_at: created_at modified_at: modified_at @@ -49,7 +50,7 @@ fn decode_metadata(mut d encoder.Decoder) !Metadata { pub fn (dir Directory) encode() []u8 { mut e := encoder.new() e.add_u8(1) // version byte - e.add_u8(u8(FileType.directory)) // type byte + e.add_u8(u8(vfs.FileType.directory)) // type byte // Encode metadata encode_metadata(mut e, dir.metadata) @@ -75,7 +76,7 @@ pub fn decode_directory(data []u8) !Directory { } type_byte := d.get_u8()! - if type_byte != u8(FileType.directory) { + if type_byte != u8(vfs.FileType.directory) { return error('Invalid type byte for directory') } @@ -97,7 +98,6 @@ pub fn decode_directory(data []u8) !Directory { metadata: metadata parent_id: parent_id children: children - myvfs: unsafe { nil } // Will be set by caller } } @@ -107,7 +107,7 @@ pub fn decode_directory(data []u8) !Directory { pub fn (f File) encode() []u8 { mut e := encoder.new() e.add_u8(1) // version byte - e.add_u8(u8(FileType.file)) // type byte + e.add_u8(u8(vfs.FileType.file)) // type byte // Encode metadata encode_metadata(mut e, f.metadata) @@ -130,7 +130,7 @@ pub fn decode_file(data []u8) !File { } type_byte := d.get_u8()! - if type_byte != u8(FileType.file) { + if type_byte != u8(vfs.FileType.file) { return error('Invalid type byte for file') } @@ -147,7 +147,6 @@ pub fn decode_file(data []u8) !File { metadata: metadata parent_id: parent_id data: data_content - myvfs: unsafe { nil } // Will be set by caller } } @@ -157,7 +156,7 @@ pub fn decode_file(data []u8) !File { pub fn (sl Symlink) encode() []u8 { mut e := encoder.new() e.add_u8(1) // version byte - e.add_u8(u8(FileType.symlink)) // type byte + e.add_u8(u8(vfs.FileType.symlink)) // type byte // Encode metadata encode_metadata(mut e, sl.metadata) @@ -180,7 +179,7 @@ pub fn decode_symlink(data []u8) !Symlink { } type_byte := d.get_u8()! - if type_byte != u8(FileType.symlink) { + if type_byte != u8(vfs.FileType.symlink) { return error('Invalid type byte for symlink') } @@ -197,6 +196,5 @@ pub fn decode_symlink(data []u8) !Symlink { metadata: metadata parent_id: parent_id target: target - myvfs: unsafe { nil } // Will be set by caller } } diff --git a/lib/vfs/ourdb_fs/encoder_test.v b/lib/vfs/vfs_db/encoder_test.v similarity index 95% rename from lib/vfs/ourdb_fs/encoder_test.v rename to lib/vfs/vfs_db/encoder_test.v index a89b1127..73aa3378 100644 --- a/lib/vfs/ourdb_fs/encoder_test.v +++ b/lib/vfs/vfs_db/encoder_test.v @@ -1,14 +1,15 @@ -module ourdb_fs +module vfs_db import os import time +import freeflowuniverse.herolib.vfs fn test_directory_encoder_decoder() ! { println('Testing encoding/decoding directories...') current_time := time.now().unix() dir := Directory{ - metadata: Metadata{ + metadata: vfs.Metadata{ id: u32(current_time) name: 'root' file_type: .directory @@ -21,7 +22,6 @@ fn test_directory_encoder_decoder() ! { } children: [u32(1), u32(2)] parent_id: 0 - myvfs: unsafe { nil } } encoded := dir.encode() @@ -50,7 +50,7 @@ fn test_file_encoder_decoder() ! { current_time := time.now().unix() file := File{ - metadata: Metadata{ + metadata: vfs.Metadata{ id: u32(current_time) name: 'test.txt' file_type: .file @@ -63,7 +63,6 @@ fn test_file_encoder_decoder() ! { } data: 'Hello, world!' parent_id: 0 - myvfs: unsafe { nil } } encoded := file.encode() @@ -90,7 +89,7 @@ fn test_symlink_encoder_decoder() ! { current_time := time.now().unix() symlink := Symlink{ - metadata: Metadata{ + metadata: vfs.Metadata{ id: u32(current_time) name: 'test.txt' file_type: .symlink @@ -103,7 +102,6 @@ fn test_symlink_encoder_decoder() ! { } target: 'test.txt' parent_id: 0 - myvfs: unsafe { nil } } encoded := symlink.encode() diff --git a/lib/vfs/vfs_db/factory.v b/lib/vfs/vfs_db/factory.v new file mode 100644 index 00000000..c54c397a --- /dev/null +++ b/lib/vfs/vfs_db/factory.v @@ -0,0 +1,43 @@ +module vfs_db + +import freeflowuniverse.herolib.data.ourdb +import freeflowuniverse.herolib.core.pathlib + +// Factory method for creating a new DatabaseVFS instance +@[params] +pub struct VFSParams { +pub: + data_dir string // Directory to store DatabaseVFS data + metadata_dir string // Directory to store DatabaseVFS metadata + incremental_mode bool // Whether to enable incremental mode +} + +// new creates a new DatabaseVFS instance +pub fn new(data_dir string, metadata_dir string) !&DatabaseVFS { + return vfs_new( + data_dir: data_dir + metadata_dir: metadata_dir + incremental_mode: false + )! +} + +// Factory method for creating a new DatabaseVFS instance +pub fn vfs_new(params VFSParams) !&DatabaseVFS { + pathlib.get_dir(path: params.data_dir, create: true) or { + return error('Failed to create data directory: ${err}') + } + + mut db_data := ourdb.new( + path: '${params.data_dir}/ourdb_fs.db_data' + incremental_mode: params.incremental_mode + )! + + mut fs := &DatabaseVFS{ + root_id: 1 + block_size: 1024 * 4 + data_dir: params.data_dir + db_data: &db_data + } + + return fs +} diff --git a/lib/vfs/vfs_db/metadata.v b/lib/vfs/vfs_db/metadata.v new file mode 100644 index 00000000..3942cabe --- /dev/null +++ b/lib/vfs/vfs_db/metadata.v @@ -0,0 +1,27 @@ +module vfs_db + +import time +import freeflowuniverse.herolib.vfs + +// Metadata represents the common metadata for both files and directories +pub struct NewMetadata { +pub mut: + name string @[required] // name of file or directory + file_type vfs.FileType @[required] + size u64 @[required] + mode u32 = 0o644 // file permissions + owner string = 'user' + group string = 'user' +} + +pub fn (mut fs DatabaseVFS) new_metadata(metadata NewMetadata) vfs.Metadata { + return vfs.new_metadata( + id: fs.get_next_id() + name: metadata.name + file_type: metadata.file_type + size: metadata.size + mode: metadata.mode + owner: metadata.owner + group: metadata.group + ) +} diff --git a/lib/vfs/vfs_db/model_directory.v b/lib/vfs/vfs_db/model_directory.v new file mode 100644 index 00000000..e2155e0c --- /dev/null +++ b/lib/vfs/vfs_db/model_directory.v @@ -0,0 +1,34 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs + +// Directory represents a directory in the virtual filesystem +pub struct Directory { +pub mut: + metadata vfs.Metadata // vfs.Metadata from models_common.v + children []u32 // List of child entry IDs (instead of actual entries) + parent_id u32 // ID of parent directory (0 for root) +} + +fn (d &Directory) get_metadata() vfs.Metadata { + return d.metadata +} + +fn (d &Directory) get_path() string { + return d.metadata.name +} + +// is_dir returns true if the entry is a directory +pub fn (d &Directory) is_dir() bool { + return d.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (d &Directory) is_file() bool { + return d.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (d &Directory) is_symlink() bool { + return d.metadata.file_type == .symlink +} diff --git a/lib/vfs/vfs_db/model_file.v b/lib/vfs/vfs_db/model_file.v new file mode 100644 index 00000000..724db2da --- /dev/null +++ b/lib/vfs/vfs_db/model_file.v @@ -0,0 +1,91 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs + +// File represents a file in the virtual filesystem +pub struct File { +pub mut: + metadata vfs.Metadata // vfs.Metadata from models_common.v + data string // File content stored in DB + parent_id u32 // ID of parent directory +} + +// write updates the file's content and returns updated file +pub fn (mut file File) write(content string) { + file.data = content + file.metadata.size = u64(content.len) + file.metadata.modified() +} + +// Rename the file +fn (mut f File) rename(name string) { + f.metadata.name = name +} + +// read returns the file's content +pub fn (mut f File) read() string { + return f.data +} + +fn (f &File) get_metadata() vfs.Metadata { + return f.metadata +} + +fn (f &File) get_path() string { + return f.metadata.name +} + +// is_dir returns true if the entry is a directory +pub fn (f &File) is_dir() bool { + return f.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (f &File) is_file() bool { + return f.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (f &File) is_symlink() bool { + return f.metadata.file_type == .symlink +} + +pub struct NewFile { +pub: + name string @[required] // name of file or directory + data string + mode u32 = 0o644 // file permissions + owner string = 'user' + group string = 'user' + parent_id u32 +} + +// mkdir creates a new directory with default permissions +pub fn (mut fs DatabaseVFS) new_file(file NewFile) !&File { + f := File{ + data: file.data + metadata: fs.new_metadata(NewMetadata{ + name: file.name + mode: file.mode + owner: file.owner + group: file.group + size: u64(file.data.len) + file_type: .file + }) + } + + // Save new directory to DB + fs.save_entry(f)! + return &f +} + +// mkdir creates a new directory with default permissions +pub fn (mut fs DatabaseVFS) copy_file(file File) !&File { + return fs.new_file( + data: file.data + name: file.metadata.name + mode: file.metadata.mode + owner: file.metadata.owner + group: file.metadata.group + ) +} diff --git a/lib/vfs/vfs_db/model_fsentry.v b/lib/vfs/vfs_db/model_fsentry.v new file mode 100644 index 00000000..7a640ac3 --- /dev/null +++ b/lib/vfs/vfs_db/model_fsentry.v @@ -0,0 +1,26 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs + +// FSEntry represents any type of filesystem entry +pub type FSEntry = Directory | File | Symlink + +fn (e &FSEntry) get_metadata() vfs.Metadata { + return e.metadata +} + +fn (e &FSEntry) get_path() string { + return e.metadata.name +} + +fn (e &FSEntry) is_dir() bool { + return e.metadata.file_type == .directory +} + +fn (e &FSEntry) is_file() bool { + return e.metadata.file_type == .file +} + +fn (e &FSEntry) is_symlink() bool { + return e.metadata.file_type == .symlink +} diff --git a/lib/vfs/vfs_db/model_symlink.v b/lib/vfs/vfs_db/model_symlink.v new file mode 100644 index 00000000..138b9cbe --- /dev/null +++ b/lib/vfs/vfs_db/model_symlink.v @@ -0,0 +1,46 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs + +// Symlink represents a symbolic link in the virtual filesystem +pub struct Symlink { +pub mut: + metadata vfs.Metadata // vfs.Metadata from models_common.v + target string // Path that this symlink points to + parent_id u32 // ID of parent directory +} + +// update_target changes the symlink's target path +pub fn (mut sl Symlink) update_target(new_target string) ! { + sl.target = new_target + sl.metadata.modified() +} + +// get_target returns the current target path +pub fn (mut sl Symlink) get_target() !string { + sl.metadata.accessed() + return sl.target +} + +fn (s &Symlink) get_metadata() vfs.Metadata { + return s.metadata +} + +fn (s &Symlink) get_path() string { + return s.metadata.name +} + +// is_dir returns true if the entry is a directory +pub fn (self &Symlink) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &Symlink) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &Symlink) is_symlink() bool { + return self.metadata.file_type == .symlink +} diff --git a/lib/vfs/vfs_db/print.v b/lib/vfs/vfs_db/print.v new file mode 100644 index 00000000..6f0c2049 --- /dev/null +++ b/lib/vfs/vfs_db/print.v @@ -0,0 +1,36 @@ +module vfs_db + +// str returns a formatted string of directory contents (non-recursive) +pub fn (mut fs DatabaseVFS) directory_print(dir Directory) string { + mut result := '${dir.metadata.name}/\n' + + for child_id in dir.children { + if entry := fs.load_entry(child_id) { + if entry is Directory { + result += ' 📁 ${entry.metadata.name}/\n' + } else if entry is File { + result += ' 📄 ${entry.metadata.name}\n' + } else if entry is Symlink { + result += ' 🔗 ${entry.metadata.name} -> ${entry.target}\n' + } + } + } + return result +} + +// printall prints the directory structure recursively +pub fn (mut fs DatabaseVFS) directory_printall(dir Directory, indent string) !string { + mut result := '${indent}📁 ${dir.metadata.name}/\n' + + for child_id in dir.children { + mut entry := fs.load_entry(child_id)! + if mut entry is Directory { + result += fs.directory_printall(entry, indent + ' ')! + } else if mut entry is File { + result += '${indent} 📄 ${entry.metadata.name}\n' + } else if mut entry is Symlink { + result += '${indent} 🔗 ${entry.metadata.name} -> ${entry.target}\n' + } + } + return result +} diff --git a/lib/vfs/vfs_db/readme.md b/lib/vfs/vfs_db/readme.md new file mode 100644 index 00000000..0753fccf --- /dev/null +++ b/lib/vfs/vfs_db/readme.md @@ -0,0 +1,167 @@ +# VFS DB: A Virtual File System with Database Backend + +A virtual file system implementation that provides a filesystem interface on top of a database backend (OURDb). This module enables hierarchical file system operations while storing all data in a key-value database. + +## Overview + +VFS DB implements a complete virtual file system that: +- Uses OURDb as the storage backend +- Supports files, directories, and symbolic links +- Provides standard file system operations +- Maintains hierarchical structure +- Handles metadata and file data efficiently + +## Architecture + +### Core Components + +#### 1. Database Backend (OURDb) +- Uses key-value store with u32 keys and []u8 values +- Stores both metadata and file content +- Provides atomic operations for data consistency + +#### 2. File System Entries +All entries (files, directories, symlinks) share common metadata: +```v +struct Metadata { + id u32 // unique identifier used as key in DB + name string // name of file or directory + file_type FileType + size u64 + created_at i64 // unix epoch timestamp + modified_at i64 // unix epoch timestamp + accessed_at i64 // unix epoch timestamp + mode u32 // file permissions + owner string + group string +} +``` + +The system supports three types of entries: +- Files: Store actual file data +- Directories: Maintain parent-child relationships +- Symlinks: Store symbolic link targets + +### Key Features + +1. **File Operations** + - Create/delete files + - Read/write file content + - Copy and move files + - Rename files + - Check file existence + +2. **Directory Operations** + - Create/delete directories + - List directory contents + - Traverse directory tree + - Manage parent-child relationships + +3. **Symbolic Link Support** + - Create symbolic links + - Read link targets + - Delete links + +4. **Metadata Management** + - Track creation, modification, and access times + - Handle file permissions + - Store owner and group information + +### Implementation Details + +1. **Entry Types** +```v +pub type FSEntry = Directory | File | Symlink +``` + +2. **Database Interface** +```v +pub interface Database { +mut: + get(id u32) ![]u8 + set(ourdb.OurDBSetArgs) !u32 + delete(id u32)! +} +``` + +3. **VFS Structure** +```v +pub struct DatabaseVFS { +pub mut: + root_id u32 + block_size u32 + data_dir string + metadata_dir string + db_data &Database + last_inserted_id u32 +} +``` + +### Usage Example + +```v +// Create a new VFS instance +mut fs := vfs_db.new(data_dir: "/path/to/data", metadata_dir: "/path/to/metadata")! + +// Create a directory +fs.dir_create("/mydir")! + +// Create and write to a file +fs.file_create("/mydir/test.txt")! +fs.file_write("/mydir/test.txt", "Hello World".bytes())! + +// Read file content +content := fs.file_read("/mydir/test.txt")! + +// Create a symbolic link +fs.link_create("/mydir/test.txt", "/mydir/link.txt")! + +// List directory contents +entries := fs.dir_list("/mydir")! + +// Delete files/directories +fs.file_delete("/mydir/test.txt")! +fs.dir_delete("/mydir")! +``` + +### Data Encoding + +The system uses an efficient binary encoding format for storing entries: +- First byte: Version number for format compatibility +- Second byte: Entry type indicator +- Remaining bytes: Entry-specific data + +This ensures minimal storage overhead while maintaining data integrity. + +## Error Handling + +The implementation uses V's error handling system with descriptive error messages for: +- File/directory not found +- Permission issues +- Invalid operations +- Database errors + +## Thread Safety + +The implementation is designed to be thread-safe through: +- Proper mutex usage +- Atomic operations +- Clear ownership semantics + +## Future Improvements + +1. **Performance Optimizations** + - Caching frequently accessed entries + - Batch operations support + - Improved directory traversal + +2. **Feature Additions** + - Extended attribute support + - Access control lists + - Quota management + - Transaction support + +3. **Robustness** + - Recovery mechanisms + - Consistency checks + - Better error recovery diff --git a/lib/vfs/vfs_db/vfs.v b/lib/vfs/vfs_db/vfs.v new file mode 100644 index 00000000..c7366808 --- /dev/null +++ b/lib/vfs/vfs_db/vfs.v @@ -0,0 +1,83 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs +import freeflowuniverse.herolib.data.ourdb +import time + +// DatabaseVFS represents the virtual filesystem +@[heap] +pub struct DatabaseVFS { +pub mut: + root_id u32 // ID of root directory + block_size u32 // Size of data blocks in bytes + data_dir string // Directory to store DatabaseVFS data + metadata_dir string // Directory where we store the metadata + db_data &Database @[str: skip] // Database instance for storage + last_inserted_id u32 +} + +pub interface Database { +mut: + get(id u32) ![]u8 + set(ourdb.OurDBSetArgs) !u32 + delete(id u32) ! +} + +// Get the next ID, it should be some kind of auto-incrementing ID +pub fn (mut fs DatabaseVFS) get_next_id() u32 { + fs.last_inserted_id = fs.last_inserted_id + 1 + return fs.last_inserted_id +} + +// load_entry loads an entry from the database by ID and sets up parent references +pub fn (mut fs DatabaseVFS) load_entry(id u32) !FSEntry { + if data := fs.db_data.get(id) { + // First byte is version, second byte indicates the type + // TODO: check we dont overflow filetype (u8 in boundaries of filetype) + entry_type := unsafe { vfs.FileType(data[1]) } + + match entry_type { + .directory { + mut dir := decode_directory(data) or { + return error('Failed to decode directory: ${err}') + } + return dir + } + .file { + mut file := decode_file(data) or { return error('Failed to decode file: ${err}') } + return file + } + .symlink { + mut symlink := decode_symlink(data) or { + return error('Failed to decode symlink: ${err}') + } + return symlink + } + } + } + return error('Entry not found') +} + +// save_entry saves an entry to the database +pub fn (mut fs DatabaseVFS) save_entry(entry FSEntry) !u32 { + match entry { + Directory { + encoded := entry.encode() + return fs.db_data.set(id: entry.metadata.id, data: encoded) or { + return error('Failed to save directory on id:${entry.metadata.id}: ${err}') + } + } + File { + encoded := entry.encode() + return fs.db_data.set(id: entry.metadata.id, data: encoded) or { + return error('Failed to save file on id:${entry.metadata.id}: ${err}') + } + } + Symlink { + encoded := entry.encode() + return fs.db_data.set(id: entry.metadata.id, data: encoded) or { + return error('Failed to save symlink on id:${entry.metadata.id}: ${err}') + } + } + } +} diff --git a/lib/vfs/vfs_db/vfs_directory.v b/lib/vfs/vfs_db/vfs_directory.v new file mode 100644 index 00000000..22e175dc --- /dev/null +++ b/lib/vfs/vfs_db/vfs_directory.v @@ -0,0 +1,438 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs + +// // write creates a new file or writes to an existing file +// pub fn (mut fs DatabaseVFS) directory_write(dir_ Directory, name string, content string) !&File { +// mut dir := dir_ +// mut file := &File{} +// mut is_new := true + +// // Check if file exists +// for child_id in dir.children { +// mut entry := fs.load_entry(child_id)! +// if entry.metadata.name == name { +// if mut entry is File { +// mut d := entry +// file = &d +// is_new = false +// break +// } else { +// return error('${name} exists but is not a file') +// } +// } +// } + +// if is_new { +// // Create new file +// current_time := time.now().unix() +// file = &File{ +// metadata: vfs.Metadata{ +// id: fs.get_next_id() +// name: name +// file_type: .file +// size: u64(content.len) +// created_at: current_time +// modified_at: current_time +// accessed_at: current_time +// mode: 0o644 +// owner: 'user' +// group: 'user' +// } +// data: content +// parent_id: dir.metadata.id +// } + +// // Save new file to DB +// fs.save_entry(file)! + +// // Update children list +// dir.children << file.metadata.id +// fs.save_entry(dir)! +// } else { +// // Update existing file +// file.write(content) +// fs.save_entry(file)! +// } + +// return file +// } + +// // read reads content from a file +// pub fn (mut dir Directory) directory_read(name string) !string { +// // Find file +// for child_id in dir.children { +// if mut entry := dir.myvfs.load_entry(child_id) { +// if entry.metadata.name == name { +// if mut entry is File { +// return entry.read() +// } else { +// return error('${name} is not a file') +// } +// } +// } +// } +// return error('File ${name} not found') +// } + +// mkdir creates a new directory with default permissions +pub fn (mut fs DatabaseVFS) directory_mkdir(mut dir Directory, name string) !&Directory { + // Check if directory already exists + for child_id in dir.children { + if entry := fs.load_entry(child_id) { + if entry.metadata.name == name { + return error('Directory ${name} already exists') + } + } + } + + new_dir := fs.new_directory(name: name, parent_id: dir.metadata.id)! + dir.children << new_dir.metadata.id + fs.save_entry(dir)! + return new_dir +} + +pub struct NewDirectory { +pub: + name string @[required] // name of file or directory + mode u32 = 0o755 // file permissions + owner string = 'user' + group string = 'user' + parent_id u32 + children []u32 +} + +// mkdir creates a new directory with default permissions +pub fn (mut fs DatabaseVFS) new_directory(dir NewDirectory) !&Directory { + d := Directory{ + parent_id: dir.parent_id + metadata: fs.new_metadata(NewMetadata{ + name: dir.name + mode: dir.mode + owner: dir.owner + group: dir.group + size: u64(0) + file_type: .directory + }) + children: dir.children + } + // Save new directory to DB + fs.save_entry(d)! + return &d +} + +// mkdir creates a new directory with default permissions +pub fn (mut fs DatabaseVFS) copy_directory(dir Directory) !&Directory { + return fs.new_directory( + name: dir.metadata.name + mode: dir.metadata.mode + owner: dir.metadata.owner + group: dir.metadata.group + ) +} + +// touch creates a new empty file with default permissions +pub fn (mut fs DatabaseVFS) directory_touch(dir_ Directory, name string) !&File { + mut dir := dir_ + + // Check if file already exists + for child_id in dir.children { + if entry := fs.load_entry(child_id) { + if entry.metadata.name == name { + return error('File ${name} already exists') + } + } + } + + new_file := fs.new_file( + parent_id: dir.metadata.id + name: name + )! + + // Update children list + dir.children << new_file.metadata.id + fs.save_entry(dir)! + return new_file +} + +// rm removes a file or directory by name +pub fn (mut fs DatabaseVFS) directory_rm(mut dir Directory, name string) ! { + mut found := false + mut found_id := u32(0) + mut found_idx := 0 + + for i, child_id in dir.children { + if entry := fs.load_entry(child_id) { + if entry.metadata.name == name { + found = true + found_id = child_id + found_idx = i + if entry is Directory { + if entry.children.len > 0 { + return error('Directory not empty') + } + } + break + } + } + } + + if !found { + return error('${name} not found') + } + + // Delete entry from DB + fs.db_data.delete(found_id) or { return error('Failed to delete entry: ${err}') } + + // Update children list + dir.children.delete(found_idx) + fs.save_entry(dir)! +} + +pub struct MoveDirArgs { +pub mut: + src_entry_name string @[required] // source entry name + dst_entry_name string @[required] // destination entry name + dst_parent_dir &Directory @[required] // destination OurDBFSDirectory +} + +pub fn (mut fs DatabaseVFS) directory_move(dir_ Directory, args_ MoveDirArgs) !&Directory { + mut dir := dir_ + mut args := args_ + mut found := false + + for child_id in dir.children { + if mut entry := fs.load_entry(child_id) { + if entry.metadata.name == args.src_entry_name { + if entry is File { + return error('${args.src_entry_name} is a file') + } + + if entry is Symlink { + return error('${args.src_entry_name} is a symlink') + } + + found = true + mut entry_ := entry as Directory + entry_.metadata.name = args.dst_entry_name + entry_.metadata.modified() + entry_.parent_id = args.dst_parent_dir.metadata.id + + // Remove from old parent's children + dir.children = dir.children.filter(it != child_id) + fs.save_entry(dir)! + + // Recursively update all child paths in moved directory + fs.move_children_recursive(mut entry_)! + + // Ensure no duplicate entries in dst_parent_dir + if entry_.metadata.id !in args.dst_parent_dir.children { + args.dst_parent_dir.children << entry_.metadata.id + } + + fs.save_entry(entry_)! + fs.save_entry(args.dst_parent_dir)! + + return &entry_ + } + } + } + + if !found { + return error('${args.src_entry_name} not found') + } + + return error('Unexpected move failure') +} + +// Recursive function to update parent_id for all children +fn (mut fs DatabaseVFS) move_children_recursive(mut dir Directory) ! { + for child in dir.children { + if mut child_entry := fs.load_entry(child) { + child_entry.parent_id = dir.metadata.id + + if child_entry is Directory { + // Recursively move subdirectories + mut child_entry_ := child_entry as Directory + fs.move_children_recursive(mut child_entry_)! + } + + fs.save_entry(child_entry)! + } + } +} + +pub struct CopyDirArgs { +pub mut: + src_entry_name string @[required] // source entry name + dst_entry_name string @[required] // destination entry name + dst_parent_dir &Directory @[required] // destination Directory +} + +pub fn (mut fs DatabaseVFS) directory_copy(mut dir Directory, args_ CopyDirArgs) !&Directory { + mut found := false + mut args := args_ + + for child_id in dir.children { + if mut entry := fs.load_entry(child_id) { + if entry.metadata.name == args.src_entry_name { + if entry is File { + return error('${args.src_entry_name} is a file, not a directory') + } + + if entry is Symlink { + return error('${args.src_entry_name} is a symlink, not a directory') + } + + found = true + mut src_dir := entry as Directory + + // Create a new directory with copied metadata + mut new_dir := fs.copy_directory(Directory{ + ...src_dir + metadata: vfs.Metadata{ + ...src_dir.metadata + name: args.dst_entry_name + } + parent_id: args.dst_parent_dir.metadata.id + })! + + // Recursively copy children + fs.copy_children_recursive(mut src_dir, mut new_dir)! + + // Save new directory + fs.save_entry(new_dir)! + args.dst_parent_dir.children << new_dir.metadata.id + fs.save_entry(args.dst_parent_dir)! + return new_dir + } + } + } + + if !found { + return error('${args.src_entry_name} not found') + } + + return error('Unexpected copy failure') +} + +fn (mut fs DatabaseVFS) copy_children_recursive(mut src_dir Directory, mut dst_dir Directory) ! { + for child_id in src_dir.children { + if mut entry := fs.load_entry(child_id) { + match entry { + Directory { + mut entry_ := entry as Directory + mut new_subdir := fs.copy_directory(Directory{ + ...entry_ + children: []u32{} + parent_id: dst_dir.metadata.id + })! + + fs.copy_children_recursive(mut entry_, mut new_subdir)! + fs.save_entry(new_subdir)! + dst_dir.children << new_subdir.metadata.id + } + File { + mut entry_ := entry as File + mut new_file := fs.copy_file(File{ + ...entry_ + parent_id: dst_dir.metadata.id + })! + dst_dir.children << new_file.metadata.id + } + Symlink { + mut entry_ := entry as Symlink + mut new_symlink := Symlink{ + metadata: fs.new_metadata( + name: entry_.metadata.name + file_type: .symlink + size: u64(0) + mode: entry_.metadata.mode + owner: entry_.metadata.owner + group: entry_.metadata.group + ) + target: entry_.target + parent_id: dst_dir.metadata.id + } + fs.save_entry(new_symlink)! + dst_dir.children << new_symlink.metadata.id + } + } + } + } + + fs.save_entry(dst_dir)! +} + +pub fn (mut fs DatabaseVFS) directory_rename(dir Directory, src_name string, dst_name string) !&Directory { + mut found := false + mut dir_ := dir + + for child_id in dir.children { + if mut entry := fs.load_entry(child_id) { + if entry.metadata.name == src_name { + found = true + entry.metadata.name = dst_name + entry.metadata.modified() + fs.save_entry(entry)! + get_dir := entry as Directory + return &get_dir + } + } + } + + if !found { + return error('${src_name} not found') + } + + return &dir_ +} + +// get_children returns all immediate children as FSEntry objects +pub fn (mut fs DatabaseVFS) directory_children(mut dir Directory, recursive bool) ![]FSEntry { + mut entries := []FSEntry{} + for child_id in dir.children { + entry := fs.load_entry(child_id)! + entries << entry + if recursive { + if entry is Directory { + mut d := entry + entries << fs.directory_children(mut d, true)! + } + } + } + + return entries +} + +// pub fn (mut dir Directory) delete() ! { +// // Delete all children first +// for child_id in dir.children { +// dir.myvfs.delete_entry(child_id) or {} +// } + +// // Clear children list +// dir.children.clear() + +// // Save the updated directory +// dir.myvfs.save_entry(dir) or { return error('Failed to save directory: ${err}') } +// } + +// add_symlink adds an existing symlink to this directory +pub fn (mut fs DatabaseVFS) directory_add_symlink(mut dir Directory, mut symlink Symlink) ! { + // Check if name already exists + for child_id in dir.children { + if entry := fs.load_entry(child_id) { + if entry.metadata.name == symlink.metadata.name { + return error('Entry with name ${symlink.metadata.name} already exists') + } + } + } + + // Save symlink to DB + fs.save_entry(symlink)! + + // Add to children + dir.children << symlink.metadata.id + fs.save_entry(dir)! +} diff --git a/lib/vfs/vfs_db/vfs_getters.v b/lib/vfs/vfs_db/vfs_getters.v new file mode 100644 index 00000000..1d41876c --- /dev/null +++ b/lib/vfs/vfs_db/vfs_getters.v @@ -0,0 +1,81 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs +import os +import time + +// Implementation of VFSImplementation interface +pub fn (mut fs DatabaseVFS) root_get_as_dir() !&Directory { + // Try to load root directory from DB if it exists + if data := fs.db_data.get(fs.root_id) { + mut loaded_root := decode_directory(data) or { + return error('Failed to decode root directory: ${err}') + } + return &loaded_root + } + + // Create and save new root directory + mut myroot := Directory{ + metadata: vfs.Metadata{ + id: fs.get_next_id() + file_type: .directory + name: '' + created_at: time.now().unix() + modified_at: time.now().unix() + accessed_at: time.now().unix() + mode: 0o755 // default directory permissions + owner: 'user' // TODO: get from system + group: 'user' // TODO: get from system + } + parent_id: 0 + } + fs.root_id = fs.save_entry(myroot)! + return &myroot +} + +fn (mut self DatabaseVFS) get_entry(path string) !FSEntry { + if path == '/' || path == '' || path == '.' { + return FSEntry(self.root_get_as_dir()!) + } + + mut current := *self.root_get_as_dir()! + parts := path.trim_left('/').split('/') + + for i := 0; i < parts.len; i++ { + mut found := false + children := self.directory_children(mut current, false)! + + for child in children { + if child.metadata.name == parts[i] { + match child { + Directory { + current = child + found = true + break + } + else { + if i == parts.len - 1 { + return child + } else { + return error('Not a directory: ${parts[i]}') + } + } + } + } + } + + if !found { + return error('Path not found: ${path}') + } + } + + return FSEntry(current) +} + +fn (mut self DatabaseVFS) get_directory(path string) !&Directory { + mut entry := self.get_entry(path)! + if mut entry is Directory { + return &entry + } + return error('Not a directory: ${path}') +} diff --git a/lib/vfs/vfs_db/vfs_implementation.v b/lib/vfs/vfs_db/vfs_implementation.v new file mode 100644 index 00000000..701b47c0 --- /dev/null +++ b/lib/vfs/vfs_db/vfs_implementation.v @@ -0,0 +1,200 @@ +module vfs_db + +import freeflowuniverse.herolib.vfs +import os +import time + +// Implementation of VFSImplementation interface +pub fn (mut fs DatabaseVFS) root_get() !vfs.FSEntry { + return fs.root_get_as_dir()! +} + +pub fn (mut self DatabaseVFS) file_create(path string) !vfs.FSEntry { + // Get parent directory + parent_path := os.dir(path) + file_name := os.base(path) + + mut parent_dir := self.get_directory(parent_path)! + return self.directory_touch(parent_dir, file_name)! +} + +pub fn (mut self DatabaseVFS) file_read(path string) ![]u8 { + mut entry := self.get_entry(path)! + if mut entry is File { + return entry.read().bytes() + } + return error('Not a file: ${path}') +} + +pub fn (mut self DatabaseVFS) file_write(path string, data []u8) ! { + mut entry := self.get_entry(path)! + if mut entry is File { + entry.write(data.bytestr()) + self.save_entry(entry)! + } else { + return error('Not a file: ${path}') + } +} + +pub fn (mut self DatabaseVFS) file_delete(path string) ! { + parent_path := os.dir(path) + file_name := os.base(path) + + mut parent_dir := self.get_directory(parent_path)! + self.directory_rm(mut parent_dir, file_name)! +} + +pub fn (mut self DatabaseVFS) dir_create(path string) !vfs.FSEntry { + parent_path := os.dir(path) + dir_name := os.base(path) + + mut parent_dir := self.get_directory(parent_path)! + return self.directory_mkdir(mut parent_dir, dir_name)! +} + +pub fn (mut self DatabaseVFS) dir_list(path string) ![]vfs.FSEntry { + mut dir := self.get_directory(path)! + return self.directory_children(mut dir, false)!.map(vfs.FSEntry(it)) +} + +pub fn (mut self DatabaseVFS) dir_delete(path string) ! { + parent_path := os.dir(path) + dir_name := os.base(path) + + mut parent_dir := self.get_directory(parent_path)! + self.directory_rm(mut parent_dir, dir_name)! +} + +pub fn (mut self DatabaseVFS) link_create(target_path string, link_path string) !vfs.FSEntry { + parent_path := os.dir(link_path) + link_name := os.base(link_path) + + mut parent_dir := self.get_directory(parent_path)! + + mut symlink := Symlink{ + metadata: vfs.Metadata{ + id: self.get_next_id() + name: link_name + file_type: .symlink + created_at: time.now().unix() + modified_at: time.now().unix() + accessed_at: time.now().unix() + mode: 0o777 + owner: 'user' + group: 'user' + } + target: target_path + parent_id: parent_dir.metadata.id + } + + self.directory_add_symlink(mut parent_dir, mut symlink)! + return symlink +} + +pub fn (mut self DatabaseVFS) link_read(path string) !string { + mut entry := self.get_entry(path)! + if mut entry is Symlink { + return entry.get_target()! + } + return error('Not a symlink: ${path}') +} + +pub fn (mut self DatabaseVFS) link_delete(path string) ! { + parent_path := os.dir(path) + file_name := os.base(path) + + mut parent_dir := self.get_directory(parent_path)! + self.directory_rm(mut parent_dir, file_name)! +} + +pub fn (mut self DatabaseVFS) exists(path_ string) bool { + path := if !path_.starts_with('/') { + '/${path_}' + } else { + path_ + } + if path == '/' { + return true + } + self.get_entry(path) or { return false } + return true +} + +pub fn (mut fs DatabaseVFS) get(path string) !vfs.FSEntry { + return fs.get_entry(path)! +} + +pub fn (mut self DatabaseVFS) rename(old_path string, new_path string) !vfs.FSEntry { + src_parent_path := os.dir(old_path) + src_name := os.base(old_path) + dst_name := os.base(new_path) + + mut src_parent_dir := self.get_directory(src_parent_path)! + return self.directory_rename(src_parent_dir, src_name, dst_name)! +} + +pub fn (mut self DatabaseVFS) copy(src_path string, dst_path string) !vfs.FSEntry { + src_parent_path := os.dir(src_path) + dst_parent_path := os.dir(dst_path) + + if !self.exists(src_parent_path) { + return error('${src_parent_path} does not exist') + } + + if !self.exists(dst_parent_path) { + return error('${dst_parent_path} does not exist') + } + + src_name := os.base(src_path) + dst_name := os.base(dst_path) + + mut src_parent_dir := self.get_directory(src_parent_path)! + mut dst_parent_dir := self.get_directory(dst_parent_path)! + + if src_parent_dir == dst_parent_dir && src_name == dst_name { + return error('Moving to the same path not supported') + } + + return self.directory_copy(mut src_parent_dir, + src_entry_name: src_name + dst_entry_name: dst_name + dst_parent_dir: dst_parent_dir + )! +} + +pub fn (mut self DatabaseVFS) move(src_path string, dst_path string) !vfs.FSEntry { + src_parent_path := os.dir(src_path) + dst_parent_path := os.dir(dst_path) + + if !self.exists(src_parent_path) { + return error('${src_parent_path} does not exist') + } + + if !self.exists(dst_parent_path) { + return error('${dst_parent_path} does not exist') + } + + src_name := os.base(src_path) + dst_name := os.base(dst_path) + + mut src_parent_dir := self.get_directory(src_parent_path)! + mut dst_parent_dir := self.get_directory(dst_parent_path)! + + if src_parent_dir == dst_parent_dir && src_name == dst_name { + return error('Moving to the same path not supported') + } + + return self.directory_move(src_parent_dir, + src_entry_name: src_name + dst_entry_name: dst_name + dst_parent_dir: dst_parent_dir + )! +} + +pub fn (mut self DatabaseVFS) delete(path string) ! { + // TODO: implement +} + +pub fn (mut self DatabaseVFS) destroy() ! { + // Nothing to do as the core VFS handles cleanup +} diff --git a/lib/vfs/vfsourdb/vfsourdb_test.v b/lib/vfs/vfs_db/vfs_implementation_test.v similarity index 98% rename from lib/vfs/vfsourdb/vfsourdb_test.v rename to lib/vfs/vfs_db/vfs_implementation_test.v index 19cf5fcd..c1f13359 100644 --- a/lib/vfs/vfsourdb/vfsourdb_test.v +++ b/lib/vfs/vfs_db/vfs_implementation_test.v @@ -1,9 +1,9 @@ -module vfsourdb +module vfs_db import os import rand -fn setup_vfs() !(&OurDBVFS, string, string) { +fn setup_vfs() !(&DatabaseVFS, string, string) { test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_test_data_${rand.string(3)}') test_meta_dir := os.join_path(os.temp_dir(), 'vfsourdb_test_meta_${rand.string(3)}') diff --git a/lib/vfs/vfs_local/README.md b/lib/vfs/vfs_local/README.md new file mode 100644 index 00000000..7254efad --- /dev/null +++ b/lib/vfs/vfs_local/README.md @@ -0,0 +1,9 @@ +### Local Filesystem (LocalVFS) + +The LocalVFS implementation provides a direct passthrough to the operating system's filesystem. It implements all vfscore operations by delegating to the corresponding OS filesystem operations. + +Features: +- Direct access to local filesystem +- Full support for all vfscore operations +- Preserves file permissions and metadata +- Efficient for local file operations \ No newline at end of file diff --git a/lib/vfs/vfscore/local.v b/lib/vfs/vfs_local/local.v similarity index 85% rename from lib/vfs/vfscore/local.v rename to lib/vfs/vfs_local/local.v index 555ae2b6..c8133b4f 100644 --- a/lib/vfs/vfscore/local.v +++ b/lib/vfs/vfs_local/local.v @@ -1,45 +1,16 @@ -module vfscore +module vfs_local import os +import freeflowuniverse.herolib.vfs -// LocalFSEntry implements FSEntry for local filesystem -struct LocalFSEntry { -mut: - path string - metadata Metadata -} - -// is_dir returns true if the entry is a directory -pub fn (self &LocalFSEntry) is_dir() bool { - return self.metadata.file_type == .directory -} - -// is_file returns true if the entry is a file -pub fn (self &LocalFSEntry) is_file() bool { - return self.metadata.file_type == .file -} - -// is_symlink returns true if the entry is a symlink -pub fn (self &LocalFSEntry) is_symlink() bool { - return self.metadata.file_type == .symlink -} - -fn (e LocalFSEntry) get_metadata() Metadata { - return e.metadata -} - -fn (e LocalFSEntry) get_path() string { - return e.path -} - -// LocalVFS implements VFSImplementation for local filesystem +// LocalVFS implements vfs.VFSImplementation for local filesystem pub struct LocalVFS { mut: root_path string } // Create a new LocalVFS instance -pub fn new_local_vfs(root_path string) !VFSImplementation { +pub fn new_local_vfs(root_path string) !vfs.VFSImplementation { mut myvfs := LocalVFS{ root_path: root_path } @@ -67,19 +38,20 @@ pub fn (mut myvfs LocalVFS) destroy() ! { myvfs.init()! } -// Convert path to Metadata with improved security and information gathering -fn (myvfs LocalVFS) os_attr_to_metadata(path string) !Metadata { +// Convert path to vfs.Metadata with improved security and information gathering +fn (myvfs LocalVFS) os_attr_to_metadata(path string) !vfs.Metadata { // Get file info atomically to prevent TOCTOU issues attr := os.stat(path) or { return error('Failed to get file attributes: ${err}') } - mut file_type := FileType.file + mut file_type := vfs.FileType.file if os.is_dir(path) { file_type = .directory } else if os.is_link(path) { file_type = .symlink } - return Metadata{ + return vfs.Metadata{ + id: u32(attr.inode) // QUESTION: what should id be here name: os.base(path) file_type: file_type size: u64(attr.size) @@ -95,7 +67,7 @@ fn (myvfs LocalVFS) abs_path(path string) string { } // Basic operations -pub fn (myvfs LocalVFS) root_get() !FSEntry { +pub fn (myvfs LocalVFS) root_get() !vfs.FSEntry { if !os.exists(myvfs.root_path) { return error('Root path does not exist: ${myvfs.root_path}') } @@ -109,7 +81,7 @@ pub fn (myvfs LocalVFS) root_get() !FSEntry { } // File operations with improved error handling and TOCTOU protection -pub fn (myvfs LocalVFS) file_create(path string) !FSEntry { +pub fn (myvfs LocalVFS) file_create(path string) !vfs.FSEntry { abs_path := myvfs.abs_path(path) if os.exists(abs_path) { return error('File already exists: ${path}') @@ -157,7 +129,7 @@ pub fn (myvfs LocalVFS) file_delete(path string) ! { } // Directory operations with improved error handling -pub fn (myvfs LocalVFS) dir_create(path string) !FSEntry { +pub fn (myvfs LocalVFS) dir_create(path string) !vfs.FSEntry { abs_path := myvfs.abs_path(path) if os.exists(abs_path) { return error('Path already exists: ${path}') @@ -172,7 +144,7 @@ pub fn (myvfs LocalVFS) dir_create(path string) !FSEntry { } } -pub fn (myvfs LocalVFS) dir_list(path string) ![]FSEntry { +pub fn (myvfs LocalVFS) dir_list(path string) ![]vfs.FSEntry { abs_path := myvfs.abs_path(path) if !os.exists(abs_path) { return error('Directory does not exist: ${path}') @@ -182,7 +154,7 @@ pub fn (myvfs LocalVFS) dir_list(path string) ![]FSEntry { } entries := os.ls(abs_path) or { return error('Failed to list directory ${path}: ${err}') } - mut result := []FSEntry{cap: entries.len} + mut result := []vfs.FSEntry{cap: entries.len} for entry in entries { rel_path := os.join_path(path, entry) @@ -213,7 +185,7 @@ pub fn (myvfs LocalVFS) exists(path string) bool { return os.exists(myvfs.abs_path(path)) } -pub fn (myvfs LocalVFS) get(path string) !FSEntry { +pub fn (myvfs LocalVFS) get(path string) !vfs.FSEntry { abs_path := myvfs.abs_path(path) if !os.exists(abs_path) { return error('Entry does not exist: ${path}') @@ -227,7 +199,7 @@ pub fn (myvfs LocalVFS) get(path string) !FSEntry { } } -pub fn (myvfs LocalVFS) rename(old_path string, new_path string) !FSEntry { +pub fn (myvfs LocalVFS) rename(old_path string, new_path string) !vfs.FSEntry { abs_old := myvfs.abs_path(old_path) abs_new := myvfs.abs_path(new_path) @@ -241,16 +213,16 @@ pub fn (myvfs LocalVFS) rename(old_path string, new_path string) !FSEntry { os.mv(abs_old, abs_new) or { return error('Failed to rename ${old_path} to ${new_path}: ${err}') } - metadata := myvfs.os_attr_to_metadata(new_path) or { + metadata := myvfs.os_attr_to_metadata(abs_new) or { return error('Failed to get metadata: ${err}') } return LocalFSEntry{ - path: new_path + path: abs_new metadata: metadata } } -pub fn (myvfs LocalVFS) copy(src_path string, dst_path string) !FSEntry { +pub fn (myvfs LocalVFS) copy(src_path string, dst_path string) !vfs.FSEntry { abs_src := myvfs.abs_path(src_path) abs_dst := myvfs.abs_path(dst_path) @@ -271,7 +243,7 @@ pub fn (myvfs LocalVFS) copy(src_path string, dst_path string) !FSEntry { } } -pub fn (myvfs LocalVFS) move(src_path string, dst_path string) !FSEntry { +pub fn (myvfs LocalVFS) move(src_path string, dst_path string) !vfs.FSEntry { abs_src := myvfs.abs_path(src_path) abs_dst := myvfs.abs_path(dst_path) @@ -309,7 +281,7 @@ pub fn (myvfs LocalVFS) delete(path string) ! { } // Symlink operations with improved handling -pub fn (myvfs LocalVFS) link_create(target_path string, link_path string) !FSEntry { +pub fn (myvfs LocalVFS) link_create(target_path string, link_path string) !vfs.FSEntry { abs_target := myvfs.abs_path(target_path) abs_link := myvfs.abs_path(link_path) diff --git a/lib/vfs/vfscore/local_test.v b/lib/vfs/vfs_local/local_test.v similarity index 99% rename from lib/vfs/vfscore/local_test.v rename to lib/vfs/vfs_local/local_test.v index 652344ee..9e54565b 100644 --- a/lib/vfs/vfscore/local_test.v +++ b/lib/vfs/vfs_local/local_test.v @@ -1,4 +1,4 @@ -module vfscore +module vfs_local import os diff --git a/lib/vfs/vfs_local/model_fsentry.v b/lib/vfs/vfs_local/model_fsentry.v new file mode 100644 index 00000000..9b6deea9 --- /dev/null +++ b/lib/vfs/vfs_local/model_fsentry.v @@ -0,0 +1,34 @@ +module vfs_local + +import os +import freeflowuniverse.herolib.vfs + +// LocalFSEntry implements FSEntry for local filesystem +struct LocalFSEntry { +mut: + path string + metadata vfs.Metadata +} + +// is_dir returns true if the entry is a directory +pub fn (self &LocalFSEntry) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &LocalFSEntry) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &LocalFSEntry) is_symlink() bool { + return self.metadata.file_type == .symlink +} + +fn (e LocalFSEntry) get_metadata() vfs.Metadata { + return e.metadata +} + +fn (e LocalFSEntry) get_path() string { + return e.path +} diff --git a/lib/vfs/vfsdedupe/vfsdedupe.v b/lib/vfs/vfsdedupe/vfsdedupe.v deleted file mode 100644 index 6972e9de..00000000 --- a/lib/vfs/vfsdedupe/vfsdedupe.v +++ /dev/null @@ -1,470 +0,0 @@ -module vfsdedupe - -import freeflowuniverse.herolib.vfs.vfscore -import freeflowuniverse.herolib.data.dedupestor -import freeflowuniverse.herolib.data.ourdb -import os -import time - -// Metadata for files and directories -struct Metadata { -pub mut: - id u32 - name string - file_type vfscore.FileType - size u64 - created_at i64 - modified_at i64 - accessed_at i64 - parent_id u32 - hash string // For files, stores the dedupstore hash. For symlinks, stores target path -} - -// Serialization methods for Metadata -pub fn (m Metadata) str() string { - return '${m.id}|${m.name}|${int(m.file_type)}|${m.size}|${m.created_at}|${m.modified_at}|${m.accessed_at}|${m.parent_id}|${m.hash}' -} - -pub fn Metadata.from_str(s string) !Metadata { - parts := s.split('|') - if parts.len != 9 { - return error('Invalid metadata string format') - } - return Metadata{ - id: parts[0].u32() - name: parts[1] - file_type: unsafe { vfscore.FileType(parts[2].int()) } - size: parts[3].u64() - created_at: parts[4].i64() - modified_at: parts[5].i64() - accessed_at: parts[6].i64() - parent_id: parts[7].u32() - hash: parts[8] - } -} - -// DedupeVFS represents a VFS that uses DedupeStore as the underlying storage -pub struct DedupeVFS { -mut: - dedup &dedupestor.DedupeStore // For storing file contents - meta &ourdb.OurDB // For storing metadata -} - -// new creates a new DedupeVFS instance -pub fn new(data_dir string) !&DedupeVFS { - dedup := dedupestor.new( - path: os.join_path(data_dir, 'dedup') - )! - - meta := ourdb.new( - path: os.join_path(data_dir, 'meta') - incremental_mode: true - )! - - mut vfs := DedupeVFS{ - dedup: dedup - meta: &meta - } - - // Create root if it doesn't exist - if !vfs.exists('/') { - vfs.create_root()! - } - - return &vfs -} - -fn (mut self DedupeVFS) create_root() ! { - root_meta := Metadata{ - id: 1 - name: '/' - file_type: .directory - created_at: time.now().unix() - modified_at: time.now().unix() - accessed_at: time.now().unix() - parent_id: 0 // Root has no parent - } - self.meta.set(id: 1, data: root_meta.str().bytes())! -} - -// Implementation of VFSImplementation interface -pub fn (mut self DedupeVFS) root_get() !vfscore.FSEntry { - root_meta := self.get_metadata(1)! - return convert_to_vfscore_entry(root_meta) -} - -pub fn (mut self DedupeVFS) file_create(path string) !vfscore.FSEntry { - parent_path := os.dir(path) - file_name := os.base(path) - - mut parent_meta := self.get_metadata_by_path(parent_path)! - if parent_meta.file_type != .directory { - return error('Parent is not a directory: ${parent_path}') - } - - // Create new file metadata - id := self.meta.get_next_id() or { return error('Failed to get next id') } - file_meta := Metadata{ - id: id - name: file_name - file_type: .file - created_at: time.now().unix() - modified_at: time.now().unix() - accessed_at: time.now().unix() - parent_id: parent_meta.id - } - - self.meta.set(id: id, data: file_meta.str().bytes())! - return convert_to_vfscore_entry(file_meta) -} - -pub fn (mut self DedupeVFS) file_read(path string) ![]u8 { - mut meta := self.get_metadata_by_path(path)! - if meta.file_type != .file { - return error('Not a file: ${path}') - } - if meta.hash == '' { - return []u8{} // Empty file - } - return self.dedup.get(meta.hash)! -} - -pub fn (mut self DedupeVFS) file_write(path string, data []u8) ! { - mut meta := self.get_metadata_by_path(path)! - if meta.file_type != .file { - return error('Not a file: ${path}') - } - - // Store data in dedupstore - this will handle deduplication - hash := self.dedup.store(data)! - - // Update metadata - meta.hash = hash - meta.size = u64(data.len) - meta.modified_at = time.now().unix() - self.meta.set(id: meta.id, data: meta.str().bytes())! -} - -pub fn (mut self DedupeVFS) file_delete(path string) ! { - self.delete(path)! -} - -pub fn (mut self DedupeVFS) dir_create(path string) !vfscore.FSEntry { - parent_path := os.dir(path) - dir_name := os.base(path) - - mut parent_meta := self.get_metadata_by_path(parent_path)! - if parent_meta.file_type != .directory { - return error('Parent is not a directory: ${parent_path}') - } - - // Create new directory metadata - id := self.meta.get_next_id() or { return error('Failed to get next id') } - dir_meta := Metadata{ - id: id - name: dir_name - file_type: .directory - created_at: time.now().unix() - modified_at: time.now().unix() - accessed_at: time.now().unix() - parent_id: parent_meta.id - } - - self.meta.set(id: id, data: dir_meta.str().bytes())! - return convert_to_vfscore_entry(dir_meta) -} - -pub fn (mut self DedupeVFS) dir_list(path string) ![]vfscore.FSEntry { - mut dir_meta := self.get_metadata_by_path(path)! - if dir_meta.file_type != .directory { - return error('Not a directory: ${path}') - } - - mut entries := []vfscore.FSEntry{} - - // Iterate through all IDs up to the current max - max_id := self.meta.get_next_id() or { return error('Failed to get next id') } - for id in 1 .. max_id { - meta_bytes := self.meta.get(id) or { continue } - meta := Metadata.from_str(meta_bytes.bytestr()) or { continue } - if meta.parent_id == dir_meta.id { - entries << convert_to_vfscore_entry(meta) - } - } - - return entries -} - -pub fn (mut self DedupeVFS) dir_delete(path string) ! { - self.delete(path)! -} - -pub fn (mut self DedupeVFS) exists(path string) bool { - self.get_metadata_by_path(path) or { return false } - return true -} - -pub fn (mut self DedupeVFS) get(path string) !vfscore.FSEntry { - meta := self.get_metadata_by_path(path)! - return convert_to_vfscore_entry(meta) -} - -pub fn (mut self DedupeVFS) rename(old_path string, new_path string) !vfscore.FSEntry { - mut meta := self.get_metadata_by_path(old_path)! - new_parent_path := os.dir(new_path) - new_name := os.base(new_path) - - mut new_parent_meta := self.get_metadata_by_path(new_parent_path)! - if new_parent_meta.file_type != .directory { - return error('New parent is not a directory: ${new_parent_path}') - } - - meta.name = new_name - meta.parent_id = new_parent_meta.id - meta.modified_at = time.now().unix() - - self.meta.set(id: meta.id, data: meta.str().bytes())! - return convert_to_vfscore_entry(meta) -} - -pub fn (mut self DedupeVFS) copy(src_path string, dst_path string) !vfscore.FSEntry { - mut src_meta := self.get_metadata_by_path(src_path)! - dst_parent_path := os.dir(dst_path) - dst_name := os.base(dst_path) - - mut dst_parent_meta := self.get_metadata_by_path(dst_parent_path)! - if dst_parent_meta.file_type != .directory { - return error('Destination parent is not a directory: ${dst_parent_path}') - } - - // Create new metadata with same properties but new ID - id := self.meta.get_next_id() or { return error('Failed to get next id') } - new_meta := Metadata{ - id: id - name: dst_name - file_type: src_meta.file_type - size: src_meta.size - created_at: time.now().unix() - modified_at: time.now().unix() - accessed_at: time.now().unix() - parent_id: dst_parent_meta.id - hash: src_meta.hash // Reuse same hash since dedupstore deduplicates content - } - - self.meta.set(id: id, data: new_meta.str().bytes())! - return convert_to_vfscore_entry(new_meta) -} - -pub fn (mut self DedupeVFS) move(src_path string, dst_path string) !vfscore.FSEntry { - return self.rename(src_path, dst_path)! -} - -pub fn (mut self DedupeVFS) delete(path string) ! { - if path == '/' { - return error('Cannot delete root directory') - } - - mut meta := self.get_metadata_by_path(path)! - - if meta.file_type == .directory { - // Check if directory is empty - children := self.dir_list(path)! - if children.len > 0 { - return error('Directory not empty: ${path}') - } - } - - self.meta.delete(meta.id)! -} - -pub fn (mut self DedupeVFS) link_create(target_path string, link_path string) !vfscore.FSEntry { - parent_path := os.dir(link_path) - link_name := os.base(link_path) - - mut parent_meta := self.get_metadata_by_path(parent_path)! - if parent_meta.file_type != .directory { - return error('Parent is not a directory: ${parent_path}') - } - - // Create symlink metadata - id := self.meta.get_next_id() or { return error('Failed to get next id') } - link_meta := Metadata{ - id: id - name: link_name - file_type: .symlink - created_at: time.now().unix() - modified_at: time.now().unix() - accessed_at: time.now().unix() - parent_id: parent_meta.id - hash: target_path // Store target path in hash field for symlinks - } - - self.meta.set(id: id, data: link_meta.str().bytes())! - return convert_to_vfscore_entry(link_meta) -} - -pub fn (mut self DedupeVFS) link_read(path string) !string { - mut meta := self.get_metadata_by_path(path)! - if meta.file_type != .symlink { - return error('Not a symlink: ${path}') - } - return meta.hash // For symlinks, hash field stores target path -} - -pub fn (mut self DedupeVFS) link_delete(path string) ! { - self.delete(path)! -} - -pub fn (mut self DedupeVFS) destroy() ! { - // Nothing to do as the underlying stores handle cleanup -} - -// Helper methods -fn (mut self DedupeVFS) get_metadata(id u32) !Metadata { - meta_bytes := self.meta.get(id)! - return Metadata.from_str(meta_bytes.bytestr()) or { return error('Failed to parse metadata') } -} - -fn (mut self DedupeVFS) get_metadata_by_path(path_ string) !Metadata { - path := if path_ == '' || path_ == '.' { '/' } else { path_ } - - if path == '/' { - return self.get_metadata(1)! // Root always has ID 1 - } - - mut current := self.get_metadata(1)! // Start at root - parts := path.trim_left('/').split('/') - - for part in parts { - mut found := false - max_id := self.meta.get_next_id() or { return error('Failed to get next id') } - - for id in 1 .. max_id { - meta_bytes := self.meta.get(id) or { continue } - meta := Metadata.from_str(meta_bytes.bytestr()) or { continue } - if meta.parent_id == current.id && meta.name == part { - current = meta - found = true - break - } - } - - if !found { - return error('Path not found: ${path}') - } - } - - return current -} - -// Convert between internal metadata and vfscore types -fn convert_to_vfscore_entry(meta Metadata) vfscore.FSEntry { - vfs_meta := vfscore.Metadata{ - id: meta.id - name: meta.name - file_type: meta.file_type - size: meta.size - created_at: meta.created_at - modified_at: meta.modified_at - accessed_at: meta.accessed_at - } - - match meta.file_type { - .directory { - return &DirectoryEntry{ - metadata: vfs_meta - path: meta.name - } - } - .file { - return &FileEntry{ - metadata: vfs_meta - path: meta.name - } - } - .symlink { - return &SymlinkEntry{ - metadata: vfs_meta - path: meta.name - target: meta.hash // For symlinks, hash field stores target path - } - } - } -} - -// Entry type implementations -struct DirectoryEntry { - metadata vfscore.Metadata - path string -} - -fn (e &DirectoryEntry) get_metadata() vfscore.Metadata { - return e.metadata -} - -fn (e &DirectoryEntry) get_path() string { - return e.path -} - -pub fn (self &DirectoryEntry) is_dir() bool { - return self.metadata.file_type == .directory -} - -pub fn (self &DirectoryEntry) is_file() bool { - return self.metadata.file_type == .file -} - -pub fn (self &DirectoryEntry) is_symlink() bool { - return self.metadata.file_type == .symlink -} - -struct FileEntry { - metadata vfscore.Metadata - path string -} - -fn (e &FileEntry) get_metadata() vfscore.Metadata { - return e.metadata -} - -fn (e &FileEntry) get_path() string { - return e.path -} - -pub fn (self &FileEntry) is_dir() bool { - return self.metadata.file_type == .directory -} - -pub fn (self &FileEntry) is_file() bool { - return self.metadata.file_type == .file -} - -pub fn (self &FileEntry) is_symlink() bool { - return self.metadata.file_type == .symlink -} - -struct SymlinkEntry { - metadata vfscore.Metadata - path string - target string -} - -fn (e &SymlinkEntry) get_metadata() vfscore.Metadata { - return e.metadata -} - -fn (e &SymlinkEntry) get_path() string { - return e.path -} - -pub fn (self &SymlinkEntry) is_dir() bool { - return self.metadata.file_type == .directory -} - -pub fn (self &SymlinkEntry) is_file() bool { - return self.metadata.file_type == .file -} - -pub fn (self &SymlinkEntry) is_symlink() bool { - return self.metadata.file_type == .symlink -} diff --git a/lib/vfs/vfsdedupe/vfsdedupe_test.v b/lib/vfs/vfsdedupe/vfsdedupe_test.v deleted file mode 100644 index bb7b0b93..00000000 --- a/lib/vfs/vfsdedupe/vfsdedupe_test.v +++ /dev/null @@ -1,107 +0,0 @@ -module vfsdedupe - -import os -import time -import freeflowuniverse.herolib.lib.vfs.vfscore -import freeflowuniverse.herolib.lib.data.dedupestor -import freeflowuniverse.herolib.lib.data.ourdb - -fn testsuite_begin() { - os.rmdir_all('testdata/vfsdedupe') or {} - os.mkdir_all('testdata/vfsdedupe') or {} -} - -fn test_deduplication() { - mut vfs := new('testdata/vfsdedupe')! - - // Create test files with same content - content1 := 'Hello, World!'.bytes() - content2 := 'Hello, World!'.bytes() // Same content - content3 := 'Different content'.bytes() - - // Create files - file1 := vfs.file_create('/file1.txt')! - file2 := vfs.file_create('/file2.txt')! - file3 := vfs.file_create('/file3.txt')! - - // Write same content to file1 and file2 - vfs.file_write('/file1.txt', content1)! - vfs.file_write('/file2.txt', content2)! - vfs.file_write('/file3.txt', content3)! - - // Read back and verify content - read1 := vfs.file_read('/file1.txt')! - read2 := vfs.file_read('/file2.txt')! - read3 := vfs.file_read('/file3.txt')! - - assert read1 == content1 - assert read2 == content2 - assert read3 == content3 - - // Verify deduplication by checking internal state - meta1 := vfs.get_metadata_by_path('/file1.txt')! - meta2 := vfs.get_metadata_by_path('/file2.txt')! - meta3 := vfs.get_metadata_by_path('/file3.txt')! - - // Files with same content should have same hash - assert meta1.hash == meta2.hash - assert meta1.hash != meta3.hash - - // Test copy operation maintains deduplication - vfs.copy('/file1.txt', '/file1_copy.txt')! - meta_copy := vfs.get_metadata_by_path('/file1_copy.txt')! - assert meta_copy.hash == meta1.hash - - // Test modifying copy creates new hash - vfs.file_write('/file1_copy.txt', 'Modified content'.bytes())! - meta_copy_modified := vfs.get_metadata_by_path('/file1_copy.txt')! - assert meta_copy_modified.hash != meta1.hash -} - -fn test_basic_operations() { - mut vfs := new('testdata/vfsdedupe')! - - // Test directory operations - dir := vfs.dir_create('/testdir')! - assert dir.is_dir() - - subdir := vfs.dir_create('/testdir/subdir')! - assert subdir.is_dir() - - // Test file operations with deduplication - content := 'Test content'.bytes() - - file1 := vfs.file_create('/testdir/file1.txt')! - assert file1.is_file() - vfs.file_write('/testdir/file1.txt', content)! - - file2 := vfs.file_create('/testdir/file2.txt')! - assert file2.is_file() - vfs.file_write('/testdir/file2.txt', content)! // Same content - - // Verify deduplication - meta1 := vfs.get_metadata_by_path('/testdir/file1.txt')! - meta2 := vfs.get_metadata_by_path('/testdir/file2.txt')! - assert meta1.hash == meta2.hash - - // Test listing - entries := vfs.dir_list('/testdir')! - assert entries.len == 3 // subdir, file1.txt, file2.txt - - // Test deletion - vfs.file_delete('/testdir/file1.txt')! - assert !vfs.exists('/testdir/file1.txt') - - // Verify file2 still works after file1 deletion - read2 := vfs.file_read('/testdir/file2.txt')! - assert read2 == content - - // Clean up - vfs.dir_delete('/testdir/subdir')! - vfs.file_delete('/testdir/file2.txt')! - vfs.dir_delete('/testdir')! -} - -fn testsuite_end() { - os.rmdir_all('testdata/vfsdedupe') or {} -} diff --git a/lib/vfs/vfsnested/nested_test.v b/lib/vfs/vfsnested/nested_test.v index 4f097f10..d9c72795 100644 --- a/lib/vfs/vfsnested/nested_test.v +++ b/lib/vfs/vfsnested/nested_test.v @@ -1,6 +1,6 @@ module vfsnested -import freeflowuniverse.herolib.vfs.vfscore +import freeflowuniverse.herolib.vfs.vfs_local import os fn test_nested() ! { @@ -12,9 +12,9 @@ fn test_nested() ! { os.mkdir_all('/tmp/test_nested_vfs/vfs3') or { panic(err) } // Create VFS instances - mut vfs1 := vfscore.new_local_vfs('/tmp/test_nested_vfs/vfs1') or { panic(err) } - mut vfs2 := vfscore.new_local_vfs('/tmp/test_nested_vfs/vfs2') or { panic(err) } - mut vfs3 := vfscore.new_local_vfs('/tmp/test_nested_vfs/vfs3') or { panic(err) } + 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() diff --git a/lib/vfs/vfsnested/vfsnested.v b/lib/vfs/vfsnested/vfsnested.v index c09e422d..5c134d5b 100644 --- a/lib/vfs/vfsnested/vfsnested.v +++ b/lib/vfs/vfsnested/vfsnested.v @@ -1,22 +1,22 @@ module vfsnested -import freeflowuniverse.herolib.vfs.vfscore +import freeflowuniverse.herolib.vfs // 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 + 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]vfscore.VFSImplementation{} + 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 vfscore.VFSImplementation) ! { +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}') } @@ -24,7 +24,7 @@ pub fn (mut self NestedVFS) add_vfs(prefix string, impl vfscore.VFSImplementatio } // find_vfs finds the appropriate VFS implementation for a given path -fn (self &NestedVFS) find_vfs(path string) !(vfscore.VFSImplementation, string) { +fn (self &NestedVFS) find_vfs(path string) !(vfs.VFSImplementation, string) { if path == '' || path == '/' { return self, '/' } @@ -46,10 +46,11 @@ fn (self &NestedVFS) find_vfs(path string) !(vfscore.VFSImplementation, string) } // Implementation of VFSImplementation interface -pub fn (mut self NestedVFS) root_get() !vfscore.FSEntry { +pub fn (mut self NestedVFS) root_get() !vfs.FSEntry { // Return a special root entry that represents the nested VFS return &RootEntry{ - metadata: vfscore.Metadata{ + metadata: vfs.Metadata{ + id: 0 name: '' file_type: .directory size: 0 @@ -70,7 +71,7 @@ pub fn (mut self NestedVFS) link_delete(path string) ! { return impl.link_delete(rel_path) } -pub fn (mut self NestedVFS) file_create(path string) !vfscore.FSEntry { +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) } @@ -90,19 +91,20 @@ pub fn (mut self NestedVFS) file_delete(path string) ! { return impl.file_delete(rel_path) } -pub fn (mut self NestedVFS) dir_create(path string) !vfscore.FSEntry { +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) ![]vfscore.FSEntry { +pub fn (mut self NestedVFS) dir_list(path string) ![]vfs.FSEntry { // Special case for root directory if path == '' || path == '/' { - mut entries := []vfscore.FSEntry{} + mut entries := []vfs.FSEntry{} for prefix, mut impl in self.vfs_map { root := impl.root_get() or { continue } entries << &MountEntry{ - metadata: vfscore.Metadata{ + metadata: vfs.Metadata{ + id: 0 name: prefix file_type: .directory size: 0 @@ -134,7 +136,7 @@ pub fn (mut self NestedVFS) exists(path string) bool { return impl.exists(rel_path) } -pub fn (mut self NestedVFS) get(path string) !vfscore.FSEntry { +pub fn (mut self NestedVFS) get(path string) !vfs.FSEntry { if path == '' || path == '/' { return self.root_get() } @@ -142,7 +144,7 @@ pub fn (mut self NestedVFS) get(path string) !vfscore.FSEntry { return impl.get(rel_path) } -pub fn (mut self NestedVFS) rename(old_path string, new_path string) !vfscore.FSEntry { +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)! @@ -154,7 +156,7 @@ pub fn (mut self NestedVFS) rename(old_path string, new_path string) !vfscore.FS return renamed_file } -pub fn (mut self NestedVFS) copy(src_path string, dst_path string) !vfscore.FSEntry { +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)! @@ -170,13 +172,13 @@ pub fn (mut self NestedVFS) copy(src_path string, dst_path string) !vfscore.FSEn return new_file } -pub fn (mut self NestedVFS) move(src_path string, dst_path string) !vfscore.FSEntry { +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) !vfscore.FSEntry { +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) } @@ -194,10 +196,10 @@ pub fn (mut self NestedVFS) destroy() ! { // Special entry types for the nested VFS struct RootEntry { - metadata vfscore.Metadata + metadata vfs.Metadata } -fn (e &RootEntry) get_metadata() vfscore.Metadata { +fn (e &RootEntry) get_metadata() vfs.Metadata { return e.metadata } @@ -222,11 +224,11 @@ pub fn (self &RootEntry) is_symlink() bool { pub struct MountEntry { pub mut: - metadata vfscore.Metadata - impl vfscore.VFSImplementation + metadata vfs.Metadata + impl vfs.VFSImplementation } -fn (e &MountEntry) get_metadata() vfscore.Metadata { +fn (e &MountEntry) get_metadata() vfs.Metadata { return e.metadata } diff --git a/lib/vfs/vfsourdb/readme.md b/lib/vfs/vfsourdb/readme.md deleted file mode 100644 index 7fa2f484..00000000 --- a/lib/vfs/vfsourdb/readme.md +++ /dev/null @@ -1,8 +0,0 @@ -# VFS Overlay of OURDb - -use the ourdb_fs implementation underneith which speaks with the ourdb - -this is basically a filesystem interface for storing files into an ourdb. - - - diff --git a/lib/vfs/vfsourdb/vfsourdb.v b/lib/vfs/vfsourdb/vfsourdb.v deleted file mode 100644 index 67a75342..00000000 --- a/lib/vfs/vfsourdb/vfsourdb.v +++ /dev/null @@ -1,410 +0,0 @@ -module vfsourdb - -import freeflowuniverse.herolib.vfs.vfscore -import freeflowuniverse.herolib.vfs.ourdb_fs -import os -import time - -// OurDBVFS represents a VFS that uses OurDB as the underlying storage -pub struct OurDBVFS { -mut: - core &ourdb_fs.OurDBFS -} - -// new creates a new OurDBVFS instance -pub fn new(data_dir string, metadata_dir string) !&OurDBVFS { - mut core := ourdb_fs.new( - data_dir: data_dir - metadata_dir: metadata_dir - incremental_mode: false - )! - - return &OurDBVFS{ - core: core - } -} - -// Implementation of VFSImplementation interface -pub fn (mut self OurDBVFS) root_get() !vfscore.FSEntry { - mut root := self.core.get_root()! - return convert_to_vfscore_entry(root) -} - -pub fn (mut self OurDBVFS) file_create(path string) !vfscore.FSEntry { - // Get parent directory - parent_path := os.dir(path) - file_name := os.base(path) - - mut parent_dir := self.get_directory(parent_path)! - mut file := parent_dir.touch(file_name)! - return convert_to_vfscore_entry(file) -} - -pub fn (mut self OurDBVFS) file_read(path string) ![]u8 { - mut entry := self.get_entry(path)! - if mut entry is ourdb_fs.File { - content := entry.read()! - return content.bytes() - } - return error('Not a file: ${path}') -} - -pub fn (mut self OurDBVFS) file_write(path string, data []u8) ! { - mut entry := self.get_entry(path)! - if mut entry is ourdb_fs.File { - entry.write(data.bytestr())! - } else { - return error('Not a file: ${path}') - } -} - -pub fn (mut self OurDBVFS) delete(path string) ! { - println('Not implemented') -} - -pub fn (mut self OurDBVFS) link_delete(path string) ! { - parent_path := os.dir(path) - file_name := os.base(path) - - mut parent_dir := self.get_directory(parent_path)! - parent_dir.rm(file_name)! -} - -pub fn (mut self OurDBVFS) file_delete(path string) ! { - parent_path := os.dir(path) - file_name := os.base(path) - - mut parent_dir := self.get_directory(parent_path)! - parent_dir.rm(file_name)! -} - -pub fn (mut self OurDBVFS) dir_create(path string) !vfscore.FSEntry { - parent_path := os.dir(path) - dir_name := os.base(path) - - mut parent_dir := self.get_directory(parent_path)! - mut new_dir := parent_dir.mkdir(dir_name)! - return convert_to_vfscore_entry(new_dir) -} - -pub fn (mut self OurDBVFS) dir_list(path string) ![]vfscore.FSEntry { - mut dir := self.get_directory(path)! - mut entries := dir.children(false)! - mut result := []vfscore.FSEntry{} - - for entry in entries { - result << convert_to_vfscore_entry(entry) - } - - return result -} - -pub fn (mut self OurDBVFS) dir_delete(path string) ! { - parent_path := os.dir(path) - dir_name := os.base(path) - - mut parent_dir := self.get_directory(parent_path)! - parent_dir.rm(dir_name)! -} - -pub fn (mut self OurDBVFS) exists(path_ string) bool { - path := if !path_.starts_with('/') { - '/${path_}' - } else { - path_ - } - if path == '/' { - return true - } - self.get_entry(path) or { return false } - return true -} - -pub fn (mut self OurDBVFS) get(path string) !vfscore.FSEntry { - mut entry := self.get_entry(path)! - return convert_to_vfscore_entry(entry) -} - -pub fn (mut self OurDBVFS) rename(old_path string, new_path string) !vfscore.FSEntry { - src_parent_path := os.dir(old_path) - src_name := os.base(old_path) - dst_name := os.base(new_path) - - mut src_parent_dir := self.get_directory(src_parent_path)! - renamed_dir := src_parent_dir.rename(src_name, dst_name)! - return convert_to_vfscore_entry(renamed_dir) -} - -pub fn (mut self OurDBVFS) copy(src_path string, dst_path string) !vfscore.FSEntry { - src_parent_path := os.dir(src_path) - dst_parent_path := os.dir(dst_path) - - if !self.exists(src_parent_path) { - return error('${src_parent_path} does not exist') - } - - if !self.exists(dst_parent_path) { - return error('${dst_parent_path} does not exist') - } - - src_name := os.base(src_path) - dst_name := os.base(dst_path) - - mut src_parent_dir := self.get_directory(src_parent_path)! - mut dst_parent_dir := self.get_directory(dst_parent_path)! - - if src_parent_dir == dst_parent_dir && src_name == dst_name { - return error('Moving to the same path not supported') - } - - copied_dir := src_parent_dir.copy( - src_entry_name: src_name - dst_entry_name: dst_name - dst_parent_dir: dst_parent_dir - )! - - return convert_to_vfscore_entry(copied_dir) -} - -pub fn (mut self OurDBVFS) move(src_path string, dst_path string) !vfscore.FSEntry { - src_parent_path := os.dir(src_path) - dst_parent_path := os.dir(dst_path) - - if !self.exists(src_parent_path) { - return error('${src_parent_path} does not exist') - } - - if !self.exists(dst_parent_path) { - return error('${dst_parent_path} does not exist') - } - - src_name := os.base(src_path) - dst_name := os.base(dst_path) - - mut src_parent_dir := self.get_directory(src_parent_path)! - mut dst_parent_dir := self.get_directory(dst_parent_path)! - - if src_parent_dir == dst_parent_dir && src_name == dst_name { - return error('Moving to the same path not supported') - } - - moved_dir := src_parent_dir.move( - src_entry_name: src_name - dst_entry_name: dst_name - dst_parent_dir: dst_parent_dir - )! - - return convert_to_vfscore_entry(moved_dir) -} - -pub fn (mut self OurDBVFS) link_create(target_path string, link_path string) !vfscore.FSEntry { - parent_path := os.dir(link_path) - link_name := os.base(link_path) - - mut parent_dir := self.get_directory(parent_path)! - - mut symlink := ourdb_fs.Symlink{ - metadata: ourdb_fs.Metadata{ - id: self.core.get_next_id() - name: link_name - file_type: .symlink - created_at: time.now().unix() - modified_at: time.now().unix() - accessed_at: time.now().unix() - mode: 0o777 - owner: 'user' - group: 'user' - } - target: target_path - parent_id: parent_dir.metadata.id - myvfs: self.core - } - - parent_dir.add_symlink(mut symlink)! - return convert_to_vfscore_entry(symlink) -} - -pub fn (mut self OurDBVFS) link_read(path string) !string { - mut entry := self.get_entry(path)! - if mut entry is ourdb_fs.Symlink { - return entry.get_target()! - } - return error('Not a symlink: ${path}') -} - -pub fn (mut self OurDBVFS) destroy() ! { - // Nothing to do as the core VFS handles cleanup -} - -fn (mut self OurDBVFS) get_entry(path string) !ourdb_fs.FSEntry { - if path == '/' || path == '' || path == '.' { - return ourdb_fs.FSEntry(self.core.get_root()!) - } - - mut current := *self.core.get_root()! - parts := path.trim_left('/').split('/') - - for i := 0; i < parts.len; i++ { - mut found := false - children := current.children(false)! - - for child in children { - if child.metadata.name == parts[i] { - match child { - ourdb_fs.Directory { - current = child - found = true - break - } - else { - if i == parts.len - 1 { - return child - } else { - return error('Not a directory: ${parts[i]}') - } - } - } - } - } - - if !found { - return error('Path not found: ${path}') - } - } - - return ourdb_fs.FSEntry(current) -} - -fn (mut self OurDBVFS) get_directory(path string) !&ourdb_fs.Directory { - mut entry := self.get_entry(path)! - if mut entry is ourdb_fs.Directory { - return &entry - } - return error('Not a directory: ${path}') -} - -fn convert_to_vfscore_entry(entry ourdb_fs.FSEntry) vfscore.FSEntry { - match entry { - ourdb_fs.Directory { - return &DirectoryEntry{ - metadata: convert_metadata(entry.metadata) - path: entry.metadata.name - } - } - ourdb_fs.File { - return &FileEntry{ - metadata: convert_metadata(entry.metadata) - path: entry.metadata.name - } - } - ourdb_fs.Symlink { - return &SymlinkEntry{ - metadata: convert_metadata(entry.metadata) - path: entry.metadata.name - target: entry.target - } - } - } -} - -fn convert_metadata(meta ourdb_fs.Metadata) vfscore.Metadata { - return vfscore.Metadata{ - id: meta.id - name: meta.name - file_type: match meta.file_type { - .file { vfscore.FileType.file } - .directory { vfscore.FileType.directory } - .symlink { vfscore.FileType.symlink } - } - size: meta.size - created_at: meta.created_at - modified_at: meta.modified_at - accessed_at: meta.accessed_at - } -} - -// Entry type implementations -struct DirectoryEntry { - metadata vfscore.Metadata - path string -} - -fn (e &DirectoryEntry) get_metadata() vfscore.Metadata { - return e.metadata -} - -fn (e &DirectoryEntry) get_path() string { - return e.path -} - -// is_dir returns true if the entry is a directory -pub fn (self &DirectoryEntry) is_dir() bool { - return self.metadata.file_type == .directory -} - -// is_file returns true if the entry is a file -pub fn (self &DirectoryEntry) is_file() bool { - return self.metadata.file_type == .file -} - -// is_symlink returns true if the entry is a symlink -pub fn (self &DirectoryEntry) is_symlink() bool { - return self.metadata.file_type == .symlink -} - -struct FileEntry { - metadata vfscore.Metadata - path string -} - -fn (e &FileEntry) get_metadata() vfscore.Metadata { - return e.metadata -} - -fn (e &FileEntry) get_path() string { - return e.path -} - -// is_dir returns true if the entry is a directory -pub fn (self &FileEntry) is_dir() bool { - return self.metadata.file_type == .directory -} - -// is_file returns true if the entry is a file -pub fn (self &FileEntry) is_file() bool { - return self.metadata.file_type == .file -} - -// is_symlink returns true if the entry is a symlink -pub fn (self &FileEntry) is_symlink() bool { - return self.metadata.file_type == .symlink -} - -struct SymlinkEntry { - metadata vfscore.Metadata - path string - target string -} - -fn (e &SymlinkEntry) get_metadata() vfscore.Metadata { - return e.metadata -} - -fn (e &SymlinkEntry) get_path() string { - return e.path -} - -// is_dir returns true if the entry is a directory -pub fn (self &SymlinkEntry) is_dir() bool { - return self.metadata.file_type == .directory -} - -// is_file returns true if the entry is a file -pub fn (self &SymlinkEntry) is_file() bool { - return self.metadata.file_type == .file -} - -// is_symlink returns true if the entry is a symlink -pub fn (self &SymlinkEntry) is_symlink() bool { - return self.metadata.file_type == .symlink -} diff --git a/lib/vfs/webdav/app.v b/lib/vfs/webdav/app.v index d0425057..527073c6 100644 --- a/lib/vfs/webdav/app.v +++ b/lib/vfs/webdav/app.v @@ -2,17 +2,17 @@ module webdav import veb import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.vfs.vfscore +import freeflowuniverse.herolib.vfs @[heap] pub struct App { veb.Middleware[Context] pub mut: lock_manager LockManager - user_db map[string]string @[required] - vfs vfscore.VFSImplementation + user_db map[string]string @[required] + vfs vfs.VFSImplementation } - + pub struct Context { veb.Context } @@ -20,28 +20,27 @@ pub struct Context { @[params] pub struct AppArgs { pub mut: - user_db map[string]string @[required] - vfs vfscore.VFSImplementation + user_db map[string]string @[required] + vfs vfs.VFSImplementation } pub fn new_app(args AppArgs) !&App { mut app := &App{ - user_db: args.user_db.clone() - vfs: args.vfs + user_db: args.user_db.clone() + vfs: args.vfs } - // register middlewares for all routes - app.use(handler: app.auth_middleware) - app.use(handler: logging_middleware) + // register middlewares for all routes + app.use(handler: app.auth_middleware) + app.use(handler: logging_middleware) return app } - @[params] pub struct RunParams { pub mut: - port int = 8088 + port int = 8088 background bool } @@ -52,4 +51,4 @@ pub fn (mut app App) run(params RunParams) { } else { veb.run[App, Context](mut app, params.port) } -} \ No newline at end of file +} diff --git a/lib/vfs/webdav/logic_test.v b/lib/vfs/webdav/logic_test.v index 8dec0a5b..852ad38f 100644 --- a/lib/vfs/webdav/logic_test.v +++ b/lib/vfs/webdav/logic_test.v @@ -1,15 +1,15 @@ import freeflowuniverse.herolib.vfs.webdav import freeflowuniverse.herolib.vfs.vfsnested -import freeflowuniverse.herolib.vfs.vfscore -import freeflowuniverse.herolib.vfs.vfsourdb +import freeflowuniverse.herolib.vfs +import freeflowuniverse.herolib.vfs.vfs_db import os fn test_logic() ! { println('Testing OurDB VFS Logic to WebDAV Server...') // Create test directories - test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_test_data') - test_meta_dir := os.join_path(os.temp_dir(), 'vfsourdb_test_meta') + test_data_dir := os.join_path(os.temp_dir(), 'vfs_db_test_data') + test_meta_dir := os.join_path(os.temp_dir(), 'vfs_db_test_meta') os.mkdir_all(test_data_dir)! os.mkdir_all(test_meta_dir)! @@ -20,7 +20,7 @@ fn test_logic() ! { } // Create VFS instance; lower level VFS Implementations that use OurDB - mut vfs1 := vfsourdb.new(test_data_dir, test_meta_dir)! + mut vfs1 := vfs_db.new(test_data_dir, test_meta_dir)! mut high_level_vfs := vfsnested.new() @@ -31,8 +31,6 @@ fn test_logic() ! { entries := high_level_vfs.dir_list('/')! assert entries.len == 1 // Data directory - panic('entries: ${entries[0]}') - // // Check if dir is existing // assert high_level_vfs.exists('/') == true diff --git a/lib/vfs/webdav/methods.v b/lib/vfs/webdav/methods.v index 1ed44cf9..5aff9ca2 100644 --- a/lib/vfs/webdav/methods.v +++ b/lib/vfs/webdav/methods.v @@ -9,9 +9,9 @@ import veb @['/:path...'; options] pub fn (app &App) options(mut ctx Context, path string) veb.Result { ctx.res.set_status(.ok) - ctx.res.header.add_custom('dav', '1,2') or {return ctx.server_error(err.msg())} + ctx.res.header.add_custom('dav', '1,2') or { return ctx.server_error(err.msg()) } ctx.res.header.add(.allow, 'OPTIONS, PROPFIND, MKCOL, GET, HEAD, POST, PUT, DELETE, COPY, MOVE') - ctx.res.header.add_custom('MS-Author-Via', 'DAV') or {return ctx.server_error(err.msg())} + ctx.res.header.add_custom('MS-Author-Via', 'DAV') or { return ctx.server_error(err.msg()) } ctx.res.header.add(.access_control_allow_origin, '*') ctx.res.header.add(.access_control_allow_methods, 'OPTIONS, PROPFIND, MKCOL, GET, HEAD, POST, PUT, DELETE, COPY, MOVE') ctx.res.header.add(.access_control_allow_headers, 'Authorization, Content-Type') @@ -22,7 +22,7 @@ pub fn (app &App) options(mut ctx Context, path string) veb.Result { @['/:path...'; lock] pub fn (mut app App) lock_handler(mut ctx Context, path string) veb.Result { resource := ctx.req.url - owner := ctx.get_custom_header('owner') or {return ctx.server_error(err.msg())} + owner := ctx.get_custom_header('owner') or { return ctx.server_error(err.msg()) } if owner.len == 0 { ctx.res.set_status(.bad_request) return ctx.text('Owner header is required.') @@ -36,14 +36,14 @@ pub fn (mut app App) lock_handler(mut ctx Context, path string) veb.Result { } ctx.res.set_status(.ok) - ctx.res.header.add_custom('Lock-Token', token) or {return ctx.server_error(err.msg())} + ctx.res.header.add_custom('Lock-Token', token) or { return ctx.server_error(err.msg()) } return ctx.text('Lock granted with token: ${token}') } @['/:path...'; unlock] pub fn (mut app App) unlock_handler(mut ctx Context, path string) veb.Result { resource := ctx.req.url - token := ctx.get_custom_header('Lock-Token') or {return ctx.server_error(err.msg())} + token := ctx.get_custom_header('Lock-Token') or { return ctx.server_error(err.msg()) } if token.len == 0 { console.print_stderr('Unlock failed: `Lock-Token` header required.') ctx.res.set_status(.bad_request) @@ -96,23 +96,23 @@ pub fn (mut app App) exists(mut ctx Context, path string) veb.Result { // Add necessary WebDAV headers ctx.res.header.add(.authorization, 'Basic') // Indicates Basic auth usage ctx.res.header.add_custom('DAV', '1, 2') or { - return ctx.server_error('Failed to set DAV header: $err') + return ctx.server_error('Failed to set DAV header: ${err}') } ctx.res.header.add_custom('Etag', 'abc123xyz') or { - return ctx.server_error('Failed to set ETag header: $err') + return ctx.server_error('Failed to set ETag header: ${err}') } ctx.res.header.add(.content_length, '0') // HEAD request, so no body ctx.res.header.add(.date, time.now().as_utc().format()) // Correct UTC date format // ctx.res.header.add(.content_type, 'application/xml') // XML is common for WebDAV metadata ctx.res.header.add_custom('Allow', 'OPTIONS, GET, HEAD, PROPFIND, PROPPATCH, MKCOL, PUT, DELETE, COPY, MOVE, LOCK, UNLOCK') or { - return ctx.server_error('Failed to set Allow header: $err') + return ctx.server_error('Failed to set Allow header: ${err}') } ctx.res.header.add(.accept_ranges, 'bytes') // Allows range-based file downloads ctx.res.header.add_custom('Cache-Control', 'no-cache, no-store, must-revalidate') or { - return ctx.server_error('Failed to set Cache-Control header: $err') + return ctx.server_error('Failed to set Cache-Control header: ${err}') } ctx.res.header.add_custom('Last-Modified', time.now().as_utc().format()) or { - return ctx.server_error('Failed to set Last-Modified header: $err') + return ctx.server_error('Failed to set Last-Modified header: ${err}') } ctx.res.set_status(.ok) ctx.res.set_version(.v1_1) @@ -217,7 +217,7 @@ fn (mut app App) propfind(mut ctx Context, path string) veb.Result { if !app.vfs.exists(path) { return ctx.not_found() } - depth := ctx.req.header.get_custom('Depth') or {'0'}.int() + depth := ctx.req.header.get_custom('Depth') or { '0' }.int() responses := app.get_responses(path, depth) or { console.print_stderr('failed to get responses: ${err}') @@ -251,11 +251,9 @@ fn (mut app App) create_or_update(mut ctx Context, path string) veb.Result { return ctx.server_error('failed to get FS Entry ${path}: ${err.msg()}') } } - + data := ctx.req.data.bytes() - app.vfs.file_write(path, data) or { - return ctx.server_error(err.msg()) - } + app.vfs.file_write(path, data) or { return ctx.server_error(err.msg()) } return ctx.ok('HTTP 200: Successfully saved file: ${path}') } diff --git a/lib/vfs/webdav/prop.v b/lib/vfs/webdav/prop.v index ee98f541..88becba4 100644 --- a/lib/vfs/webdav/prop.v +++ b/lib/vfs/webdav/prop.v @@ -1,16 +1,18 @@ module webdav import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.vfs.vfscore +import freeflowuniverse.herolib.vfs import encoding.xml import os import time import veb -fn generate_response_element(entry vfscore.FSEntry) !xml.XMLNode { +fn generate_response_element(entry vfs.FSEntry) !xml.XMLNode { path := if entry.is_dir() && entry.get_path() != '/' { '${entry.get_path()}/' - } else { entry.get_path() } + } else { + entry.get_path() + } return xml.XMLNode{ name: 'D:response' @@ -18,8 +20,8 @@ fn generate_response_element(entry vfscore.FSEntry) !xml.XMLNode { xml.XMLNode{ name: 'D:href' children: [path] - }, - generate_propstat_element(entry)! + }, + generate_propstat_element(entry)!, ] } } @@ -34,7 +36,7 @@ const xml_500_status = xml.XMLNode{ children: ['HTTP/1.1 500 Internal Server Error'] } -fn generate_propstat_element(entry vfscore.FSEntry) !xml.XMLNode { +fn generate_propstat_element(entry vfs.FSEntry) !xml.XMLNode { prop := generate_prop_element(entry) or { // TODO: status should be according to returned error return xml.XMLNode{ @@ -49,7 +51,7 @@ fn generate_propstat_element(entry vfscore.FSEntry) !xml.XMLNode { } } -fn generate_prop_element(entry vfscore.FSEntry) !xml.XMLNode { +fn generate_prop_element(entry vfs.FSEntry) !xml.XMLNode { metadata := entry.get_metadata() display_name := xml.XMLNode{ @@ -135,16 +137,16 @@ fn format_iso8601(t time.Time) string { fn (mut app App) get_responses(path string, depth int) ![]xml.XMLNodeContents { mut responses := []xml.XMLNodeContents{} - + entry := app.vfs.get(path)! responses << generate_response_element(entry)! if depth == 0 { return responses } - entries := app.vfs.dir_list(path) or {return responses} + entries := app.vfs.dir_list(path) or { return responses } for e in entries { responses << generate_response_element(e)! } return responses -} \ No newline at end of file +} diff --git a/lib/vfs/webdav/server_test.v b/lib/vfs/webdav/server_test.v index c81f10d6..813c0ee2 100644 --- a/lib/vfs/webdav/server_test.v +++ b/lib/vfs/webdav/server_test.v @@ -8,11 +8,11 @@ import rand fn test_run() { mut app := new_app( - user_db: { + user_db: { 'mario': '123' } )! - app.run() + spawn app.run() } // fn test_get() {