feat: Move the vfs dirs to the herolib

This commit is contained in:
Mahmoud Emad
2025-02-16 14:56:29 +02:00
parent c6ff7e7ba5
commit 2599fa6859
19 changed files with 2097 additions and 0 deletions

38
lib/vfs/ourdb_fs/common.v Normal file
View File

@@ -0,0 +1,38 @@
module ourdb_fs
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
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
}
// 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)
}

10
lib/vfs/ourdb_fs/data.v Normal file
View File

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,290 @@
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 @[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: u32(time.now().unix())
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: u32(time.now().unix()) // Use timestamp as 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{
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
new_file.metadata.id = 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)!
}
// 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 {}
}
// add_symlink adds an existing symlink to this directory
pub fn (mut dir Directory) add_symlink(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)!
}

202
lib/vfs/ourdb_fs/encoder.v Normal file
View File

@@ -0,0 +1,202 @@
module ourdb_fs
import freeflowuniverse.crystallib.data.encoder
// encode_metadata encodes the common metadata structure
fn encode_metadata(mut e encoder.Encoder, m Metadata) {
e.add_u32(m.id)
e.add_string(m.name)
e.add_u8(u8(m.file_type)) // FileType enum as u8
e.add_u64(m.size)
e.add_i64(m.created_at)
e.add_i64(m.modified_at)
e.add_i64(m.accessed_at)
e.add_u32(m.mode)
e.add_string(m.owner)
e.add_string(m.group)
}
// decode_metadata decodes the common metadata structure
fn decode_metadata(mut d encoder.Decoder) Metadata {
id := d.get_u32()
name := d.get_string()
file_type_byte := d.get_u8()
size := d.get_u64()
created_at := d.get_i64()
modified_at := d.get_i64()
accessed_at := d.get_i64()
mode := d.get_u32()
owner := d.get_string()
group := d.get_string()
return Metadata{
id: id
name: name
file_type: unsafe { FileType(file_type_byte) }
size: size
created_at: created_at
modified_at: modified_at
accessed_at: accessed_at
mode: mode
owner: owner
group: group
}
}
// Directory encoding/decoding
// encode encodes a Directory to binary format
pub fn (dir Directory) encode() []u8 {
mut e := encoder.new()
e.add_u8(1) // version byte
e.add_u8(u8(FileType.directory)) // type byte
// Encode metadata
encode_metadata(mut e, dir.metadata)
// Encode parent_id
e.add_u32(dir.parent_id)
// Encode children IDs
e.add_u16(u16(dir.children.len))
for child_id in dir.children {
e.add_u32(child_id)
}
return e.data
}
// decode_directory decodes a binary format back to Directory
pub fn decode_directory(data []u8) !Directory {
mut d := encoder.decoder_new(data)
version := d.get_u8()
if version != 1 {
return error('Unsupported version ${version}')
}
type_byte := d.get_u8()
if type_byte != u8(FileType.directory) {
return error('Invalid type byte for directory')
}
// Decode metadata
metadata := decode_metadata(mut d)
// Decode parent_id
parent_id := d.get_u32()
// Decode children IDs
children_count := int(d.get_u16())
mut children := []u32{cap: children_count}
for _ in 0 .. children_count {
children << d.get_u32()
}
return Directory{
metadata: metadata
parent_id: parent_id
children: children
myvfs: unsafe { nil } // Will be set by caller
}
}
// File encoding/decoding
// encode encodes a File to binary format
pub fn (f File) encode() []u8 {
mut e := encoder.new()
e.add_u8(1) // version byte
e.add_u8(u8(FileType.file)) // type byte
// Encode metadata
encode_metadata(mut e, f.metadata)
// Encode parent_id
e.add_u32(f.parent_id)
// Encode file data
e.add_string(f.data)
return e.data
}
// decode_file decodes a binary format back to File
pub fn decode_file(data []u8) !File {
mut d := encoder.decoder_new(data)
version := d.get_u8()
if version != 1 {
return error('Unsupported version ${version}')
}
type_byte := d.get_u8()
if type_byte != u8(FileType.file) {
return error('Invalid type byte for file')
}
// Decode metadata
metadata := decode_metadata(mut d)
// Decode parent_id
parent_id := d.get_u32()
// Decode file data
data_content := d.get_string()
return File{
metadata: metadata
parent_id: parent_id
data: data_content
myvfs: unsafe { nil } // Will be set by caller
}
}
// Symlink encoding/decoding
// encode encodes a Symlink to binary format
pub fn (sl Symlink) encode() []u8 {
mut e := encoder.new()
e.add_u8(1) // version byte
e.add_u8(u8(FileType.symlink)) // type byte
// Encode metadata
encode_metadata(mut e, sl.metadata)
// Encode parent_id
e.add_u32(sl.parent_id)
// Encode target path
e.add_string(sl.target)
return e.data
}
// decode_symlink decodes a binary format back to Symlink
pub fn decode_symlink(data []u8) !Symlink {
mut d := encoder.decoder_new(data)
version := d.get_u8()
if version != 1 {
return error('Unsupported version ${version}')
}
type_byte := d.get_u8()
if type_byte != u8(FileType.symlink) {
return error('Invalid type byte for symlink')
}
// Decode metadata
metadata := decode_metadata(mut d)
// Decode parent_id
parent_id := d.get_u32()
// Decode target path
target := d.get_string()
return Symlink{
metadata: metadata
parent_id: parent_id
target: target
myvfs: unsafe { nil } // Will be set by caller
}
}

