fix and refactor vfs modules to merge ourdb implementations into generic vfs_db and separate vfs_local

This commit is contained in:
timurgordon
2025-02-27 11:12:17 +03:00
parent 972bb9f755
commit fe1becabaf
40 changed files with 1474 additions and 2152 deletions

View File

@@ -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() !
}
}
// 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
}

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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)!
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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}') }
}

View File

@@ -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
}
}

View File

@@ -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()

43
lib/vfs/vfs_db/factory.v Normal file
View File

@@ -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
}

27
lib/vfs/vfs_db/metadata.v Normal file
View File

@@ -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
)
}

View File

@@ -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
}

View File

@@ -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
)
}

View File

@@ -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
}

View File

@@ -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
}

36
lib/vfs/vfs_db/print.v Normal file
View File

@@ -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
}

167
lib/vfs/vfs_db/readme.md Normal file
View File

@@ -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

83
lib/vfs/vfs_db/vfs.v Normal file
View File

@@ -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}')
}
}
}
}

View File

@@ -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)!
}

View File

@@ -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}')
}

View File

@@ -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
}

View File

@@ -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)}')

View File

@@ -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

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
module vfscore
module vfs_local
import os

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {}
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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}')
}

View File

@@ -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
}
}

View File

@@ -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() {