feat: Improve directory copy functionality and add error handling

- Refactor `Directory.copy()` to use a struct for arguments,
  improving readability and maintainability.
- Add comprehensive error handling to `Directory.copy()`,
  preventing unexpected failures and providing informative error
  messages.  This includes handling cases where the source is not
  a directory, or a source and destination path are the same.
- Implement recursive copying of directory contents, including files
  and symlinks.
- Add unit tests to cover the new `copy` functionality and error
  handling.
- Update `OurDBVFS.copy()` to utilize the improved `Directory.copy()`
  method and add input validation.
This commit is contained in:
Mahmoud Emad
2025-02-24 13:55:16 +02:00
parent 988602f90f
commit 4fe1e70881
3 changed files with 183 additions and 46 deletions

View File

@@ -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 {
@@ -313,48 +313,147 @@ fn move_children_recursive(mut dir Directory) ! {
}
}
pub fn (mut dir Directory) copy(src_name string, dst_name string) !Directory {
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 new_entry := FSEntry(dir)
current_time := time.now().unix()
mut args := args_
for child_id in dir.children {
if mut entry := dir.myvfs.load_entry(child_id) {
if entry.metadata.name == src_name {
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
new_entry = entry
// Create a new copy
if entry is Directory {
mut entry_ := entry as Directory
mut src_dir := entry as Directory
// Create a new directory with copied metadata
current_time := time.now().unix()
mut new_dir := Directory{
metadata: entry_.metadata
children: entry_.children
parent_id: entry_.parent_id
myvfs: entry_.myvfs
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
}
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
// Recursively copy children
copy_children_recursive(mut src_dir, mut new_dir)!
dir.children << new_dir.metadata.id
dir.metadata.modified_at = current_time
dir.metadata.id = dir.myvfs.save_entry(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()!
dir.myvfs.save_entry(new_dir)!
return new_dir
}
return &new_dir
}
}
}
if !found {
return error('${src_name} not found')
return error('${args.src_entry_name} not found')
}
return &new_entry as Directory
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 {

View File

@@ -137,11 +137,32 @@ pub fn (mut self OurDBVFS) rename(old_path string, new_path string) !vfscore.FSE
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)!
copied_dir := src_parent_dir.copy(src_name, dst_name)!
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)
}

View File

@@ -81,6 +81,23 @@ fn test_directory_move() ! {
assert vfs.exists('/test_dir2/test.txt') == true
}
fn test_directory_copy() ! {
mut vfs, data_dir, meta_dir := setup_vfs()!
defer {
teardown_vfs(data_dir, meta_dir)
}
vfs.dir_create('/test_dir')!
vfs.file_create('/test_dir/test.txt')!
// Perform copy
copied_dir := vfs.copy('/test_dir', '/test_dir2')!
assert copied_dir.get_metadata().name == 'test_dir2'
assert vfs.exists('/test_dir') == true
assert vfs.exists('/test_dir/test.txt') == true
assert vfs.exists('/test_dir2/test.txt') == true
}
fn test_nested_directory_move() ! {
mut vfs, data_dir, meta_dir := setup_vfs()!
defer {