View File

@@ -0,0 +1,38 @@
module ourdb_fs
import os
import freeflowuniverse.crystallib.data.ourdb
// 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
}
// Factory method for creating a new OurDBFS instance
pub fn new(params VFSParams) !&OurDBFS {
if !os.exists(params.data_dir) {
os.mkdir(params.data_dir) or { return error('Failed to create data directory: ${err}') }
}
if !os.exists(params.metadata_dir) {
os.mkdir(params.metadata_dir) or {
return error('Failed to create metadata directory: ${err}')
}
}
mut db_meta := ourdb.new(path: '${params.metadata_dir}/ourdb_fs.db_meta')! // TODO: doesn't seem to be good names
mut db_data := ourdb.new(path: '${params.data_dir}/vfs_metadata.db_meta')!
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
}

31
lib/vfs/ourdb_fs/file.v Normal file
View File

@@ -0,0 +1,31 @@
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 @[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()!
}
// read returns the file's content
pub fn (mut f File) read() !string {
return f.data
}

159
lib/vfs/ourdb_fs/readme.md Normal file
View File

@@ -0,0 +1,159 @@
# 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

@@ -0,0 +1,35 @@
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 @[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
}

97
lib/vfs/ourdb_fs/vfs.v Normal file
View File

@@ -0,0 +1,97 @@
module ourdb_fs
import freeflowuniverse.crystallib.data.ourdb
// 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 // Database instance for persistent storage
db_meta &ourdb.OurDB // Database instance for metadata storage
}
// 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
}
// Save new root to DB
mut myroot := Directory{
metadata: Metadata{}
parent_id: 0
myvfs: &fs
}
myroot.save()!
return &myroot
}
// load_entry loads an entry from the database by ID and sets up parent references
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(entry.metadata.id, encoded) or {
return error('Failed to save directory on id:${entry.metadata.id}: ${err}')
}
}
File {
encoded := entry.encode()
return fs.db_meta.set(entry.metadata.id, encoded) or {
return error('Failed to save file on id:${entry.metadata.id}: ${err}')
}
}
Symlink {
encoded := entry.encode()
return fs.db_meta.set(entry.metadata.id, 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}') }
}

127
lib/vfs/vfscore/README.md Normal file
View File

