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