feat(herofs): Complete HeroFS implementation with comprehensive testing

- Implement high-level filesystem tools (find, cp, mv, rm) with pattern matching
- Add complete import/export functionality for VFS ↔ real filesystem operations
- Implement symlink operations with broken link detection
- Add comprehensive error condition testing (blob limits, invalid refs, edge cases)
- Fix blob hash-based retrieval using Redis mapping instead of membership
- Add 5 test suites with 100% green CI coverage
- Clean up placeholder code and improve error messages
- Document known limitations (directory merging, quota enforcement)

Features added:
- fs_tools_*.v: High-level filesystem operations with FindOptions/CopyOptions/MoveOptions
- fs_tools_import_export.v: Bidirectional VFS/filesystem data transfer
- fs_symlink_test.v: Complete symlink lifecycle testing
- fs_error_conditions_test.v: Edge cases and error condition validation
- Working examples for all functionality

Fixes:
- Blob get_by_hash() now uses direct Redis hash mapping
- File listing handles deleted files gracefully
- V compiler namespace conflicts resolved in tests
- All compilation warnings cleaned up

Ready for open source publication with production-grade test coverage.
This commit is contained in:
Mahmoud-Emad
2025-09-16 18:35:26 +03:00
parent 3669edf24e
commit af1d6a7485
25 changed files with 3243 additions and 836 deletions

211
examples/hero/herofs/fs_tools_example.vsh Normal file → Executable file
View File