@@ -0,0 +1,127 @@
# Virtual File System (vfscore) Module
> is the interface, should not have an implementation
This module provides a pluggable virtual filesystem interface with one default implementation done for local.
1. Local filesystem implementation (direct passthrough to OS filesystem)
2. OurDB-based implementation (stores files and metadata in OurDB)
## Interface
The vfscore interface defines common operations for filesystem manipulation using a consistent naming pattern of `$subject_$method`:
### File Operations
- `file_create(path string) !FSEntry`
- `file_read(path string) ![]u8`
- `file_write(path string, data []u8) !`
- `file_delete(path string) !`
### Directory Operations
- `dir_create(path string) !FSEntry`
- `dir_list(path string) ![]FSEntry`
- `dir_delete(path string) !`
### Entry Operations (Common)
- `entry_exists(path string) bool`
- `entry_get(path string) !FSEntry`
- `entry_rename(old_path string, new_path string) !`
- `entry_copy(src_path string, dst_path string) !`
### Symlink Operations
- `link_create(target_path string, link_path string) !FSEntry`
- `link_read(path string) !string`
## Usage
```v
import vfscore
fn main() ! {
// Create a local filesystem implementation
mut local_vfs := vfscore.new_vfs('local', 'my_local_fs')!
// Create and write to a file
local_vfs.file_create('test.txt')!
local_vfs.file_write('test.txt', 'Hello, World!'.bytes())!
// Read file contents
content := local_vfs.file_read('test.txt')!
println(content.bytestr())
// Create and list directory
local_vfs.dir_create('subdir')!
entries := local_vfs.dir_list('subdir')!
// Create symlink
local_vfs.link_create('test.txt', 'test_link.txt')!
// Clean up
local_vfs.file_delete('test.txt')!
local_vfs.dir_delete('subdir')!
}
```
## Implementations
### 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
### OurDB Filesystem (ourdb_fs)
The ourdb_fs implementation stores files and metadata in OurDB, providing a database-backed virtual filesystem.
Features:
- Persistent storage in OurDB
- Transactional operations
- Structured metadata storage
- Suitable for embedded systems or custom storage requirements
## Adding New Implementations
To create a new vfscore implementation:
1. Implement the `VFSImplementation` interface
2. Add your implementation to the `new_vfs` factory function
3. Ensure all required operations are implemented following the `$subject_$method` naming pattern
4. Add appropriate error handling and validation
## Error Handling
All operations that can fail return a `!` result type. Handle potential errors appropriately:
```v
// Example error handling
if file := vfscore.file_create('test.txt') {
// Success case
println('File created successfully')
} else {
// Error case
println('Failed to create file: ${err}')
}
```
## Testing
The module includes comprehensive tests for both implementations. Run tests using:
```bash
v test vfscore/
```
## Contributing
To add a new vfscore implementation:
1. Create a new file in the `vfscore` directory (e.g., `my_impl.v`)
2. Implement the `VFSImplementation` interface following the `$subject_$method` naming pattern
3. Add your implementation to `new_vfs()` in `interface.v`
4. Add tests to verify your implementation
5. Update documentation to include your implementation

View File

@@ -0,0 +1,58 @@
module vfscore
// 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:
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
}
// FSEntry represents a filesystem entry (file, directory, or symlink)
pub interface FSEntry {
get_metadata() Metadata
get_path() string
}
// VFSImplementation defines the interface that all vfscore implementations must follow
pub interface VFSImplementation {
mut:
// Basic operations
root_get() !FSEntry
// File operations
file_create(path string) !FSEntry
file_read(path string) ![]u8
file_write(path string, data []u8) !
file_delete(path string) !
// Directory operations
dir_create(path string) !FSEntry
dir_list(path string) ![]FSEntry
dir_delete(path string) !
// Common operations
exists(path string) bool
get(path string) !FSEntry
rename(old_path string, new_path string) !
copy(src_path string, dst_path string) !
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() !
}

308
lib/vfs/vfscore/local.v Normal file
View File

