feat: Enhance VFS with file and directory manipulation
- Add `move`, `copy`, and `rename` methods to `Directory` and `File` for improved file system management. - Refactor `move` operation in `Directory` for better error handling and support for recursive directory moves. Improves robustness and clarity of the move operation. - Implement a `MoveDirArgs` struct to improve the clarity and maintainability of the `move` function arguments. - Remove unnecessary `save()` calls for improved performance. - Add comprehensive tests for the new and improved file system operations. Ensures reliability and correctness of the added functionality.
This commit is contained in:
@@ -93,22 +93,22 @@ pub fn (mut dir Directory) read(name string) !string {
|
||||
}
|
||||
|
||||
// str returns a formatted string of directory contents (non-recursive)
|
||||
pub fn (mut dir Directory) str() string {
|
||||
mut result := '${dir.metadata.name}/\n'
|
||||
// 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
|
||||
}
|
||||
// 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 {
|
||||
@@ -240,43 +240,112 @@ pub fn (mut dir Directory) rm(name string) ! {
|
||||
dir.myvfs.save_entry(dir)!
|
||||
}
|
||||
|
||||
pub fn (mut dir Directory) move(src_name string, dst_name string) !FSEntry {
|
||||
mut found := false
|
||||
mut new_entry := FSEntry(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)!
|
||||
new_entry = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return error('${src_name} not found')
|
||||
}
|
||||
|
||||
return new_entry
|
||||
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 (mut dir Directory) rename(src_name string, dst_name string) !FSEntry {
|
||||
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 fn (mut dir Directory) copy(src_name string, dst_name string) !Directory {
|
||||
mut found := false
|
||||
mut new_entry := FSEntry(dir)
|
||||
current_time := time.now().unix()
|
||||
|
||||
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)!
|
||||
new_entry = entry
|
||||
break
|
||||
// Create a new copy
|
||||
if entry is Directory {
|
||||
mut entry_ := entry as Directory
|
||||
mut new_dir := Directory{
|
||||
metadata: entry_.metadata
|
||||
children: entry_.children
|
||||
parent_id: entry_.parent_id
|
||||
myvfs: entry_.myvfs
|
||||
}
|
||||
|
||||
new_dir.metadata.id = entry_.myvfs.get_next_id()
|
||||
new_dir.metadata.name = dst_name
|
||||
new_dir.metadata.created_at = current_time
|
||||
new_dir.metadata.modified_at = current_time
|
||||
new_dir.metadata.accessed_at = current_time
|
||||
|
||||
dir.children << new_dir.metadata.id
|
||||
dir.metadata.modified_at = current_time
|
||||
dir.metadata.id = dir.myvfs.save_entry(dir)!
|
||||
|
||||
dir.myvfs.save_entry(new_dir)!
|
||||
return new_dir
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +354,31 @@ pub fn (mut dir Directory) rename(src_name string, dst_name string) !FSEntry {
|
||||
return error('${src_name} not found')
|
||||
}
|
||||
|
||||
return new_entry
|
||||
return &new_entry as Directory
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -25,6 +25,32 @@ pub fn (mut f File) write(content string) ! {
|
||||
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
|
||||
|
||||
@@ -84,8 +84,6 @@ pub fn (mut self OurDBVFS) dir_create(path string) !vfscore.FSEntry {
|
||||
|
||||
mut parent_dir := self.get_directory(parent_path)!
|
||||
mut new_dir := parent_dir.mkdir(dir_name)!
|
||||
new_dir.save()! // Ensure the directory is saved
|
||||
|
||||
return convert_to_vfscore_entry(new_dir)
|
||||
}
|
||||
|
||||
@@ -137,17 +135,44 @@ pub fn (mut self OurDBVFS) rename(old_path string, new_path string) !vfscore.FSE
|
||||
return convert_to_vfscore_entry(renamed_dir)
|
||||
}
|
||||
|
||||
pub fn (mut self OurDBVFS) copy(src_path string, dst_path string) ! {
|
||||
return error('Not implemented')
|
||||
}
|
||||
|
||||
pub fn (mut self OurDBVFS) move(src_path string, dst_path string) !vfscore.FSEntry {
|
||||
pub fn (mut self OurDBVFS) copy(src_path string, dst_path string) !vfscore.FSEntry {
|
||||
src_parent_path := os.dir(src_path)
|
||||
src_name := os.base(src_path)
|
||||
dst_name := os.base(dst_path)
|
||||
|
||||
mut src_parent_dir := self.get_directory(src_parent_path)!
|
||||
moved_dir := src_parent_dir.move(src_name, dst_name)!
|
||||
copied_dir := src_parent_dir.copy(src_name, dst_name)!
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,96 +1,123 @@
|
||||
module vfsourdb
|
||||
|
||||
import os
|
||||
import rand
|
||||
|
||||
fn test_vfsourdb() ! {
|
||||
println('Testing OurDB VFS...')
|
||||
|
||||
// Create test directories
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_test_data')
|
||||
test_meta_dir := os.join_path(os.temp_dir(), 'vfsourdb_test_meta')
|
||||
fn setup_vfs() !(&OurDBVFS, 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)}')
|
||||
|
||||
os.mkdir_all(test_data_dir)!
|
||||
os.mkdir_all(test_meta_dir)!
|
||||
|
||||
mut vfs := new(test_data_dir, test_meta_dir)!
|
||||
return vfs, test_data_dir, test_meta_dir
|
||||
}
|
||||
|
||||
fn teardown_vfs(data_dir string, meta_dir string) {
|
||||
os.rmdir_all(data_dir) or {}
|
||||
os.rmdir_all(meta_dir) or {}
|
||||
}
|
||||
|
||||
fn test_root_directory() ! {
|
||||
mut vfs, data_dir, meta_dir := setup_vfs()!
|
||||
defer {
|
||||
os.rmdir_all(test_data_dir) or {}
|
||||
os.rmdir_all(test_meta_dir) or {}
|
||||
teardown_vfs(data_dir, meta_dir)
|
||||
}
|
||||
|
||||
// Create VFS instance
|
||||
mut vfs := new(test_data_dir, test_meta_dir)!
|
||||
|
||||
// Test root directory
|
||||
mut root := vfs.root_get()!
|
||||
assert root.get_metadata().file_type == .directory
|
||||
assert root.get_metadata().name == ''
|
||||
}
|
||||
|
||||
// Test directory creation
|
||||
fn test_directory_operations() ! {
|
||||
mut vfs, data_dir, meta_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir, meta_dir)
|
||||
}
|
||||
|
||||
// Test creation
|
||||
mut test_dir := vfs.dir_create('/test_dir')!
|
||||
assert test_dir.get_metadata().name == 'test_dir'
|
||||
assert test_dir.get_metadata().file_type == .directory
|
||||
|
||||
// Test file creation and writing
|
||||
// Test listing
|
||||
entries := vfs.dir_list('/')!
|
||||
assert entries.any(it.get_metadata().name == 'test_dir')
|
||||
}
|
||||
|
||||
fn test_file_operations() ! {
|
||||
mut vfs, data_dir, meta_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir, meta_dir)
|
||||
}
|
||||
|
||||
vfs.dir_create('/test_dir')!
|
||||
|
||||
// Test file creation
|
||||
mut test_file := vfs.file_create('/test_dir/test.txt')!
|
||||
assert test_file.get_metadata().name == 'test.txt'
|
||||
assert test_file.get_metadata().file_type == .file
|
||||
|
||||
// Test writing/reading
|
||||
test_content := 'Hello, World!'.bytes()
|
||||
vfs.file_write('/test_dir/test.txt', test_content)!
|
||||
assert vfs.file_read('/test_dir/test.txt')! == test_content
|
||||
}
|
||||
|
||||
// Test file reading
|
||||
read_content := vfs.file_read('/test_dir/test.txt')!
|
||||
assert read_content == test_content
|
||||
fn test_directory_move() ! {
|
||||
mut vfs, data_dir, meta_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir, meta_dir)
|
||||
}
|
||||
|
||||
// Test directory move
|
||||
vfs.dir_create('/test_dir')!
|
||||
vfs.file_create('/test_dir/test.txt')!
|
||||
|
||||
// Perform move
|
||||
moved_dir := vfs.move('/test_dir', '/test_dir2')!
|
||||
|
||||
assert moved_dir.get_metadata().name == 'test_dir2'
|
||||
assert moved_dir.get_metadata().file_type == .directory
|
||||
|
||||
assert vfs.exists('/test_dir') == false
|
||||
assert vfs.exists('/test_dir2/test.txt') == true
|
||||
}
|
||||
|
||||
// Test directory listing
|
||||
mut entries := vfs.dir_list('/test_dir2')!
|
||||
assert entries.len == 1
|
||||
assert entries[0].get_metadata().name == 'test.txt'
|
||||
fn test_nested_directory_move() ! {
|
||||
mut vfs, data_dir, meta_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir, meta_dir)
|
||||
}
|
||||
|
||||
// Test directory rename
|
||||
renamed_dir := vfs.rename('/test_dir2', '/test_dir')!
|
||||
assert moved_dir.get_metadata().name == 'test_dir2'
|
||||
assert moved_dir.get_metadata().file_type == .directory
|
||||
vfs.dir_create('/test_dir2')!
|
||||
vfs.dir_create('/test_dir2/folder1')!
|
||||
vfs.file_create('/test_dir2/folder1/file1.txt')!
|
||||
vfs.dir_create('/test_dir2/folder2')!
|
||||
|
||||
// Test directory listing
|
||||
entries = vfs.dir_list('/test_dir')!
|
||||
assert entries.len == 1
|
||||
assert entries[0].get_metadata().name == 'test.txt'
|
||||
// Move folder1 into folder2
|
||||
moved_dir := vfs.move('/test_dir2/folder1', '/test_dir2/folder2/folder1')!
|
||||
assert moved_dir.get_metadata().name == 'folder1'
|
||||
assert vfs.exists('/test_dir2/folder2/folder1/file1.txt') == true
|
||||
}
|
||||
|
||||
// Test exists
|
||||
assert vfs.exists('/test_dir') == true
|
||||
assert vfs.exists('/test_dir/test.txt') == true
|
||||
assert vfs.exists('/nonexistent') == false
|
||||
fn test_deletion_operations() ! {
|
||||
mut vfs, data_dir, meta_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir, meta_dir)
|
||||
}
|
||||
|
||||
// Test symlink creation and reading
|
||||
vfs.link_create('/test_dir/test.txt', '/test_dir/test_link')!
|
||||
link_target := vfs.link_read('/test_dir/test_link')!
|
||||
assert link_target == '/test_dir/test.txt'
|
||||
|
||||
// Test symlink deletion
|
||||
vfs.link_delete('/test_dir/test_link')!
|
||||
assert vfs.exists('/test_dir/test_link') == false
|
||||
vfs.dir_create('/test_dir')!
|
||||
vfs.file_create('/test_dir/test.txt')!
|
||||
|
||||
// Test file deletion
|
||||
vfs.file_delete('/test_dir/test.txt')!
|
||||
assert vfs.exists('/test_dir/test.txt') == false
|
||||
assert vfs.exists('/test_dir') == true
|
||||
|
||||
entries = vfs.dir_list('/test_dir')!
|
||||
assert entries.len == 0
|
||||
|
||||
// Test directory deletion
|
||||
vfs.dir_delete('/test_dir')!
|
||||
assert vfs.exists('/test_dir') == false
|
||||
|
||||
println('Test completed successfully!')
|
||||
}
|
||||
|
||||
// Add more test functions for other operations like:
|
||||
// - test_directory_copy()
|
||||
// - test_symlink_operations()
|
||||
// - test_directory_rename()
|
||||
// - test_file_metadata()
|
||||
|
||||
Reference in New Issue
Block a user