...
This commit is contained in:
@@ -301,6 +301,7 @@ pub fn fs_handle(mut f FSFactory, rpcid int, servercontext map[string]string, us
|
||||
res := f.fs.list(args)!
|
||||
return new_response(rpcid, json.encode(res))
|
||||
}
|
||||
|
||||
else {
|
||||
console.print_stderr('Method not found on fs: ${method}')
|
||||
return new_error(rpcid,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
module herofs
|
||||
|
||||
import freeflowuniverse.herolib.hero.db
|
||||
|
||||
|
||||
// CopyOptions provides options for copy operations
|
||||
@[params]
|
||||
pub struct CopyOptions {
|
||||
|
||||
447
lib/hero/herofs/fs_tools_cp_test.v
Normal file
447
lib/hero/herofs/fs_tools_cp_test.v
Normal file
@@ -0,0 +1,447 @@
|
||||
module herofs
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import freeflowuniverse.herolib.hero.db
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
fn test_cp_file() ! {
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a source directory and a file
|
||||
src_dir_id := fs.factory.fs_dir.create_path(fs.id, '/src')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'file content')!
|
||||
blob = fs.factory.fs_blob.set(blob)!
|
||||
mut file := fs.factory.fs_file.new(
|
||||
name: 'test_file.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, src_dir_id)!
|
||||
|
||||
// 2. Create a destination directory
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
|
||||
// 3. Copy the file
|
||||
fs.cp('/src/test_file.txt', '/dest/', FindOptions{}, CopyOptions{})!
|
||||
|
||||
// 4. Verify the file is copied
|
||||
dest_dir := fs.factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.files.len == 1
|
||||
copied_file := fs.factory.fs_file.get(dest_dir.files[0])!
|
||||
assert copied_file.name == 'test_file.txt'
|
||||
assert copied_file.blobs[0] == blob.id // Should reference the same blob by default
|
||||
}
|
||||
|
||||
fn test_cp_file_overwrite() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a source directory and a file
|
||||
src_dir_id := fs.factory.fs_dir.create_path(fs.id, '/src')!
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'original content')!
|
||||
blob1 = fs.factory.fs_blob.set(blob1)!
|
||||
mut file1 := fs.factory.fs_file.new(
|
||||
name: 'overwrite_file.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob1.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file1 = fs.factory.fs_file.set(file1)!
|
||||
fs.factory.fs_file.add_to_directory(file1.id, src_dir_id)!
|
||||
|
||||
// 2. Create a destination directory and an existing file with the same name
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
mut blob_existing := fs.factory.fs_blob.new(data: 'existing content')!
|
||||
blob_existing = fs.factory.fs_blob.set(blob_existing)!
|
||||
mut existing_file := fs.factory.fs_file.new(
|
||||
name: 'overwrite_file.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob_existing.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
existing_file = fs.factory.fs_file.set(existing_file)!
|
||||
fs.factory.fs_file.add_to_directory(existing_file.id, dest_dir_id)!
|
||||
|
||||
// 3. Copy the file with overwrite enabled
|
||||
fs.cp('/src/overwrite_file.txt', '/dest/', FindOptions{}, CopyOptions{overwrite: true})!
|
||||
|
||||
// 4. Verify the file is overwritten
|
||||
dest_dir := fs.factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.files.len == 1
|
||||
copied_file := fs.factory.fs_file.get(dest_dir.files[0])!
|
||||
assert copied_file.name == 'overwrite_file.txt'
|
||||
assert copied_file.blobs[0] == blob1.id // Should now reference the new blob
|
||||
}
|
||||
|
||||
fn test_cp_file_no_overwrite_error() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a source directory and a file
|
||||
src_dir_id := fs.factory.fs_dir.create_path(fs.id, '/src')!
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'original content')!
|
||||
blob1 = fs.factory.fs_blob.set(blob1)!
|
||||
mut file1 := fs.factory.fs_file.new(
|
||||
name: 'no_overwrite_file.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob1.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file1 = fs.factory.fs_file.set(file1)!
|
||||
fs.factory.fs_file.add_to_directory(file1.id, src_dir_id)!
|
||||
|
||||
// 2. Create a destination directory and an existing file with the same name
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
mut blob_existing := fs.factory.fs_blob.new(data: 'existing content')!
|
||||
blob_existing = fs.factory.fs_blob.set(blob_existing)!
|
||||
mut existing_file := fs.factory.fs_file.new(
|
||||
name: 'no_overwrite_file.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob_existing.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
existing_file = fs.factory.fs_file.set(existing_file)!
|
||||
fs.factory.fs_file.add_to_directory(existing_file.id, dest_dir_id)!
|
||||
|
||||
// 3. Attempt to copy the file without overwrite (should error)
|
||||
res := fs.cp('/src/no_overwrite_file.txt', '/dest/', FindOptions{}, CopyOptions{overwrite: false})
|
||||
assert res.err().msg().contains('already exists')
|
||||
}
|
||||
|
||||
fn test_cp_directory_recursive() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create source directory structure
|
||||
src_root_id := fs.factory.fs_dir.create_path(fs.id, '/src_root')!
|
||||
src_subdir_id := fs.factory.fs_dir.create_path(fs.id, '/src_root/subdir')!
|
||||
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'file1 content')!
|
||||
blob1 = fs.factory.fs_blob.set(blob1)!
|
||||
mut file1 := fs.factory.fs_file.new(
|
||||
name: 'file1.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob1.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file1 = fs.factory.fs_file.set(file1)!
|
||||
fs.factory.fs_file.add_to_directory(file1.id, src_root_id)!
|
||||
|
||||
mut blob2 := fs.factory.fs_blob.new(data: 'file2 content')!
|
||||
blob2 = fs.factory.fs_blob.set(blob2)!
|
||||
mut file2 := fs.factory.fs_file.new(
|
||||
name: 'file2.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob2.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file2 = fs.factory.fs_file.set(file2)!
|
||||
fs.factory.fs_file.add_to_directory(file2.id, src_subdir_id)!
|
||||
|
||||
// 2. Create destination root
|
||||
dest_root_id := fs.factory.fs_dir.create_path(fs.id, '/dest_root')!
|
||||
|
||||
// 3. Copy source_root to dest_root recursively
|
||||
fs.cp('/src_root', '/dest_root/', FindOptions{}, CopyOptions{recursive: true})!
|
||||
|
||||
// 4. Verify destination structure
|
||||
dest_root := fs.factory.fs_dir.get(dest_root_id)!
|
||||
assert dest_root.directories.len == 1 // Should contain 'src_root'
|
||||
copied_src_root_dir := fs.factory.fs_dir.get(dest_root.directories[0])!
|
||||
assert copied_src_root_dir.name == 'src_root'
|
||||
assert copied_src_root_dir.files.len == 1 // Should contain file1.txt
|
||||
|
||||
copied_subdir := fs.factory.fs_dir.get(copied_src_root_dir.directories[0])!
|
||||
assert copied_subdir.name == 'subdir'
|
||||
assert copied_subdir.files.len == 1 // Should contain file2.txt
|
||||
}
|
||||
|
||||
fn test_cp_directory_merge_overwrite() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create source directory structure
|
||||
src_dir_id := fs.factory.fs_dir.create_path(fs.id, '/src')!
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'src file content')!
|
||||
blob1 = fs.factory.fs_blob.set(blob1)!
|
||||
mut file1 := fs.factory.fs_file.new(
|
||||
name: 'file1.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob1.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file1 = fs.factory.fs_file.set(file1)!
|
||||
fs.factory.fs_file.add_to_directory(file1.id, src_dir_id)!
|
||||
|
||||
// 2. Create destination directory with an existing file and a new file
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
mut blob_existing := fs.factory.fs_blob.new(data: 'existing file content')!
|
||||
blob_existing = fs.factory.fs_blob.set(blob_existing)!
|
||||
mut existing_file := fs.factory.fs_file.new(
|
||||
name: 'file1.txt' // Same name as source file
|
||||
fs_id: fs.id
|
||||
blobs: [blob_existing.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
existing_file = fs.factory.fs_file.set(existing_file)!
|
||||
fs.factory.fs_file.add_to_directory(existing_file.id, dest_dir_id)!
|
||||
|
||||
mut blob_new_dest := fs.factory.fs_blob.new(data: 'new dest file content')!
|
||||
blob_new_dest = fs.factory.fs_blob.set(blob_new_dest)!
|
||||
mut new_dest_file := fs.factory.fs_file.new(
|
||||
name: 'file_only_in_dest.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob_new_dest.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
new_dest_file = fs.factory.fs_file.set(new_dest_file)!
|
||||
fs.factory.fs_file.add_to_directory(new_dest_file.id, dest_dir_id)!
|
||||
|
||||
// 3. Copy source directory to destination with overwrite (should merge and overwrite file1.txt)
|
||||
fs.cp('/src', '/dest/', FindOptions{}, CopyOptions{recursive: true, overwrite: true})!
|
||||
|
||||
// 4. Verify destination contents
|
||||
dest_dir := fs.factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.files.len == 2 // Should have file1.txt (overwritten) and file_only_in_dest.txt
|
||||
|
||||
mut found_file1 := false
|
||||
mut found_file_only_in_dest := false
|
||||
|
||||
for file_id in dest_dir.files {
|
||||
file := fs.factory.fs_file.get(file_id)!
|
||||
if file.name == 'file1.txt' {
|
||||
assert file.blobs[0] == blob1.id // Should be overwritten with source content
|
||||
found_file1 = true
|
||||
} else if file.name == 'file_only_in_dest.txt' {
|
||||
assert file.blobs[0] == blob_new_dest.id
|
||||
found_file_only_in_dest = true
|
||||
}
|
||||
}
|
||||
assert found_file1
|
||||
assert found_file_only_in_dest
|
||||
}
|
||||
|
||||
fn test_cp_file_to_non_existent_path() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a source file
|
||||
mut blob := fs.factory.fs_blob.new(data: 'content')!
|
||||
blob = fs.factory.fs_blob.set(blob)!
|
||||
mut file := fs.factory.fs_file.new(
|
||||
name: 'source.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, fs.root_dir_id)!
|
||||
|
||||
// 2. Copy the file to a non-existent path
|
||||
fs.cp('/source.txt', '/new_dir/new_file.txt', FindOptions{}, CopyOptions{})!
|
||||
|
||||
// 3. Verify the directory and file are created
|
||||
new_dir := fs.get_dir_by_absolute_path('/new_dir')!
|
||||
assert new_dir.files.len == 1
|
||||
copied_file := fs.factory.fs_file.get(new_dir.files[0])!
|
||||
assert copied_file.name == 'new_file.txt'
|
||||
assert copied_file.blobs[0] == blob.id
|
||||
}
|
||||
|
||||
fn test_cp_symlink() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a target file
|
||||
mut blob := fs.factory.fs_blob.new(data: 'target content')!
|
||||
blob = fs.factory.fs_blob.set(blob)!
|
||||
mut target_file := fs.factory.fs_file.new(
|
||||
name: 'target.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
target_file = fs.factory.fs_file.set(target_file)!
|
||||
fs.factory.fs_file.add_to_directory(target_file.id, fs.root_dir_id)!
|
||||
|
||||
// 2. Create a source symlink
|
||||
mut symlink := fs.factory.fs_symlink.new(
|
||||
name: 'link_to_target.txt'
|
||||
fs_id: fs.id
|
||||
parent_id: fs.root_dir_id
|
||||
target_id: target_file.id
|
||||
target_type: .file
|
||||
)!
|
||||
symlink = fs.factory.fs_symlink.set(symlink)!
|
||||
mut root_dir := fs.root_dir()!
|
||||
root_dir.symlinks << symlink.id
|
||||
fs.factory.fs_dir.set(root_dir)!
|
||||
|
||||
// 3. Create a destination directory
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
|
||||
// 4. Copy the symlink
|
||||
fs.cp('/link_to_target.txt', '/dest/', FindOptions{}, CopyOptions{})!
|
||||
|
||||
// 5. Verify the symlink is copied
|
||||
dest_dir := fs.factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.symlinks.len == 1
|
||||
copied_symlink := fs.factory.fs_symlink.get(dest_dir.symlinks[0])!
|
||||
assert copied_symlink.name == 'link_to_target.txt'
|
||||
assert copied_symlink.target_id == target_file.id
|
||||
assert copied_symlink.target_type == .file
|
||||
}
|
||||
|
||||
fn test_cp_symlink_overwrite() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a target file
|
||||
mut blob := fs.factory.fs_blob.new(data: 'target content')!
|
||||
blob = fs.factory.fs_blob.set(blob)!
|
||||
mut target_file := fs.factory.fs_file.new(
|
||||
name: 'target.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
target_file = fs.factory.fs_file.set(target_file)!
|
||||
fs.factory.fs_file.add_to_directory(target_file.id, fs.root_dir_id)!
|
||||
|
||||
// 2. Create a source symlink
|
||||
mut symlink1 := fs.factory.fs_symlink.new(
|
||||
name: 'link_to_target.txt'
|
||||
fs_id: fs.id
|
||||
parent_id: fs.root_dir_id
|
||||
target_id: target_file.id
|
||||
target_type: .file
|
||||
)!
|
||||
symlink1 = fs.factory.fs_symlink.set(symlink1)!
|
||||
mut root_dir := fs.root_dir()!
|
||||
root_dir.symlinks << symlink1.id
|
||||
fs.factory.fs_dir.set(root_dir)!
|
||||
|
||||
// 3. Create a destination directory and an existing symlink with the same name
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
mut other_target_file := fs.factory.fs_file.new(
|
||||
name: 'other_target.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
other_target_file = fs.factory.fs_file.set(other_target_file)!
|
||||
fs.factory.fs_file.add_to_directory(other_target_file.id, fs.root_dir_id)!
|
||||
|
||||
mut existing_symlink := fs.factory.fs_symlink.new(
|
||||
name: 'link_to_target.txt'
|
||||
fs_id: fs.id
|
||||
parent_id: dest_dir_id
|
||||
target_id: other_target_file.id
|
||||
target_type: .file
|
||||
)!
|
||||
existing_symlink = fs.factory.fs_symlink.set(existing_symlink)!
|
||||
mut dest_dir := fs.factory.fs_dir.get(dest_dir_id)!
|
||||
dest_dir.symlinks << existing_symlink.id
|
||||
fs.factory.fs_dir.set(dest_dir)!
|
||||
|
||||
// 4. Copy the symlink with overwrite enabled
|
||||
fs.cp('/link_to_target.txt', '/dest/', FindOptions{}, CopyOptions{overwrite: true})!
|
||||
|
||||
// 5. Verify the symlink is overwritten
|
||||
dest_dir = fs.factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.symlinks.len == 1
|
||||
copied_symlink := fs.factory.fs_symlink.get(dest_dir.symlinks[0])!
|
||||
assert copied_symlink.name == 'link_to_target.txt'
|
||||
assert copied_symlink.target_id == target_file.id // Should now point to the original target
|
||||
}
|
||||
|
||||
fn test_cp_file_copy_blobs_false() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a source directory and a file
|
||||
src_dir_id := fs.factory.fs_dir.create_path(fs.id, '/src')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'file content')!
|
||||
blob = fs.factory.fs_blob.set(blob)!
|
||||
mut file := fs.factory.fs_file.new(
|
||||
name: 'test_file.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, src_dir_id)!
|
||||
|
||||
// 2. Create a destination directory
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
|
||||
// 3. Copy the file with copy_blobs set to false
|
||||
fs.cp('/src/test_file.txt', '/dest/', FindOptions{}, CopyOptions{copy_blobs: false})!
|
||||
|
||||
// 4. Verify the file is copied and references the same blob
|
||||
dest_dir := fs.factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.files.len == 1
|
||||
copied_file := fs.factory.fs_file.get(dest_dir.files[0])!
|
||||
assert copied_file.name == 'test_file.txt'
|
||||
assert copied_file.blobs[0] == blob.id // Should reference the same blob
|
||||
}
|
||||
|
||||
fn test_cp_file_copy_blobs_true() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
defer {
|
||||
delete_fs_test() or {panic("bug")}
|
||||
}
|
||||
|
||||
// 1. Create a source directory and a file
|
||||
src_dir_id := fs.factory.fs_dir.create_path(fs.id, '/src')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'file content')!
|
||||
blob = fs.factory.fs_blob.set(blob)!
|
||||
mut file := fs.factory.fs_file.new(
|
||||
name: 'test_file.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, src_dir_id)!
|
||||
|
||||
// 2. Create a destination directory
|
||||
dest_dir_id := fs.factory.fs_dir.create_path(fs.id, '/dest')!
|
||||
|
||||
// 3. Copy the file with copy_blobs set to true
|
||||
fs.cp('/src/test_file.txt', '/dest/', FindOptions{}, CopyOptions{copy_blobs: true})!
|
||||
|
||||
// 4. Verify the file is copied and has a new blob
|
||||
dest_dir := fs.factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.files.len == 1
|
||||
copied_file := fs.factory.fs_file.get(dest_dir.files[0])!
|
||||
assert copied_file.name == 'test_file.txt'
|
||||
assert copied_file.blobs[0] != blob.id // Should have a new blob ID
|
||||
|
||||
// Verify the content of the new blob is the same
|
||||
new_blob := fs.factory.fs_blob.get(copied_file.blobs[0])!
|
||||
assert new_blob.data == 'file content'
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
module herofs
|
||||
|
||||
import os
|
||||
|
||||
|
||||
// FindResult represents the result of a filesystem search
|
||||
pub struct FindResult {
|
||||
pub mut:
|
||||
@@ -26,6 +29,8 @@ pub mut:
|
||||
follow_symlinks bool // Whether to follow symbolic links during search
|
||||
}
|
||||
|
||||
|
||||
|
||||
// find searches for filesystem objects starting from a given path
|
||||
//
|
||||
// Parameters:
|
||||
@@ -114,6 +119,11 @@ fn (mut self Fs) find_recursive(dir_id u32, current_path string, opts FindOption
|
||||
}
|
||||
}
|
||||
|
||||
// Stop if we have reached max_depth
|
||||
if opts.max_depth >= 0 && current_depth >= opts.max_depth {
|
||||
return
|
||||
}
|
||||
|
||||
// Get files in current directory
|
||||
for file_id in current_dir.files {
|
||||
println('DEBUG: Processing file ID ${file_id}')
|
||||
@@ -144,58 +154,64 @@ fn (mut self Fs) find_recursive(dir_id u32, current_path string, opts FindOption
|
||||
}
|
||||
} else {
|
||||
if symlink.target_type == .file {
|
||||
if self.factory.fs_file.exist(symlink.target_id)! {
|
||||
target_file := self.factory.fs_file.get(symlink.target_id)!
|
||||
target_file_path := join_path(current_path, target_file.name)
|
||||
// Check if we've already added this file to avoid duplicates
|
||||
mut found := false
|
||||
for result in results {
|
||||
if result.id == target_file.id && result.result_type == .file {
|
||||
found = true
|
||||
break
|
||||
if self.factory.fs_file.exist(symlink.target_id)! {
|
||||
target_file := self.factory.fs_file.get(symlink.target_id)!
|
||||
|
||||
// Resolve the absolute path of the target file
|
||||
target_abs_path := self.get_abs_path_for_item(target_file.id, .file)!
|
||||
|
||||
// Check if we've already added this file to avoid duplicates
|
||||
mut found := false
|
||||
for result in results {
|
||||
if result.id == target_file.id && result.result_type == .file {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
results << FindResult{
|
||||
result_type: .file
|
||||
id: target_file.id
|
||||
path: target_abs_path // Use the absolute path
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dangling symlink, just add the symlink itself
|
||||
return error('Dangling symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
results << FindResult{
|
||||
result_type: .file
|
||||
id: target_file.id
|
||||
path: target_file_path
|
||||
|
||||
if symlink.target_type == .directory {
|
||||
if self.factory.fs_dir.exist(symlink.target_id)! {
|
||||
target_dir := self.factory.fs_dir.get(symlink.target_id)!
|
||||
|
||||
// Resolve the absolute path of the target directory
|
||||
target_abs_path := self.get_abs_path_for_item(target_dir.id, .directory)!
|
||||
|
||||
// Check if we've already added this directory to avoid duplicates
|
||||
mut found := false
|
||||
for result in results {
|
||||
if result.id == target_dir.id && result.result_type == .directory {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
results << FindResult{
|
||||
result_type: .directory
|
||||
id: target_dir.id
|
||||
path: target_abs_path // Use the absolute path
|
||||
}
|
||||
if opts.recursive {
|
||||
self.find_recursive(symlink.target_id, target_abs_path,
|
||||
opts, mut results, current_depth + 1)!
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dangling symlink, just add the symlink itself
|
||||
return error('Dangling dir symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dangling symlink, just add the symlink itself
|
||||
return error('Dangling symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
|
||||
}
|
||||
}
|
||||
|
||||
if symlink.target_type == .directory {
|
||||
if self.factory.fs_dir.exist(symlink.target_id)! {
|
||||
target_dir := self.factory.fs_dir.get(symlink.target_id)!
|
||||
target_dir_path := join_path(current_path, target_dir.name)
|
||||
// Check if we've already added this directory to avoid duplicates
|
||||
mut found := false
|
||||
for result in results {
|
||||
if result.id == target_dir.id && result.result_type == .directory {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
results << FindResult{
|
||||
result_type: .directory
|
||||
id: target_dir.id
|
||||
path: target_dir_path
|
||||
}
|
||||
if opts.recursive {
|
||||
self.find_recursive(symlink.target_id, target_dir_path,
|
||||
opts, mut results, current_depth + 1)!
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dangling symlink, just add the symlink itself
|
||||
return error('Dangling dir symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,6 +361,7 @@ pub fn (mut self Fs) get_symlink_by_absolute_path(path string) !FsSymlink {
|
||||
if path_parts.len == 0 || path_parts[path_parts.len - 1] == '' {
|
||||
return error('Invalid symlink path: "${path}"')
|
||||
}
|
||||
|
||||
|
||||
symlink_name := path_parts[path_parts.len - 1]
|
||||
dir_path := if path_parts.len == 1 {
|
||||
118
lib/hero/herofs/fs_tools_find_test.v
Normal file
118
lib/hero/herofs/fs_tools_find_test.v
Normal file
@@ -0,0 +1,118 @@
|
||||
module herofs
|
||||
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import os
|
||||
|
||||
fn test_find() {
|
||||
mut f := new_fs_test()!
|
||||
// defer {
|
||||
// delete_fs_test()!
|
||||
// }
|
||||
|
||||
// Create a test directory structure
|
||||
f.factory.fs_dir.create_path(f.id, '/test_dir/subdir')!
|
||||
|
||||
mut blob1 := f.factory.fs_blob.new(data: 'hello'.bytes())!
|
||||
blob1 = f.factory.fs_blob.set(blob1)!
|
||||
mut file1 := f.factory.fs_file.new(
|
||||
name: 'file1.txt'
|
||||
fs_id: f.id
|
||||
blobs: [blob1.id]
|
||||
)!
|
||||
file1 = f.factory.fs_file.set(file1)!
|
||||
dir1 := f.get_dir_by_absolute_path('/test_dir')!
|
||||
f.factory.fs_file.add_to_directory(file1.id, dir1.id)!
|
||||
|
||||
mut blob2 := f.factory.fs_blob.new(data: 'world'.bytes())!
|
||||
blob2 = f.factory.fs_blob.set(blob2)!
|
||||
mut file2 := f.factory.fs_file.new(
|
||||
name: 'file2.log'
|
||||
fs_id: f.id
|
||||
blobs: [blob2.id]
|
||||
)!
|
||||
file2 = f.factory.fs_file.set(file2)!
|
||||
f.factory.fs_file.add_to_directory(file2.id, dir1.id)!
|
||||
|
||||
mut blob3 := f.factory.fs_blob.new(data: 'sub'.bytes())!
|
||||
blob3 = f.factory.fs_blob.set(blob3)!
|
||||
mut file3 := f.factory.fs_file.new(
|
||||
name: 'file3.txt'
|
||||
fs_id: f.id
|
||||
blobs: [blob3.id]
|
||||
)!
|
||||
file3 = f.factory.fs_file.set(file3)!
|
||||
dir2 := f.get_dir_by_absolute_path('/test_dir/subdir')!
|
||||
f.factory.fs_file.add_to_directory(file3.id, dir2.id)!
|
||||
|
||||
mut symlink1 := f.factory.fs_symlink.new(
|
||||
name: 'link1.txt'
|
||||
fs_id: f.id
|
||||
parent_id: dir1.id
|
||||
target_id: file1.id
|
||||
target_type: .file
|
||||
)!
|
||||
symlink1 = f.factory.fs_symlink.set(symlink1)!
|
||||
mut dir1_mut := f.factory.fs_dir.get(dir1.id)!
|
||||
dir1_mut.symlinks << symlink1.id
|
||||
f.factory.fs_dir.set(dir1_mut)!
|
||||
// Test 1: Find all files recursively (default)
|
||||
mut results_all := f.find('/', FindOptions{})!
|
||||
// root, test_dir, file1.txt, file2.log, link1.txt, subdir, file3.txt
|
||||
assert results_all.len == 7
|
||||
|
||||
// Test 2: Find text files
|
||||
mut results_txt := f.find('/', FindOptions{
|
||||
include_patterns: ['*.txt']
|
||||
})!
|
||||
assert results_txt.filter(it.result_type == .file).len == 2
|
||||
for result in results_txt {
|
||||
if result.result_type == .file {
|
||||
assert result.path.ends_with('.txt')
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: Find files non-recursively
|
||||
mut results_non_recursive := f.find('/test_dir', FindOptions{
|
||||
recursive: false
|
||||
})!
|
||||
// test_dir, file1.txt, file2.log, subdir, link1.txt
|
||||
assert results_non_recursive.len == 5
|
||||
|
||||
// Test 4: Exclude log files
|
||||
mut results_no_log := f.find('/', FindOptions{
|
||||
exclude_patterns: ['*.log']
|
||||
})!
|
||||
for result in results_no_log {
|
||||
assert !result.path.ends_with('.log')
|
||||
}
|
||||
|
||||
// Test 5: Find with max depth
|
||||
mut results_depth_1 := f.find('/', FindOptions{
|
||||
max_depth: 1
|
||||
})!
|
||||
// root, test_dir
|
||||
assert results_depth_1.len == 2
|
||||
|
||||
// Test 6: Find a specific file
|
||||
mut results_specific_file := f.find('/test_dir/file1.txt', FindOptions{})!
|
||||
assert results_specific_file.len == 1
|
||||
assert results_specific_file[0].path == '/test_dir/file1.txt'
|
||||
|
||||
// Test 7: Find with symlinks not followed
|
||||
mut results_symlinks := f.find('/', FindOptions{
|
||||
follow_symlinks: false
|
||||
})!
|
||||
mut found_symlink := false
|
||||
for result in results_symlinks {
|
||||
if result.result_type == .symlink {
|
||||
found_symlink = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert found_symlink
|
||||
|
||||
// Test 8: Find a specific directory
|
||||
mut results_specific_dir := f.find('/test_dir/subdir', FindOptions{})!
|
||||
// should contain subdir and file3.txt
|
||||
assert results_specific_dir.len == 2
|
||||
}
|
||||
41
lib/hero/herofs/fs_tools_helpers.v
Normal file
41
lib/hero/herofs/fs_tools_helpers.v
Normal file
@@ -0,0 +1,41 @@
|
||||
module herofs
|
||||
|
||||
import os
|
||||
|
||||
// join_path joins path components
|
||||
pub fn join_path(base string, component string) string {
|
||||
if base == '/' {
|
||||
return '/${component}'
|
||||
}
|
||||
return '${base}/${component}'
|
||||
}
|
||||
|
||||
// normalize_path normalizes a path
|
||||
pub fn normalize_path(p string) string {
|
||||
return p.replace("'", '')
|
||||
}
|
||||
|
||||
// should_include checks if a name should be included based on patterns
|
||||
pub fn should_include(name string, include_patterns []string, exclude_patterns []string) bool {
|
||||
// Exclude based on exclude_patterns
|
||||
for pattern in exclude_patterns {
|
||||
if name.contains(pattern.replace('*', '')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If include_patterns is empty, include everything not excluded
|
||||
if include_patterns.len == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Include based on include_patterns
|
||||
for pattern in include_patterns {
|
||||
if name.contains(pattern.replace('*', '')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If include_patterns is not empty and no pattern matched, exclude
|
||||
return false
|
||||
}
|
||||
50
lib/hero/herofs/fs_tools_path.v
Normal file
50
lib/hero/herofs/fs_tools_path.v
Normal file
@@ -0,0 +1,50 @@
|
||||
module herofs
|
||||
|
||||
// get_abs_path_for_item returns the absolute path for a given filesystem item ID and type
|
||||
pub fn (mut self Fs) get_abs_path_for_item(id u32, item_type FSItemType) !string {
|
||||
match item_type {
|
||||
.file {
|
||||
// Find the directory containing the file
|
||||
// This is inefficient and should be optimized in a real implementation
|
||||
all_dirs := self.factory.fs_dir.list()!
|
||||
for dir in all_dirs {
|
||||
if id in dir.files {
|
||||
parent_path := self.get_abs_path_for_item(dir.id, .directory)!
|
||||
file := self.factory.fs_file.get(id)!
|
||||
return join_path(parent_path, file.name)
|
||||
}
|
||||
}
|
||||
return error('File with ID ${id} not found in any directory')
|
||||
}
|
||||
.directory {
|
||||
mut path_parts := []string{}
|
||||
mut current_dir_id := id
|
||||
for {
|
||||
dir := self.factory.fs_dir.get(current_dir_id)!
|
||||
path_parts.insert(0, dir.name)
|
||||
if dir.parent_id == 0 {
|
||||
break
|
||||
}
|
||||
current_dir_id = dir.parent_id
|
||||
}
|
||||
// Don't prepend slash to root, which is just 'root'
|
||||
if path_parts.len > 0 && path_parts[0] == 'root' {
|
||||
path_parts.delete(0)
|
||||
}
|
||||
return '/' + path_parts.join('/')
|
||||
}
|
||||
.symlink {
|
||||
// Similar to file logic, find parent directory
|
||||
all_dirs := self.factory.fs_dir.list()!
|
||||
for dir in all_dirs {
|
||||
if id in dir.symlinks {
|
||||
parent_path := self.get_abs_path_for_item(dir.id, .directory)!
|
||||
symlink := self.factory.fs_symlink.get(id)!
|
||||
return join_path(parent_path, symlink.name)
|
||||
}
|
||||
}
|
||||
return error('Symlink with ID ${id} not found in any directory')
|
||||
}
|
||||
}
|
||||
return '' // Should be unreachable
|
||||
}
|
||||
Reference in New Issue
Block a user