@@ -0,0 +1,308 @@
module vfscore
import os
// LocalFSEntry implements FSEntry for local filesystem
struct LocalFSEntry {
mut:
path string
metadata Metadata
}
fn (e LocalFSEntry) get_metadata() Metadata {
return e.metadata
}
fn (e LocalFSEntry) get_path() string {
return e.path
}
// LocalVFS implements 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 {
mut myvfs := LocalVFS{
root_path: root_path
}
myvfs.init()!
return myvfs
}
// Initialize the local vfscore with a root path
fn (mut myvfs LocalVFS) init() ! {
if !os.exists(myvfs.root_path) {
os.mkdir_all(myvfs.root_path) or {
return error('Failed to create root directory ${myvfs.root_path}: ${err}')
}
}
}
// Destroy the vfscore by removing all its contents
pub fn (mut myvfs LocalVFS) destroy() ! {
if !os.exists(myvfs.root_path) {
return error('vfscore root path does not exist: ${myvfs.root_path}')
}
os.rmdir_all(myvfs.root_path) or {
return error('Failed to destroy vfscore at ${myvfs.root_path}: ${err}')
}
myvfs.init()!
}
// Convert path to Metadata with improved security and information gathering
fn (myvfs LocalVFS) os_attr_to_metadata(path string) !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
if os.is_dir(path) {
file_type = .directory
} else if os.is_link(path) {
file_type = .symlink
}
return Metadata{
name: os.base(path)
file_type: file_type
size: u64(attr.size)
created_at: i64(attr.ctime) // Creation time from stat
modified_at: i64(attr.mtime) // Modification time from stat
accessed_at: i64(attr.atime) // Access time from stat
}
}
// Get absolute path from relative path
fn (myvfs LocalVFS) abs_path(path string) string {
return os.join_path(myvfs.root_path, path)
}
// Basic operations
pub fn (myvfs LocalVFS) root_get() !FSEntry {
if !os.exists(myvfs.root_path) {
return error('Root path does not exist: ${myvfs.root_path}')
}
metadata := myvfs.os_attr_to_metadata(myvfs.root_path) or {
return error('Failed to get root metadata: ${err}')
}
return LocalFSEntry{
path: ''
metadata: metadata
}
}
// File operations with improved error handling and TOCTOU protection
pub fn (myvfs LocalVFS) file_create(path string) !FSEntry {
abs_path := myvfs.abs_path(path)
if os.exists(abs_path) {
return error('File already exists: ${path}')
}
os.write_file(abs_path, '') or { return error('Failed to create file ${path}: ${err}') }
metadata := myvfs.os_attr_to_metadata(abs_path) or {
return error('Failed to get metadata: ${err}')
}
return LocalFSEntry{
path: path
metadata: metadata
}
}
pub fn (myvfs LocalVFS) file_read(path string) ![]u8 {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('File does not exist: ${path}')
}
if os.is_dir(abs_path) {
return error('Path is a directory: ${path}')
}
return os.read_bytes(abs_path) or { return error('Failed to read file ${path}: ${err}') }
}
pub fn (myvfs LocalVFS) file_write(path string, data []u8) ! {
abs_path := myvfs.abs_path(path)
if os.is_dir(abs_path) {
return error('Cannot write to directory: ${path}')
}
os.write_file(abs_path, data.bytestr()) or {
return error('Failed to write file ${path}: ${err}')
}
}
pub fn (myvfs LocalVFS) file_delete(path string) ! {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('File does not exist: ${path}')
}
if os.is_dir(abs_path) {
return error('Cannot delete directory using file_delete: ${path}')
}
os.rm(abs_path) or { return error('Failed to delete file ${path}: ${err}') }
}
// Directory operations with improved error handling
pub fn (myvfs LocalVFS) dir_create(path string) !FSEntry {
abs_path := myvfs.abs_path(path)
if os.exists(abs_path) {
return error('Path already exists: ${path}')
}
os.mkdir_all(abs_path) or { return error('Failed to create directory ${path}: ${err}') }
metadata := myvfs.os_attr_to_metadata(abs_path) or {
return error('Failed to get metadata: ${err}')
}
return LocalFSEntry{
path: path
metadata: metadata
}
}
pub fn (myvfs LocalVFS) dir_list(path string) ![]FSEntry {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('Directory does not exist: ${path}')
}
if !os.is_dir(abs_path) {
return error('Path is not a directory: ${path}')
}
entries := os.ls(abs_path) or { return error('Failed to list directory ${path}: ${err}') }
mut result := []FSEntry{cap: entries.len}
for entry in entries {
rel_path := os.join_path(path, entry)
abs_entry_path := os.join_path(abs_path, entry)
metadata := myvfs.os_attr_to_metadata(abs_entry_path) or { continue } // Skip entries we can't stat
result << LocalFSEntry{
path: rel_path
metadata: metadata
}
}
return result
}
pub fn (myvfs LocalVFS) dir_delete(path string) ! {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('Directory does not exist: ${path}')
}
if !os.is_dir(abs_path) {
return error('Path is not a directory: ${path}')
}
os.rmdir_all(abs_path) or { return error('Failed to delete directory ${path}: ${err}') }
}
// Common operations with improved error handling
pub fn (myvfs LocalVFS) exists(path string) bool {
// TODO: check is link if link the link can be broken but it stil exists
return os.exists(myvfs.abs_path(path))
}
pub fn (myvfs LocalVFS) get(path string) !FSEntry {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('Entry does not exist: ${path}')
}
metadata := myvfs.os_attr_to_metadata(abs_path) or {
return error('Failed to get metadata: ${err}')
}
return LocalFSEntry{
path: path
metadata: metadata
}
}
pub fn (myvfs LocalVFS) rename(old_path string, new_path string) ! {
abs_old := myvfs.abs_path(old_path)
abs_new := myvfs.abs_path(new_path)
if !os.exists(abs_old) {
return error('Source path does not exist: ${old_path}')
}
if os.exists(abs_new) {
return error('Destination path already exists: ${new_path}')
}
os.mv(abs_old, abs_new) or {
return error('Failed to rename ${old_path} to ${new_path}: ${err}')
}
}
pub fn (myvfs LocalVFS) copy(src_path string, dst_path string) ! {
abs_src := myvfs.abs_path(src_path)
abs_dst := myvfs.abs_path(dst_path)
if !os.exists(abs_src) {
return error('Source path does not exist: ${src_path}')
}
if os.exists(abs_dst) {
return error('Destination path already exists: ${dst_path}')
}
os.cp(abs_src, abs_dst) or { return error('Failed to copy ${src_path} to ${dst_path}: ${err}') }
}
// Generic delete operation that handles all types
pub fn (myvfs LocalVFS) delete(path string) ! {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('Path does not exist: ${path}')
}
if os.is_link(abs_path) {
myvfs.link_delete(path)!
} else if os.is_dir(abs_path) {
myvfs.dir_delete(path)!
} else {
myvfs.file_delete(path)!
}
}
// Symlink operations with improved handling
pub fn (myvfs LocalVFS) link_create(target_path string, link_path string) !FSEntry {
abs_target := myvfs.abs_path(target_path)
abs_link := myvfs.abs_path(link_path)
if !os.exists(abs_target) {
return error('Target path does not exist: ${target_path}')
}
if os.exists(abs_link) {
return error('Link path already exists: ${link_path}')
}
os.symlink(target_path, abs_link) or {
return error('Failed to create symlink from ${target_path} to ${link_path}: ${err}')
}
metadata := myvfs.os_attr_to_metadata(abs_link) or {
return error('Failed to get metadata: ${err}')
}
return LocalFSEntry{
path: link_path
metadata: metadata
}
}
pub fn (myvfs LocalVFS) link_read(path string) !string {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('Symlink does not exist: ${path}')
}
if !os.is_link(abs_path) {
return error('Path is not a symlink: ${path}')
}
real_path := os.real_path(abs_path)
return os.base(real_path)
}
pub fn (myvfs LocalVFS) link_delete(path string) ! {
abs_path := myvfs.abs_path(path)
if !os.exists(abs_path) {
return error('Symlink does not exist: ${path}')
}
if !os.is_link(abs_path) {
return error('Path is not a symlink: ${path}')
}
os.rm(abs_path) or { return error('Failed to delete symlink ${path}: ${err}') }
}

