feat: Move the vfs dirs to the herolib
This commit is contained in:
38
lib/vfs/ourdb_fs/common.v
Normal file
38
lib/vfs/ourdb_fs/common.v
Normal 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
10
lib/vfs/ourdb_fs/data.v
Normal 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)
|
||||
}
|
||||
290
lib/vfs/ourdb_fs/directory.v
Normal file
290
lib/vfs/ourdb_fs/directory.v
Normal 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
202
lib/vfs/ourdb_fs/encoder.v
Normal 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
|
||||
}
|
||||
}
|
||||
38
lib/vfs/ourdb_fs/factory.v
Normal file
38
lib/vfs/ourdb_fs/factory.v
Normal 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
31
lib/vfs/ourdb_fs/file.v
Normal 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
159
lib/vfs/ourdb_fs/readme.md
Normal 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
|
||||
35
lib/vfs/ourdb_fs/symlink.v
Normal file
35
lib/vfs/ourdb_fs/symlink.v
Normal 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
97
lib/vfs/ourdb_fs/vfs.v
Normal 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
127
lib/vfs/vfscore/README.md
Normal 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
|
||||
58
lib/vfs/vfscore/interface.v
Normal file
58
lib/vfs/vfscore/interface.v
Normal 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
308
lib/vfs/vfscore/local.v
Normal 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}') }
|
||||
}
|
||||
65
lib/vfs/vfscore/local_test.v
Normal file
65
lib/vfs/vfscore/local_test.v
Normal 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 {}
|
||||
}
|
||||
84
lib/vfs/vfsnested/nested_test.v
Normal file
84
lib/vfs/vfsnested/nested_test.v
Normal 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!')
|
||||
}
|
||||
4
lib/vfs/vfsnested/readme.md
Normal file
4
lib/vfs/vfsnested/readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# VFS Overlay
|
||||
|
||||
This virtual filesystem combines multiple other VFS'es
|
||||
|
||||
190
lib/vfs/vfsnested/vfsnested.v
Normal file
190
lib/vfs/vfsnested/vfsnested.v
Normal 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}'
|
||||
}
|
||||
8
lib/vfs/vfsourdb/readme.md
Normal file
8
lib/vfs/vfsourdb/readme.md
Normal 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
284
lib/vfs/vfsourdb/vfsourdb.v
Normal 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
|
||||
}
|
||||
69
lib/vfs/vfsourdb/vfsourdb_test.v
Normal file
69
lib/vfs/vfsourdb/vfsourdb_test.v
Normal 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!')
|
||||
}
|
||||
Reference in New Issue
Block a user