This commit is contained in:
2025-09-27 08:09:54 +04:00
parent daa204555d
commit 45d1d60166
7 changed files with 725 additions and 48 deletions

View File

@@ -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,

View File

@@ -1,5 +1,8 @@
module herofs
import freeflowuniverse.herolib.hero.db
// CopyOptions provides options for copy operations
@[params]
pub struct CopyOptions {

View 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'
}

View File

@@ -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 {

View 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
}

View 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
}

View 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
}