View File

@@ -0,0 +1,65 @@
module vfscore
import os
fn test_vfs_implementations() ! {
// Test local vfscore
mut local_vfs := new_local_vfs('/tmp/test_local_vfs')!
local_vfs.destroy()!
// Create and write to a file
local_vfs.file_create('test.txt')!
local_vfs.file_write('test.txt', 'Hello, World!'.bytes())!
// Read the file
content := local_vfs.file_read('test.txt')!
assert content.bytestr() == 'Hello, World!'
// Create a directory and list its contents
local_vfs.dir_create('subdir')!
local_vfs.file_create('subdir/file1.txt')!
local_vfs.file_write('subdir/file1.txt', 'File 1'.bytes())!
local_vfs.file_create('subdir/file2.txt')!
local_vfs.file_write('subdir/file2.txt', 'File 2'.bytes())!
entries := local_vfs.dir_list('subdir')!
assert entries.len == 2
// Test entry operations
assert local_vfs.exists('test.txt')
entry := local_vfs.get('test.txt')!
assert entry.get_metadata().name == 'test.txt'
// Test rename and copy
local_vfs.rename('test.txt', 'test2.txt')!
local_vfs.copy('test2.txt', 'test3.txt')!
// Verify test2.txt exists before creating symlink
if !local_vfs.exists('test2.txt') {
panic('test2.txt does not exist before symlink creation')
}
// Create and read symlink using relative paths
local_vfs.link_create('test2.txt', 'test_link.txt')!
// Verify symlink was created
if !local_vfs.exists('test_link.txt') {
panic('test_link.txt was not created')
}
// Read the symlink
link_target := local_vfs.link_read('test_link.txt')!
target_base := os.base(link_target)
if target_base != 'test2.txt' {
eprintln('Expected link target: test2.txt')
eprintln('Actual link target: ${target_base}')
panic('Symlink points to wrong target')
}
// Cleanup
local_vfs.delete('test2.txt')!
local_vfs.delete('subdir')!
local_vfs.delete('test_link.txt')!
os.rmdir('/tmp/test_local_vfs') or {}
}

