feat: Add file move operation

- Added `move` operation to `Directory` to rename files and
  directories within the same directory.  This improves
  file management capabilities.
- Updated `VFS` interface to include `move` function with
  FSEntry return type for consistency. This allows for
  retrieving metadata of the moved file/directory.
- Implemented `move` operation for `LocalVFS`, `OurDBVFS`, and
  `NestedVFS`.  This provides consistent file move
  functionality across different VFS implementations.
- Added tests for the new move functionality in
  `vfsourdb_test.v`. This ensures the correct behavior of the
  new feature.
This commit is contained in:
Mahmoud Emad
2025-02-23 22:26:05 +02:00
parent aeeacc877b
commit c0b57e2a01
7 changed files with 77 additions and 38 deletions

View File

@@ -240,6 +240,31 @@ 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
// Create a new directory entry with the new name
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
}
// get_children returns all immediate children as FSEntry objects
pub fn (mut dir Directory) children(recursive bool) ![]FSEntry {
mut entries := []FSEntry{}

View File

@@ -11,8 +11,8 @@ pub mut:
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
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
}
@@ -57,7 +57,7 @@ pub fn (mut fs OurDBFS) get_root() !&Directory {
}
// load_entry loads an entry from the database by ID and sets up parent references
fn (mut fs OurDBFS) load_entry(id u32) !FSEntry {
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)

View File

@@ -65,7 +65,7 @@ mut:
get(path string) !FSEntry
rename(old_path string, new_path string) !
copy(src_path string, dst_path string) !
move(src_path string, dst_path string) !
move(src_path string, dst_path string) !FSEntry
delete(path string) !
// Symlink operations

View File

@@ -257,7 +257,7 @@ pub fn (myvfs LocalVFS) copy(src_path string, dst_path string) ! {
os.cp(abs_src, abs_dst) or { return error('Failed to copy ${src_path} to ${dst_path}: ${err}') }
}
pub fn (myvfs LocalVFS) move(src_path string, dst_path string) ! {
pub fn (myvfs LocalVFS) move(src_path string, dst_path string) !FSEntry {
abs_src := myvfs.abs_path(src_path)
abs_dst := myvfs.abs_path(dst_path)
@@ -269,6 +269,13 @@ pub fn (myvfs LocalVFS) move(src_path string, dst_path string) ! {
}
os.mv(abs_src, abs_dst) or { return error('Failed to move ${src_path} to ${dst_path}: ${err}') }
metadata := myvfs.os_attr_to_metadata(abs_dst) or {
return error('Failed to get metadata: ${err}')
}
return LocalFSEntry{
path: dst_path
metadata: metadata
}
}
// Generic delete operation that handles all types

View File

@@ -168,19 +168,10 @@ pub fn (mut self NestedVFS) copy(src_path string, dst_path string) ! {
return dst_impl.file_write(dst_rel_path, data)
}
pub fn (mut self NestedVFS) move(src_path string, dst_path string) ! {
pub fn (mut self NestedVFS) move(src_path string, dst_path string) !vfscore.FSEntry {
mut src_impl, src_rel_path := self.find_vfs(src_path)!
mut dst_impl, dst_rel_path := self.find_vfs(dst_path)!
if src_impl == dst_impl {
_, dst_rel_path := self.find_vfs(dst_path)!
return src_impl.move(src_rel_path, dst_rel_path)
}
// Move across different VFS implementations
// TODO: Q: What if it's not file? What if it's a symlink or directory?
data := src_impl.file_read(src_rel_path)!
dst_impl.file_create(dst_rel_path)!
return dst_impl.file_write(dst_rel_path, data)
}
pub fn (mut self NestedVFS) link_create(target_path string, link_path string) !vfscore.FSEntry {
@@ -238,7 +229,7 @@ fn (e &MountEntry) get_metadata() vfscore.Metadata {
}
fn (e &MountEntry) get_path() string {
return "/${e.metadata.name.trim_left('/')}"
return '/${e.metadata.name.trim_left('/')}'
}
// is_dir returns true if the entry is a directory

View File

@@ -135,8 +135,14 @@ 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) ! {
return error('Not implemented')
pub fn (mut self OurDBVFS) move(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)!
return convert_to_vfscore_entry(moved_dir)
}
pub fn (mut self OurDBVFS) link_create(target_path string, link_path string) !vfscore.FSEntry {

View File

@@ -42,35 +42,45 @@ fn test_vfsourdb() ! {
read_content := vfs.file_read('/test_dir/test.txt')!
assert read_content == test_content
// Test directory 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_dir')!
mut entries := vfs.dir_list('/test_dir2')!
assert entries.len == 1
assert entries[0].get_metadata().name == 'test.txt'
// Test exists
assert vfs.exists('/test_dir') == true
assert vfs.exists('/test_dir/test.txt') == true
assert vfs.exists('/test_dir2') == true
assert vfs.exists('/test_dir2/test.txt') == true
assert vfs.exists('/nonexistent') == false
// Test symlink creation and reading
vfs.link_create('/test_dir/test.txt', '/test_dir/test_link')!
link_target := vfs.link_read('/test_dir/test_link')!
assert link_target == '/test_dir/test.txt'
vfs.link_create('/test_dir2/test.txt', '/test_dir2/test_link')!
link_target := vfs.link_read('/test_dir2/test_link')!
assert link_target == '/test_dir2/test.txt'
// Test symlink deletion
vfs.link_delete('/test_dir/test_link')!
assert vfs.exists('/test_dir/test_link') == false
vfs.link_delete('/test_dir2/test_link')!
assert vfs.exists('/test_dir2/test_link') == false
// Test file deletion
vfs.file_delete('/test_dir/test.txt')!
assert vfs.exists('/test_dir/test.txt') == false
vfs.file_delete('/test_dir2/test.txt')!
assert vfs.exists('/test_dir2/test.txt') == false
assert vfs.exists('/test_dir2') == true
entries = vfs.dir_list('/test_dir')!
entries = vfs.dir_list('/test_dir2')!
assert entries.len == 0
// Test directory deletion
vfs.dir_delete('/test_dir')!
assert vfs.exists('/test_dir') == false
vfs.dir_delete('/test_dir2')!
assert vfs.exists('/test_dir2') == false
println('Test completed successfully!')
}