@@ -1,6 +1,5 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.hero.herofs
// Example demonstrating the new FsTools high-level filesystem operations
@@ -18,110 +17,98 @@ fn main() {
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
)!
// Save the filesystem to get an ID
fs_id := fs_factory.fs.set(my_fs)!
println('Created filesystem: ${my_fs.name} with ID: ${fs_id}')
// Get the tools interface
mut tools := fs_factory.tools()
// Save the filesystem
fs_factory.fs.set(mut my_fs)!
println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
// Create root directory
mut root_dir := fs_factory.fs_dir.new(
name: 'root'
fs_id: fs_id
fs_id: my_fs.id
parent_id: 0
description: 'Root directory'
)!
root_dir_id := fs_factory.fs_dir.set(root_dir)!
fs_factory.fs_dir.set(mut root_dir)!
// Update the filesystem with the root directory ID
my_fs.root_dir_id = root_dir_id
fs_factory.fs.set(my_fs)!
my_fs.root_dir_id = root_dir.id
fs_factory.fs.set(mut my_fs)!
// Create some sample directory structure
println('\nCreating sample directory structure...')
// Create directories using the high-level tools (which will use create_path)
src_dir_id := fs_factory.fs_dir.create_path(fs_id, '/src')!
docs_dir_id := fs_factory.fs_dir.create_path(fs_id, '/docs')!
test_dir_id := fs_factory.fs_dir.create_path(fs_id, '/tests')!
examples_dir_id := fs_factory.fs_dir.create_path(fs_id, '/examples')!
src_dir_id := fs_factory.fs_dir.create_path(my_fs.id, '/src')!
_ := fs_factory.fs_dir.create_path(my_fs.id, '/docs')!
test_dir_id := fs_factory.fs_dir.create_path(my_fs.id, '/tests')!
examples_dir_id := fs_factory.fs_dir.create_path(my_fs.id, '/examples')!
// Create some sample files
println('Creating sample files...')
// Create blobs for file content
v_code := 'fn main() {\n println("Hello from V!")\n}\n'.bytes()
v_blob := fs_factory.fs_blob.new(
data: v_code
mime_type: 'text/plain'
name: 'main.v content'
)!
v_blob_id := fs_factory.fs_blob.set(v_blob)!
mut v_blob := fs_factory.fs_blob.new(data: v_code)!
fs_factory.fs_blob.set(mut v_blob)!
readme_content := '# My Project\n\nThis is a sample project.\n\n## Features\n\n- Feature 1\n- Feature 2\n'.bytes()
readme_blob := fs_factory.fs_blob.new(
data: readme_content
mime_type: 'text/markdown'
name: 'README.md content'
)!
readme_blob_id := fs_factory.fs_blob.set(readme_blob)!
mut readme_blob := fs_factory.fs_blob.new(data: readme_content)!
fs_factory.fs_blob.set(mut readme_blob)!
test_content := 'fn test_main() {\n assert 1 == 1\n}\n'.bytes()
test_blob := fs_factory.fs_blob.new(
data: test_content
mime_type: 'text/plain'
name: 'test content'
)!
test_blob_id := fs_factory.fs_blob.set(test_blob)!
mut test_blob := fs_factory.fs_blob.new(data: test_content)!
fs_factory.fs_blob.set(mut test_blob)!
// Create files
main_file := fs_factory.fs_file.new(
name: 'main.v'
fs_id: fs_id
directories: [src_dir_id]
blobs: [v_blob_id]
mime_type: 'text/plain'
mut main_file := fs_factory.fs_file.new(
name: 'main.v'
fs_id: my_fs.id
blobs: [v_blob.id]
mime_type: .txt
)!
fs_factory.fs_file.set(main_file)!
fs_factory.fs_file.set(mut main_file)!
fs_factory.fs_file.add_to_directory(main_file.id, src_dir_id)!
readme_file := fs_factory.fs_file.new(
name: 'README.md'
fs_id: fs_id
directories: [root_dir_id]
blobs: [readme_blob_id]
mime_type: 'text/markdown'
mut readme_file := fs_factory.fs_file.new(
name: 'README.md'
fs_id: my_fs.id
blobs: [readme_blob.id]
mime_type: .md
)!
fs_factory.fs_file.set(readme_file)!
fs_factory.fs_file.set(mut readme_file)!
fs_factory.fs_file.add_to_directory(readme_file.id, root_dir.id)!
test_file := fs_factory.fs_file.new(
name: 'main_test.v'
fs_id: fs_id
directories: [test_dir_id]
blobs: [test_blob_id]
mime_type: 'text/plain'
mut test_file := fs_factory.fs_file.new(
name: 'main_test.v'
fs_id: my_fs.id
blobs: [test_blob.id]
mime_type: .txt
)!
fs_factory.fs_file.set(test_file)!
fs_factory.fs_file.set(mut test_file)!
fs_factory.fs_file.add_to_directory(test_file.id, test_dir_id)!
// Create a symbolic link
main_symlink := fs_factory.fs_symlink.new(
mut main_symlink := fs_factory.fs_symlink.new(
name: 'main_link.v'
fs_id: fs_id
fs_id: my_fs.id
parent_id: examples_dir_id
target_id: main_file.id
target_type: .file
description: 'Link to main.v'
)!
fs_factory.fs_symlink.set(main_symlink)!
fs_factory.fs_symlink.set(mut main_symlink)!
println('Sample filesystem structure created!')
// Get the filesystem instance for tools operations
mut fs := fs_factory.fs.get(my_fs.id)!
// Demonstrate FIND functionality
println('\n=== FIND OPERATIONS ===')
// Find all files
println('\nFinding all files...')
all_results := tools.find(fs_id, '/', recursive: true)!
all_results := fs.find('/', recursive: true)!
for result in all_results {
type_str := match result.result_type {
.file { 'FILE' }
@@ -133,14 +120,19 @@ fn main() {
// Find only V files
println('\nFinding only .v files...')
v_files := tools.find(fs_id, '/', recursive: true, include_patterns: ['*.v'])!
v_files := fs.find('/', recursive: true, include_patterns: ['*.v'])!
for result in v_files {
println('V FILE: ${result.path}')
}
// Find with exclude patterns
println('\nFinding all except test files...')
non_test_results := tools.find(fs_id, '/', recursive: true, exclude_patterns: ['*test*'])!
non_test_results := fs.find('/',
recursive: true
exclude_patterns: [
'*test*',
]
)!
for result in non_test_results {
type_str := match result.result_type {
.file { 'FILE' }
@@ -153,77 +145,56 @@ fn main() {
// Demonstrate COPY functionality
println('\n=== COPY OPERATIONS ===')
// Copy a file
println('\nCopying main.v to docs directory...')
tools.cp(fs_id, '/src/main.v', '/docs/main_copy.v', recursive: true)!
println('File copied successfully')
// Copy a single file
println('Copying /src/main.v to /docs/')
fs.cp('/src/main.v', '/docs/', herofs.FindOptions{ recursive: false }, herofs.CopyOptions{
overwrite: true
copy_blobs: true
})!
// Copy a directory
println('\nCopying src directory to backup...')
tools.cp(fs_id, '/src', '/src_backup', recursive: true)!
println('Directory copied successfully')
// Verify the copies
println('\nVerifying copies...')
copy_results := tools.find(fs_id, '/', recursive: true, include_patterns: ['*copy*', '*backup*'])!
for result in copy_results {
println('COPIED: ${result.path}')
}
// Copy all V files to examples directory
println('Copying all .v files to /examples/')
fs.cp('/', '/examples/', herofs.FindOptions{
recursive: true
include_patterns: [
'*.v',
]
}, herofs.CopyOptions{
overwrite: true
copy_blobs: false
})! // Reference same blobs
// Demonstrate MOVE functionality
println('\n=== MOVE OPERATIONS ===')
// Move a file
println('\nMoving main_copy.v to examples directory...')
tools.mv(fs_id, '/docs/main_copy.v', '/examples/main_example.v', overwrite: false)!
println('File moved successfully')
// Move the copied file to a new location with rename
println('Moving /docs/main.v to /examples/main_backup.v')
fs.mv('/docs/main.v', '/examples/main_backup.v', herofs.MoveOptions{ overwrite: true })!
// Move a directory
println('\nMoving src_backup to archive...')
tools.mv(fs_id, '/src_backup', '/archive', overwrite: false)!
println('Directory moved successfully')
// Verify the moves
println('\nVerifying moves...')
move_results := tools.find(fs_id, '/', recursive: true)!
for result in move_results {
if result.path.contains('example') || result.path.contains('archive') {
type_str := match result.result_type {
.file { 'FILE' }
.directory { 'DIR ' }
.symlink { 'LINK' }
}
println('MOVED: ${type_str}: ${result.path}')
}
}
// Move README to root
println('Moving /README.md to /project_readme.md')
fs.mv('/README.md', '/project_readme.md', herofs.MoveOptions{ overwrite: false })!
// Demonstrate REMOVE functionality
println('\n=== REMOVE OPERATIONS ===')
// Remove a single file
println('\nRemoving test file...')
tools.rm(fs_id, '/tests/main_test.v', recursive: false, delete_blobs: false)!
println('Test file removed')
// Remove a specific file
println('Removing /tests/main_test.v')
fs.rm('/tests/main_test.v', herofs.FindOptions{ recursive: false }, herofs.RemoveOptions{
delete_blobs: false
})!
// Create a temporary directory with content for removal demo
temp_dir_id := fs_factory.fs_dir.create_path(fs_id, '/temp')!
temp_file := fs_factory.fs_file.new(
name: 'temp.txt'
fs_id: fs_id
directories: [temp_dir_id]
blobs: [readme_blob_id] // Reuse existing blob
mime_type: 'text/plain'
)!
fs_factory.fs_file.set(temp_file)!
// Remove all files in docs directory (but keep the directory)
println('Removing all files in /docs/ directory')
fs.rm('/docs/', herofs.FindOptions{ recursive: false, include_patterns: ['*'] }, herofs.RemoveOptions{
delete_blobs: false
})!
// Remove directory with contents
println('\nRemoving temp directory and its contents...')
tools.rm(fs_id, '/temp', recursive: true, delete_blobs: false)!
println('Temp directory and contents removed')
println('\nAll copy, move, and remove operations completed successfully!')
// Show final filesystem state
println('\n=== FINAL FILESYSTEM STATE ===')
final_results := tools.find(fs_id, '/', recursive: true)!
final_results := fs.find('/', recursive: true)!
for result in final_results {
type_str := match result.result_type {
.file { 'FILE' }
@@ -234,4 +205,4 @@ fn main() {
}
println('\nfs_tools demonstration completed successfully!')
}
}

View File

@@ -1,9 +1,6 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.hero.herofs
import time
import os
// Advanced example of using HeroFS - the Hero Filesystem
// Demonstrates more complex operations including:
@@ -25,25 +22,25 @@ fn main() {
quota_bytes: 5 * 1024 * 1024 * 1024 // 5GB quota
)!
// Save the filesystem to get an ID
fs_id := fs_factory.fs.set(my_fs)!
println('Created filesystem: ${my_fs.name} with ID: ${fs_id}')
// Save the filesystem
fs_factory.fs.set(mut my_fs)!
println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
// Create root directory
mut root_dir := fs_factory.fs_dir.new(
name: 'root'
fs_id: fs_id
fs_id: my_fs.id
parent_id: 0 // Root has no parent
description: 'Root directory'
)!
// Save the root directory
root_dir_id := fs_factory.fs_dir.set(root_dir)!
println('Created root directory with ID: ${root_dir_id}')
fs_factory.fs_dir.set(mut root_dir)!
println('Created root directory with ID: ${root_dir.id}')
// Update the filesystem with the root directory ID
my_fs.root_dir_id = root_dir_id
fs_factory.fs.set(my_fs)!
my_fs.root_dir_id = root_dir.id
fs_factory.fs.set(mut my_fs)!
// Create a directory hierarchy
println('\nCreating directory hierarchy...')
@@ -51,44 +48,56 @@ fn main() {
// Main project directories
mut src_dir := fs_factory.fs_dir.new(
name: 'src'
fs_id: fs_id
parent_id: root_dir_id
fs_id: my_fs.id
parent_id: root_dir.id
description: 'Source code'
)!
src_dir_id := fs_factory.fs_dir.set(src_dir)!
fs_factory.fs_dir.set(mut src_dir)!
mut docs_dir := fs_factory.fs_dir.new(
name: 'docs'
fs_id: fs_id
parent_id: root_dir_id
fs_id: my_fs.id
parent_id: root_dir.id
description: 'Documentation'
)!
docs_dir_id := fs_factory.fs_dir.set(docs_dir)!
fs_factory.fs_dir.set(mut docs_dir)!
mut assets_dir := fs_factory.fs_dir.new(
name: 'assets'
fs_id: fs_id
parent_id: root_dir_id
fs_id: my_fs.id
parent_id: root_dir.id
description: 'Project assets'
)!
assets_dir_id := fs_factory.fs_dir.set(assets_dir)!
fs_factory.fs_dir.set(mut assets_dir)!
// Subdirectories
mut images_dir := fs_factory.fs_dir.new(
name: 'images'
fs_id: fs_id
parent_id: assets_dir_id
fs_id: my_fs.id
parent_id: assets_dir.id
description: 'Image assets'
)!
images_dir_id := fs_factory.fs_dir.set(images_dir)!
fs_factory.fs_dir.set(mut images_dir)!
mut api_docs_dir := fs_factory.fs_dir.new(
name: 'api'
fs_id: fs_id
parent_id: docs_dir_id
fs_id: my_fs.id
parent_id: docs_dir.id
description: 'API documentation'
)!
api_docs_dir_id := fs_factory.fs_dir.set(api_docs_dir)!
fs_factory.fs_dir.set(mut api_docs_dir)!
// Add directories to their parents
root_dir.directories << src_dir.id
root_dir.directories << docs_dir.id
root_dir.directories << assets_dir.id
fs_factory.fs_dir.set(mut root_dir)!
assets_dir.directories << images_dir.id
fs_factory.fs_dir.set(mut assets_dir)!
docs_dir.directories << api_docs_dir.id
fs_factory.fs_dir.set(mut docs_dir)!
println('Directory hierarchy created successfully')
@@ -97,67 +106,55 @@ fn main() {
// Text file for source code
code_content := 'fn main() {\n println("Hello, HeroFS!")\n}\n'.bytes()
mut code_blob := fs_factory.fs_blob.new(
data: code_content
mime_type: 'text/plain'
name: 'main.v blob'
)!
code_blob_id := fs_factory.fs_blob.set(code_blob)!
mut code_blob := fs_factory.fs_blob.new(data: code_content)!
fs_factory.fs_blob.set(mut code_blob)!
mut code_file := fs_factory.fs_file.new(
name: 'main.v'
fs_id: fs_id
directories: [src_dir_id]
blobs: [code_blob_id]
mime_type: 'text/plain'
metadata: {
name: 'main.v'
fs_id: my_fs.id
blobs: [code_blob.id]
mime_type: .txt
metadata: {
'language': 'vlang'
'version': '0.3.3'
}
)!
code_file_id := fs_factory.fs_file.set(code_file)!
fs_factory.fs_file.set(mut code_file)!
fs_factory.fs_file.add_to_directory(code_file.id, src_dir.id)!
// Markdown documentation file
docs_content := '# API Documentation\n\n## Endpoints\n\n- GET /api/v1/users\n- POST /api/v1/users\n'.bytes()
mut docs_blob := fs_factory.fs_blob.new(
data: docs_content
mime_type: 'text/markdown'
name: 'api.md blob'
)!
docs_blob_id := fs_factory.fs_blob.set(docs_blob)!
mut docs_blob := fs_factory.fs_blob.new(data: docs_content)!
fs_factory.fs_blob.set(mut docs_blob)!
mut docs_file := fs_factory.fs_file.new(
name: 'api.md'
fs_id: fs_id
directories: [api_docs_dir_id]
blobs: [docs_blob_id]
mime_type: 'text/markdown'
name: 'api.md'
fs_id: my_fs.id
blobs: [docs_blob.id]
mime_type: .md
)!
docs_file_id := fs_factory.fs_file.set(docs_file)!
fs_factory.fs_file.set(mut docs_file)!
fs_factory.fs_file.add_to_directory(docs_file.id, api_docs_dir.id)!
// Create a binary file (sample image)
// For this example, we'll just create random bytes
mut image_data := []u8{len: 1024, init: u8(index % 256)}
mut image_blob := fs_factory.fs_blob.new(
data: image_data
mime_type: 'image/png'
name: 'logo.png blob'
)!
image_blob_id := fs_factory.fs_blob.set(image_blob)!
mut image_blob := fs_factory.fs_blob.new(data: image_data)!
fs_factory.fs_blob.set(mut image_blob)!
mut image_file := fs_factory.fs_file.new(
name: 'logo.png'
fs_id: fs_id
directories: [images_dir_id]
blobs: [image_blob_id]
mime_type: 'image/png'
metadata: {
name: 'logo.png'
fs_id: my_fs.id
blobs: [image_blob.id]
mime_type: .png
metadata: {
'width': '200'
'height': '100'
'format': 'PNG'
}
)!
image_file_id := fs_factory.fs_file.set(image_file)!
fs_factory.fs_file.set(mut image_file)!
fs_factory.fs_file.add_to_directory(image_file.id, images_dir.id)!
println('Files created successfully')
@@ -167,110 +164,151 @@ fn main() {
// Symlink to the API docs from the root directory
mut api_symlink := fs_factory.fs_symlink.new(
name: 'api-docs'
fs_id: fs_id
parent_id: root_dir_id
target_id: api_docs_dir_id
fs_id: my_fs.id
parent_id: root_dir.id
target_id: api_docs_dir.id
target_type: .directory
description: 'Shortcut to API documentation'
)!
api_symlink_id := fs_factory.fs_symlink.set(api_symlink)!
fs_factory.fs_symlink.set(mut api_symlink)!
// Symlink to the logo from the docs directory
mut logo_symlink := fs_factory.fs_symlink.new(
name: 'logo.png'
fs_id: fs_id
parent_id: docs_dir_id
target_id: image_file_id
fs_id: my_fs.id
parent_id: docs_dir.id
target_id: image_file.id
target_type: .file
description: 'Shortcut to project logo'
)!
logo_symlink_id := fs_factory.fs_symlink.set(logo_symlink)!
fs_factory.fs_symlink.set(mut logo_symlink)!
// Add symlinks to their parent directories
root_dir.symlinks << api_symlink.id
fs_factory.fs_dir.set(mut root_dir)!
docs_dir.symlinks << logo_symlink.id
fs_factory.fs_dir.set(mut docs_dir)!
println('Symlinks created successfully')
// Demonstrate file operations
println('\nDemonstrating file operations...')
// Demonstrate filesystem navigation using find
println('\nDemonstrating filesystem navigation...')
// 1. Move a file to multiple directories (hard link-like behavior)
println('Moving logo.png to both images and docs directories...')
image_file = fs_factory.fs_file.get(image_file_id)!
fs_factory.fs_file.move(image_file_id, [images_dir_id, docs_dir_id])!
image_file = fs_factory.fs_file.get(image_file_id)!
// Get the filesystem instance for navigation
mut fs := fs_factory.fs.get(my_fs.id)!
// 2. Rename a file
println('Renaming main.v to app.v...')
fs_factory.fs_file.rename(code_file_id, 'app.v')!
code_file = fs_factory.fs_file.get(code_file_id)!
// Find all items in the filesystem
results := fs.find('/', recursive: true)!
println('Complete filesystem structure:')
for result in results {
type_str := match result.result_type {
.file { 'FILE' }
.directory { 'DIR ' }
.symlink { 'LINK' }
}
println('${type_str}: ${result.path} (ID: ${result.id})')
}
// 3. Update file metadata
// Find specific file types
println('\nFinding specific file types...')
v_files := fs.find('/', include_patterns: ['*.v'], recursive: true)!
println('V source files:')
for file in v_files {
println(' ${file.path}')
}
md_files := fs.find('/', include_patterns: ['*.md'], recursive: true)!
println('Markdown files:')
for file in md_files {
println(' ${file.path}')
}
// Find files in specific directories
println('\nFinding files in specific directories...')
src_files := fs.find('/src', recursive: true)!
println('Files in src directory:')
for file in src_files {
println(' ${file.path}')
}
// Demonstrate advanced file operations
println('\nDemonstrating advanced file operations...')
// Update file metadata
println('Updating file metadata...')
fs_factory.fs_file.update_metadata(docs_file_id, 'status', 'draft')!
fs_factory.fs_file.update_metadata(docs_file_id, 'author', 'HeroFS Team')!
fs_factory.fs_file.update_metadata(docs_file.id, 'status', 'draft')!
fs_factory.fs_file.update_metadata(docs_file.id, 'author', 'HeroFS Team')!
// 4. Update file access time when "reading" it
// Update access time
println('Updating file access time...')
fs_factory.fs_file.update_accessed(docs_file_id)!
fs_factory.fs_file.update_accessed(docs_file.id)!
// 5. Add additional content to a file (append a blob)
// Rename a file
println('Renaming main.v to app.v...')
fs_factory.fs_file.rename(code_file.id, 'app.v')!
// Append content to a file
println('Appending content to API docs...')
additional_content := '\n## Authentication\n\nUse Bearer token for authentication.\n'.bytes()
mut additional_blob := fs_factory.fs_blob.new(
data: additional_content
mime_type: 'text/markdown'
name: 'api_append.md blob'
)!
additional_blob_id := fs_factory.fs_blob.set(additional_blob)!
fs_factory.fs_file.append_blob(docs_file_id, additional_blob_id)!
mut additional_blob := fs_factory.fs_blob.new(data: additional_content)!
fs_factory.fs_blob.set(mut additional_blob)!
fs_factory.fs_file.append_blob(docs_file.id, additional_blob.id)!
// Demonstrate directory operations
println('\nDemonstrating directory operations...')
// 1. Create a new directory and move it
// Create a temporary directory
mut temp_dir := fs_factory.fs_dir.new(
name: 'temp'
fs_id: fs_id
parent_id: root_dir_id
fs_id: my_fs.id
parent_id: root_dir.id
description: 'Temporary directory'
)!
temp_dir_id := fs_factory.fs_dir.set(temp_dir)!
fs_factory.fs_dir.set(mut temp_dir)!
println('Moving temp directory to be under docs...')
fs_factory.fs_dir.move(temp_dir_id, docs_dir_id)!
// Add to parent
root_dir.directories << temp_dir.id
fs_factory.fs_dir.set(mut root_dir)!
// 2. Rename a directory
// Move temp directory under docs
println('Moving temp directory under docs...')
fs_factory.fs_dir.move(temp_dir.id, docs_dir.id)!
// Rename temp directory to drafts
println('Renaming temp directory to drafts...')
fs_factory.fs_dir.rename(temp_dir_id, 'drafts')!
fs_factory.fs_dir.rename(temp_dir.id, 'drafts')!
// 3. Check if a directory has children
has_children := fs_factory.fs_dir.has_children(docs_dir_id)!
// Check if docs directory has children
has_children := fs_factory.fs_dir.has_children(docs_dir.id)!
println('Does docs directory have children? ${has_children}')
// Demonstrate searching and filtering
println('\nDemonstrating searching and filtering...')
// Demonstrate listing operations
println('\nDemonstrating listing operations...')
// 1. List all files in the filesystem
all_files := fs_factory.fs_file.list_by_filesystem(fs_id)!
// List all files in filesystem
all_files := fs_factory.fs_file.list_by_filesystem(my_fs.id)!
println('All files in filesystem (${all_files.len}):')
for file in all_files {
println('- ${file.name} (ID: ${file.id})')
}
// 2. List files by MIME type
markdown_files := fs_factory.fs_file.list_by_mime_type('text/markdown')!
println('\nMarkdown files (${markdown_files.len}):')
for file in markdown_files {
// List files by MIME type
md_files_by_type := fs_factory.fs_file.list_by_mime_type(.md)!
println('\nMarkdown files (${md_files_by_type.len}):')
for file in md_files_by_type {
println('- ${file.name} (ID: ${file.id})')
}
// 3. List all symlinks
all_symlinks := fs_factory.fs_symlink.list_by_filesystem(fs_id)!
// List all symlinks
all_symlinks := fs_factory.fs_symlink.list_by_filesystem(my_fs.id)!
println('\nAll symlinks (${all_symlinks.len}):')
for symlink in all_symlinks {
target_type_str := if symlink.target_type == .file { 'file' } else { 'directory' }
println('- ${symlink.name} -> ${symlink.target_id} (${target_type_str})')
}
// 4. Check for broken symlinks
// Check for broken symlinks
println('\nChecking for broken symlinks:')
for symlink in all_symlinks {
is_broken := fs_factory.fs_symlink.is_broken(symlink.id)!
@@ -281,11 +319,11 @@ fn main() {
println('\nDemonstrating file content retrieval:')
// Get the updated API docs file and print its content
docs_file = fs_factory.fs_file.get(docs_file_id)!
println('Content of ${docs_file.name}:')
updated_docs_file := fs_factory.fs_file.get(docs_file.id)!
println('Content of ${updated_docs_file.name}:')
mut full_content := ''
for blob_id in docs_file.blobs {
for blob_id in updated_docs_file.blobs {
blob := fs_factory.fs_blob.get(blob_id)!
full_content += blob.data.bytestr()
}
@@ -294,12 +332,23 @@ fn main() {
println(full_content)
println('---END CONTENT---')
// Print filesystem usage
println('\nFilesystem usage:')
my_fs = fs_factory.fs.get(fs_id)!
println('Used: ${my_fs.used_bytes} bytes')
println('Quota: ${my_fs.quota_bytes} bytes')
println('Available: ${my_fs.quota_bytes - my_fs.used_bytes} bytes')
// Print filesystem information
println('\nFilesystem information:')
println('Filesystem: ${my_fs.name}')
println('Description: ${my_fs.description}')
println('Root directory ID: ${my_fs.root_dir_id}')
println('\nHeroFS advanced example completed successfully!')
println('\n=== HeroFS Advanced Example Completed Successfully! ===')
println('This example demonstrated:')
println('- Creating a complex directory hierarchy')
println('- Creating files with different content types (text, markdown, binary)')
println('- Creating symbolic links')
println('- Using the find functionality to navigate the filesystem')
println('- Advanced file operations: rename, metadata updates, append content')
println('- Advanced directory operations: move, rename, check children')
println('- Listing operations: files by filesystem, files by MIME type, symlinks')
println('- Symlink validation: checking for broken links')
println('- Retrieving and displaying file content')
println('\nAll advanced HeroFS operations are now fully implemented!')
}

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.hero.herofs
// Basic example of using HeroFS - the Hero Filesystem
@@ -18,90 +17,96 @@ fn main() {
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
)!
// Save the filesystem to get an ID
fs_id := fs_factory.fs.set(my_fs)!
println('Created filesystem: ${my_fs.name} with ID: ${fs_id}')
// Save the filesystem
fs_factory.fs.set(mut my_fs)!
println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
// Create root directory
mut root_dir := fs_factory.fs_dir.new(
name: 'root'
fs_id: fs_id
fs_id: my_fs.id
parent_id: 0 // Root has no parent
description: 'Root directory'
)!
// Save the root directory
root_dir_id := fs_factory.fs_dir.set(root_dir)!
println('Created root directory with ID: ${root_dir_id}')
fs_factory.fs_dir.set(mut root_dir)!
println('Created root directory with ID: ${root_dir.id}')
// Update the filesystem with the root directory ID
my_fs.root_dir_id = root_dir_id
fs_factory.fs.set(my_fs)!
my_fs.root_dir_id = root_dir.id
fs_factory.fs.set(mut my_fs)!
// Create some subdirectories
mut docs_dir := fs_factory.fs_dir.new(
name: 'documents'
fs_id: fs_id
parent_id: root_dir_id
fs_id: my_fs.id
parent_id: root_dir.id
description: 'Documents directory'
)!
mut pics_dir := fs_factory.fs_dir.new(
name: 'pictures'
fs_id: fs_id
parent_id: root_dir_id
fs_id: my_fs.id
parent_id: root_dir.id
description: 'Pictures directory'
)!
// Save the subdirectories
docs_dir_id := fs_factory.fs_dir.set(docs_dir)!
pics_dir_id := fs_factory.fs_dir.set(pics_dir)!
println('Created documents directory with ID: ${docs_dir_id}')
println('Created pictures directory with ID: ${pics_dir_id}')
fs_factory.fs_dir.set(mut docs_dir)!
fs_factory.fs_dir.set(mut pics_dir)!
// Add subdirectories to root directory
root_dir.directories << docs_dir.id
root_dir.directories << pics_dir.id
fs_factory.fs_dir.set(mut root_dir)!
println('Created documents directory with ID: ${docs_dir.id}')
println('Created pictures directory with ID: ${pics_dir.id}')
// Create a text file blob
text_content := 'Hello, world! This is a test file in HeroFS.'.bytes()
mut text_blob := fs_factory.fs_blob.new(
data: text_content
mime_type: 'text/plain'
name: 'hello.txt blob'
)!
mut text_blob := fs_factory.fs_blob.new(data: text_content)!
// Save the blob
blob_id := fs_factory.fs_blob.set(text_blob)!
println('Created text blob with ID: ${blob_id}')
fs_factory.fs_blob.set(mut text_blob)!
println('Created text blob with ID: ${text_blob.id}')
// Create a file referencing the blob
mut text_file := fs_factory.fs_file.new(
name: 'hello.txt'
fs_id: fs_id
directories: [docs_dir_id]
blobs: [blob_id]
mime_type: 'text/plain'
name: 'hello.txt'
fs_id: my_fs.id
blobs: [text_blob.id]
mime_type: .txt
)!
// Save the file
file_id := fs_factory.fs_file.set(text_file)!
println('Created text file with ID: ${file_id}')
fs_factory.fs_file.set(mut text_file)!
// Associate file with documents directory
fs_factory.fs_file.add_to_directory(text_file.id, docs_dir.id)!
println('Created text file with ID: ${text_file.id}')
// List all directories in the filesystem
dirs := fs_factory.fs_dir.list_by_filesystem(fs_id)!
println('\nAll directories in filesystem:')
for dir in dirs {
println('- ${dir.name} (ID: ${dir.id})')
}
// Demonstrate filesystem navigation using find
mut fs := fs_factory.fs.get(my_fs.id)!
// List all files in the documents directory
files := fs_factory.fs_file.list_by_directory(docs_dir_id)!
println('\nFiles in documents directory:')
for file in files {
println('- ${file.name} (ID: ${file.id}, Size: ${file.size_bytes} bytes)')
println('\nAll items in filesystem:')
results := fs.find('/', recursive: true)!
for result in results {
type_str := match result.result_type {
.file { 'FILE' }
.directory { 'DIR ' }
.symlink { 'LINK' }
}
println('- ${type_str}: ${result.path} (ID: ${result.id})')
// Get the file's content from its blobs
if file.blobs.len > 0 {
blob := fs_factory.fs_blob.get(file.blobs[0])!
content := blob.data.bytestr()
println(' Content: "${content}"')
// If it's a file, show its content
if result.result_type == .file {
file := fs_factory.fs_file.get(result.id)!
if file.blobs.len > 0 {
blob := fs_factory.fs_blob.get(file.blobs[0])!
content := blob.data.bytestr()
println(' Content: "${content}"')
}
}
}

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env vshell
// HeroFS RPC Example
// This example demonstrates how to start the HeroFS RPC server
import freeflowuniverse.herolib.hero.herofs.rpc { ServerArgs, start }
fn main() {
// Example 1: Start RPC server with Unix socket
println('Starting HeroFS RPC server with Unix socket...')
mut args := ServerArgs{
socket_path: '/tmp/herofs'
http_port: 0 // No HTTP server
}
start(args)!
println('HeroFS RPC server started successfully on Unix socket: ${args.socket_path}')
// Example 2: Start RPC server with HTTP
println('\nStarting HeroFS RPC server with HTTP on port 8080...')
args = ServerArgs{
socket_path: '/tmp/herofs'
http_port: 8080
}
start(args)!
println('HeroFS RPC server started successfully on HTTP port: ${args.http_port}')
}

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
import freeflowuniverse.herolib.hero.herofs
import os
// Example demonstrating HeroFS import/export functionality
// This shows how to import files from real filesystem to VFS and export them back
fn main() {
// Initialize the HeroFS factory
mut fs_factory := herofs.new()!
println('HeroFS factory initialized')
// Create a new filesystem
mut my_fs := fs_factory.fs.new(
name: 'import_export_demo'
description: 'Demonstration filesystem for import/export'
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
)!
// Save the filesystem
fs_factory.fs.set(mut my_fs)!
println('Created filesystem: ${my_fs.name} with ID: ${my_fs.id}')
// Create root directory
mut root_dir := fs_factory.fs_dir.new(
name: 'root'
fs_id: my_fs.id
parent_id: 0 // Root has no parent
)!
fs_factory.fs_dir.set(mut root_dir)!
my_fs.root_dir_id = root_dir.id
fs_factory.fs.set(mut my_fs)!
// Get filesystem instance for operations
mut fs := fs_factory.fs.get(my_fs.id)!
fs.factory = &fs_factory
// Create temporary test directory and files on real filesystem
test_dir := '/tmp/herofs_import_test_${my_fs.id}'
os.mkdir_all(test_dir)!
defer {
os.rmdir_all(test_dir) or {}
}
// Create test files
test_file1 := os.join_path(test_dir, 'hello.txt')
test_file2 := os.join_path(test_dir, 'example.v')
test_file3 := os.join_path(test_dir, 'README.md')
// Create subdirectory with files
sub_dir := os.join_path(test_dir, 'docs')
os.mkdir_all(sub_dir)!
test_file4 := os.join_path(sub_dir, 'guide.md')
// Write test content
os.write_file(test_file1, 'Hello, HeroFS Import/Export!')!
os.write_file(test_file2, 'fn main() {\n println("Imported V code!")\n}')!
os.write_file(test_file3, '# HeroFS Demo\n\nThis file was imported from real filesystem.')!
os.write_file(test_file4, '# User Guide\n\nThis is a guide in a subdirectory.')!
println('\n=== IMPORT OPERATIONS ===')
// Import single file
println('Importing single file: ${test_file1}')
fs.import(test_file1, '/imported_hello.txt', herofs.ImportOptions{
overwrite: true
preserve_meta: true
})!
// Import entire directory recursively
println('Importing directory: ${test_dir}')
fs.import(test_dir, '/imported_files', herofs.ImportOptions{
recursive: true
overwrite: true
preserve_meta: true
})!
// Verify imports
println('\nVerifying imported files...')
imported_results := fs.find('/', recursive: true)!
for result in imported_results {
type_str := match result.result_type {
.file { 'FILE' }
.directory { 'DIR ' }
.symlink { 'LINK' }
}
println('${type_str}: ${result.path}')
}
// Find specific file types
v_files := fs.find('/', recursive: true, include_patterns: ['*.v'])!
println('\nFound ${v_files.len} V files:')
for file in v_files {
println(' - ${file.path}')
}
md_files := fs.find('/', recursive: true, include_patterns: ['*.md'])!
println('\nFound ${md_files.len} Markdown files:')
for file in md_files {
println(' - ${file.path}')
}
println('\n=== EXPORT OPERATIONS ===')
// Create export directory
export_dir := '/tmp/herofs_export_test_${my_fs.id}'
os.mkdir_all(export_dir)!
defer {
os.rmdir_all(export_dir) or {}
}
// Export single file
println('Exporting single file to: ${export_dir}/exported_hello.txt')
fs.export('/imported_hello.txt', os.join_path(export_dir, 'exported_hello.txt'),
herofs.ExportOptions{
overwrite: true
preserve_meta: true
})!
// Export entire directory
println('Exporting directory to: ${export_dir}/exported_files')
fs.export('/imported_files', os.join_path(export_dir, 'exported_files'),
herofs.ExportOptions{
recursive: true
overwrite: true
preserve_meta: true
})!
// Verify exports
println('\nVerifying exported files...')
if os.exists(os.join_path(export_dir, 'exported_hello.txt')) {
content := os.read_file(os.join_path(export_dir, 'exported_hello.txt'))!
println(' exported_hello.txt: "${content}"')
}
if os.exists(os.join_path(export_dir, 'exported_files', 'hello.txt')) {
content := os.read_file(os.join_path(export_dir, 'exported_files', 'hello.txt'))!
println(' exported_files/hello.txt: "${content}"')
}
if os.exists(os.join_path(export_dir, 'exported_files', 'example.v')) {
content := os.read_file(os.join_path(export_dir, 'exported_files', 'example.v'))!
println(' exported_files/example.v contains: ${content.split('\n')[0]}')
}
if os.exists(os.join_path(export_dir, 'exported_files', 'docs', 'guide.md')) {
content := os.read_file(os.join_path(export_dir, 'exported_files', 'docs', 'guide.md'))!
println(' exported_files/docs/guide.md: "${content.split('\n')[0]}"')
}
println('\n=== MIME TYPE DETECTION ===')
// Test MIME type detection
test_extensions := ['.txt', '.v', '.md', '.html', '.json', '.png', '.unknown']
for ext in test_extensions {
mime_type := herofs.extension_to_mime_type(ext)
println('Extension ${ext} -> MIME type: ${mime_type}')
}
println('\n=== OVERWRITE BEHAVIOR TEST ===')
// Test overwrite behavior
test_overwrite_file := os.join_path(test_dir, 'overwrite_test.txt')
os.write_file(test_overwrite_file, 'Original content')!
// Import without overwrite
fs.import(test_overwrite_file, '/overwrite_test.txt', herofs.ImportOptions{
overwrite: false
})!
// Try to import again without overwrite (should fail silently or with error)
println('Testing import without overwrite (should fail)...')
fs.import(test_overwrite_file, '/overwrite_test.txt', herofs.ImportOptions{
overwrite: false
}) or {
println(' Import correctly failed when overwrite=false: ${err}')
}
// Update file content and import with overwrite
os.write_file(test_overwrite_file, 'Updated content')!
fs.import(test_overwrite_file, '/overwrite_test.txt', herofs.ImportOptions{
overwrite: true
})!
println(' Import with overwrite=true succeeded')
// Test export overwrite behavior
export_test_file := os.join_path(export_dir, 'overwrite_export_test.txt')
// Export first time
fs.export('/overwrite_test.txt', export_test_file, herofs.ExportOptions{
overwrite: false
})!
// Try to export again without overwrite (should fail)
println('Testing export without overwrite (should fail)...')
fs.export('/overwrite_test.txt', export_test_file, herofs.ExportOptions{
overwrite: false
}) or {
println(' Export correctly failed when overwrite=false: ${err}')
}
// Export with overwrite
fs.export('/overwrite_test.txt', export_test_file, herofs.ExportOptions{
overwrite: true
})!
println(' Export with overwrite=true succeeded')
// Verify final content
final_content := os.read_file(export_test_file)!
println('Final exported content: "${final_content}"')
println('\n Import/Export demonstration completed successfully!')
println('All files have been imported to VFS and exported back to real filesystem.')
println('Temporary directories will be cleaned up automatically.')
}