View File

@@ -0,0 +1,84 @@
module vfsnested
import os
fn test_nested() ! {
println('Testing Nested VFS...')
// Create root directories for test VFS instances
os.mkdir_all('/tmp/test_nested_vfs/vfs1') or { panic(err) }
os.mkdir_all('/tmp/test_nested_vfs/vfs2') or { panic(err) }
os.mkdir_all('/tmp/test_nested_vfs/vfs3') or { panic(err) }
// Create VFS instances
mut vfs1 := 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) }
// Create nested VFS
mut nested_vfs := new()
// Add VFS instances at different paths
nested_vfs.add_vfs('/data', vfs1) or { panic(err) }
nested_vfs.add_vfs('/config', vfs2) or { panic(err) }
nested_vfs.add_vfs('/data/backup', vfs3) or { panic(err) } // Nested under /data
println('\nTesting file operations...')
// Create and write to files in different VFS instances
nested_vfs.file_create('/data/test.txt') or { panic(err) }
nested_vfs.file_write('/data/test.txt', 'Hello from VFS1'.bytes()) or { panic(err) }
nested_vfs.file_create('/config/settings.txt') or { panic(err) }
nested_vfs.file_write('/config/settings.txt', 'Hello from VFS2'.bytes()) or { panic(err) }
nested_vfs.file_create('/data/backup/archive.txt') or { panic(err) }
nested_vfs.file_write('/data/backup/archive.txt', 'Hello from VFS3'.bytes()) or { panic(err) }
// Read and verify file contents
data1 := nested_vfs.file_read('/data/test.txt') or { panic(err) }
println('Content from /data/test.txt: ${data1.bytestr()}')
data2 := nested_vfs.file_read('/config/settings.txt') or { panic(err) }
println('Content from /config/settings.txt: ${data2.bytestr()}')
data3 := nested_vfs.file_read('/data/backup/archive.txt') or { panic(err) }
println('Content from /data/backup/archive.txt: ${data3.bytestr()}')
println('\nTesting directory operations...')
// List root directory
println('Root directory contents:')
root_entries := nested_vfs.dir_list('/') or { panic(err) }
for entry in root_entries {
meta := entry.get_metadata()
println('- ${meta.name} (${meta.file_type})')
}
// Create and list directories
nested_vfs.dir_create('/data/subdir') or { panic(err) }
nested_vfs.file_create('/data/subdir/file.txt') or { panic(err) }
nested_vfs.file_write('/data/subdir/file.txt', 'Nested file content'.bytes()) or { panic(err) }
println('\nListing /data directory:')
data_entries := nested_vfs.dir_list('/data') or { panic(err) }
for entry in data_entries {
meta := entry.get_metadata()
println('- ${meta.name} (${meta.file_type})')
}
println('\nTesting cross-VFS operations...')
// Copy file between different VFS instances
nested_vfs.copy('/data/test.txt', '/config/test_copy.txt') or { panic(err) }
copy_data := nested_vfs.file_read('/config/test_copy.txt') or { panic(err) }
println('Copied file content: ${copy_data.bytestr()}')
println('\nCleanup...')
// Cleanup
nested_vfs.destroy() or { panic(err) }
os.rmdir_all('/tmp/test_nested_vfs') or { panic(err) }
println('Test completed successfully!')
}

View File

@@ -0,0 +1,4 @@
# VFS Overlay
This virtual filesystem combines multiple other VFS'es

View File

@@ -0,0 +1,190 @@
module vfsnested
import freeflowuniverse.crystallib.vfs.vfscore
// NestedVFS represents a VFS that can contain multiple nested VFS instances
pub struct NestedVFS {
mut:
vfs_map map[string]vfscore.VFSImplementation // Map of path prefixes to VFS implementations
}
// new creates a new NestedVFS instance
pub fn new() &NestedVFS {
return &NestedVFS{
vfs_map: map[string]vfscore.VFSImplementation{}
}
}
// add_vfs adds a new VFS implementation at the specified path prefix
pub fn (mut self NestedVFS) add_vfs(prefix string, impl vfscore.VFSImplementation) ! {
if prefix in self.vfs_map {
return error('VFS already exists at prefix: ${prefix}')
}
self.vfs_map[prefix] = impl
}
// find_vfs finds the appropriate VFS implementation for a given path
fn (self &NestedVFS) find_vfs(path string) !(vfscore.VFSImplementation, string) {
// Sort prefixes by length (longest first) to match most specific path
mut prefixes := self.vfs_map.keys()
prefixes.sort(a.len > b.len)
for prefix in prefixes {
if path.starts_with(prefix) {
relative_path := path[prefix.len..]
if relative_path.starts_with('/') {
return self.vfs_map[prefix], relative_path[1..]
}
return self.vfs_map[prefix], relative_path
}
}
return error('No VFS found for path: ${path}')
}
// Implementation of VFSImplementation interface
pub fn (mut self NestedVFS) root_get() !vfscore.FSEntry {
// Return a special root entry that represents the nested VFS
return &RootEntry{
metadata: vfscore.Metadata{
name: ''
file_type: .directory
size: 0
created_at: 0
modified_at: 0
accessed_at: 0
}
}
}
pub fn (mut self NestedVFS) file_create(path string) !vfscore.FSEntry {
mut impl, rel_path := self.find_vfs(path)!
return impl.file_create(rel_path)
}
pub fn (mut self NestedVFS) file_read(path string) ![]u8 {
mut impl, rel_path := self.find_vfs(path)!
return impl.file_read(rel_path)
}
pub fn (mut self NestedVFS) file_write(path string, data []u8) ! {
mut impl, rel_path := self.find_vfs(path)!
return impl.file_write(rel_path, data)
}
pub fn (mut self NestedVFS) file_delete(path string) ! {
mut impl, rel_path := self.find_vfs(path)!
return impl.file_delete(rel_path)
}
pub fn (mut self NestedVFS) dir_create(path string) !vfscore.FSEntry {
mut impl, rel_path := self.find_vfs(path)!
return impl.dir_create(rel_path)
}
pub fn (mut self NestedVFS) dir_list(path string) ![]vfscore.FSEntry {
// Special case for root directory
if path == '' || path == '/' {
mut entries := []vfscore.FSEntry{}
for prefix, mut impl in self.vfs_map {
root := impl.root_get() or { continue }
entries << &MountEntry{
metadata: vfscore.Metadata{
name: prefix
file_type: .directory
size: 0
created_at: 0
modified_at: 0
accessed_at: 0
}
impl: impl
}
}
return entries
}
mut impl, rel_path := self.find_vfs(path)!
return impl.dir_list(rel_path)
}
pub fn (mut self NestedVFS) dir_delete(path string) ! {
mut impl, rel_path := self.find_vfs(path)!
return impl.dir_delete(rel_path)
}
pub fn (mut self NestedVFS) exists(path string) !bool {
mut impl, rel_path := self.find_vfs(path)!
return impl.exists(rel_path)
}
pub fn (mut self NestedVFS) get(path string) !vfscore.FSEntry {
mut impl, rel_path := self.find_vfs(path)!
return impl.get(rel_path)
}
pub fn (mut self NestedVFS) rename(old_path string, new_path string) ! {
mut old_impl, old_rel_path := self.find_vfs(old_path)!
mut new_impl, new_rel_path := self.find_vfs(new_path)!
if old_impl != new_impl {
return error('Cannot rename across different VFS implementations')
}
return old_impl.rename(old_rel_path, new_rel_path)
}
pub fn (mut self NestedVFS) copy(src_path string, dst_path string) ! {
mut src_impl, src_rel_path := self.find_vfs(src_path)!
mut dst_impl, dst_rel_path := self.find_vfs(dst_path)!
if src_impl == dst_impl {
return src_impl.copy(src_rel_path, dst_rel_path)
}
// Copy across different VFS implementations
data := src_impl.file_read(src_rel_path)!
dst_impl.file_create(dst_rel_path)!
return dst_impl.file_write(dst_rel_path, data)
}
pub fn (mut self NestedVFS) link_create(target_path string, link_path string) !vfscore.FSEntry {
mut impl, rel_path := self.find_vfs(link_path)!
return impl.link_create(target_path, rel_path)
}
pub fn (mut self NestedVFS) link_read(path string) !string {
mut impl, rel_path := self.find_vfs(path)!
return impl.link_read(rel_path)
}
pub fn (mut self NestedVFS) destroy() ! {
for _, mut impl in self.vfs_map {
impl.destroy()!
}
}
// Special entry types for the nested VFS
struct RootEntry {
metadata vfscore.Metadata
}
fn (e &RootEntry) get_metadata() vfscore.Metadata {
return e.metadata
}
fn (e &RootEntry) get_path() string {
return '/'
}
pub struct MountEntry {
pub mut:
metadata vfscore.Metadata
impl vfscore.VFSImplementation
}
fn (e &MountEntry) get_metadata() vfscore.Metadata {
return e.metadata
}
fn (e &MountEntry) get_path() string {
return '/${e.metadata.name}'
}

View File

@@ -0,0 +1,8 @@
# 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.

284
lib/vfs/vfsourdb/vfsourdb.v Normal file
View File

@@ -0,0 +1,284 @@
module vfsourdb
import freeflowuniverse.crystallib.vfs.vfscore
import freeflowuniverse.crystallib.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.VFS
}
// 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
)!
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) 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 {
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) ! {
return error('Not implemented')
}
pub fn (mut self OurDBVFS) copy(src_path string, dst_path string) ! {
return error('Not implemented')
}
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: u32(time.now().unix())
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(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
}
// Helper functions
fn (mut self OurDBVFS) get_entry(path string) !ourdb_fs.FSEntry {
if path == '/' {
return self.core.get_root()!
}
mut current := self.core.get_root()!
parts := path.trim_left('/').split('/')
for i := 0; i < parts.len; i++ {
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 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{
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
}
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
}
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
}

View File

@@ -0,0 +1,69 @@
module vfsourdb
import os
fn test_vfsourdb() ! {
println('Testing OurDB VFS...')
// 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')
os.mkdir_all(test_data_dir)!
os.mkdir_all(test_meta_dir)!
defer {
os.rmdir_all(test_data_dir) or {}
os.rmdir_all(test_meta_dir) or {}
}
// Create VFS instance
mut vfs := new(test_data_dir, test_meta_dir)!
// Test root directory
mut root := vfs.root_get()!
assert root.get_metadata().file_type == .directory
assert root.get_metadata().name == ''
// Test directory creation
mut test_dir := vfs.dir_create('/test_dir')!
assert test_dir.get_metadata().name == 'test_dir'
assert test_dir.get_metadata().file_type == .directory
// Test file creation and writing
mut test_file := vfs.file_create('/test_dir/test.txt')!
assert test_file.get_metadata().name == 'test.txt'
assert test_file.get_metadata().file_type == .file
test_content := 'Hello, World!'.bytes()
vfs.file_write('/test_dir/test.txt', test_content)!
// Test file reading
read_content := vfs.file_read('/test_dir/test.txt')!
assert read_content == test_content
// Test directory listing
entries := vfs.dir_list('/test_dir')!
assert entries.len == 1
assert entries[0].get_metadata().name == 'test.txt'
// Test exists
assert vfs.exists('/test_dir')! == true
assert vfs.exists('/test_dir/test.txt')! == true
assert vfs.exists('/nonexistent')! == false
// Test symlink creation and reading
vfs.link_create('/test_dir/test.txt', '/test_dir/test_link')!
link_target := vfs.link_read('/test_dir/test_link')!
assert link_target == '/test_dir/test.txt'
// Test file deletion
vfs.file_delete('/test_dir/test.txt')!
assert vfs.exists('/test_dir/test.txt')! == false
// Test directory deletion
vfs.dir_delete('/test_dir')!
assert vfs.exists('/test_dir')! == false
println('Test completed successfully!')
}