This commit is contained in:
2025-09-15 08:02:44 +02:00
parent 006dab5905
commit 43ae67a070
4 changed files with 604 additions and 29 deletions

View File

@@ -3,9 +3,8 @@ module db
import freeflowuniverse.herolib.data.ourtime
import freeflowuniverse.herolib.data.encoder
pub fn (mut self DB) set[T](obj_ T) !u32 {
pub fn (mut self DB) set[T](mut obj T) ! {
// Get the next ID
mut obj := obj_
if obj.id == 0 {
obj.id = self.new_id()!
}
@@ -40,7 +39,6 @@ pub fn (mut self DB) set[T](obj_ T) !u32 {
obj.dump(mut e)!
// println('set: after dump, e.data.len: ${e.data.len}')
self.redis.hset(self.db_name[T](), obj.id.str(), e.data.bytestr())!
return obj.id
}
// return the data, cannot return the object as we do not know the type

View File

@@ -79,12 +79,19 @@ pub fn (mut self DBFs) new(args FsArg) !Fs {
return o
}
pub fn (mut self DBFs) set(o Fs) !u32 {
pub fn (mut self DBFs) set(o_ Fs) !u32 {
mut o := o_
if o.root_dir_id == 0 {
// If no root directory is set, create one
mut root_dir := self.factory.fs_dir.new(
name: 'root'
fs_id: o.id
parent_id: 0 // Root has no parent
)!
root_dir := self.factory.fs_dir.set(root_dir)!
// Update the filesystem with the new root directory ID
}
id := self.db.set[Fs](o)!
// Store name -> id mapping for lookups
self.db.redis.hset('fs:names', o.name, id.str())!
return id
}

View File

@@ -69,16 +69,33 @@ pub fn (mut self FsTools) find(start_path string, opts FindOptions) ![]FindResul
// - Symlinks: Symbolic links in the current directory (handled according to opts.follow_symlinks)
// - Directories: Subdirectories of the current directory (recursed into according to opts.recursive)
fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindOptions, mut results []FindResult, current_depth int) ! {
println('DEBUG: find_recursive called with dir_id=${dir_id}, current_path="${current_path}", current_depth=${current_depth}')
// Check depth limit
if opts.max_depth >= 0 && current_depth > opts.max_depth {
println('DEBUG: Max depth reached, returning')
return
}
// Get current directory info
current_dir := self.factory.fs_dir.get(dir_id)!
println('DEBUG: Got directory "${current_dir.name}" with ${current_dir.files.len} files, ${current_dir.directories.len} directories, ${current_dir.symlinks.len} symlinks')
// Check if current directory matches search criteria
if should_include(current_dir.name, opts.include_patterns, opts.exclude_patterns) {
// Only include the directory if it's not the root directory
if current_path != '/'
&& should_include(current_dir.name, opts.include_patterns, opts.exclude_patterns) {
println('DEBUG: Including directory "${current_dir.name}" in results')
results << FindResult{
result_type: .directory
id: dir_id
path: current_path
}
}
// Always include the root directory
if current_path == '/' {
println('DEBUG: Including root directory "${current_dir.name}" in results')
results << FindResult{
result_type: .directory
id: dir_id
@@ -88,9 +105,11 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
// Get files in current directory
for file_id in current_dir.files {
println('DEBUG: Processing file ID ${file_id}')
file := self.factory.fs_file.get(file_id)!
if should_include(file.name, opts.include_patterns, opts.exclude_patterns) {
file_path := join_path(current_path, file.name)
println('DEBUG: Including file "${file.name}" in results')
results << FindResult{
result_type: .file
id: file.id
@@ -117,10 +136,20 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
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)
results << FindResult{
result_type: .file
id: target_file.id
path: target_file_path
// 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_file_path
}
}
} else {
// dangling symlink, just add the symlink itself
@@ -132,14 +161,24 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
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)
results << FindResult{
result_type: .directory
id: target_dir.id
path: target_dir_path
// 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 opts.recursive {
self.find_recursive(symlink.target_id, target_dir_path, opts, mut
results, current_depth + 1)!
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
@@ -151,21 +190,27 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
}
for dir_id2 in current_dir.directories {
println('DEBUG: Found child directory ID ${dir_id2} in directory ${dir_id}')
subdir := self.factory.fs_dir.get(dir_id2)!
if should_include(subdir.name, opts.include_patterns, opts.exclude_patterns) {
subdir_path := join_path(current_path, subdir.name)
results << FindResult{
result_type: .directory
id: subdir.id
path: subdir_path
}
// Process subdirectories if recursive
if opts.recursive {
self.find_recursive(dir_id, subdir_path, opts, mut results, current_depth + 1)!
// Include child directories in results when not recursive
// When recursive, the directory will be included in the results when find_recursive is called on it
if !opts.recursive {
println('DEBUG: Including directory "${subdir.name}" in results')
results << FindResult{
result_type: .directory
id: subdir.id
path: subdir_path
}
} else {
println('DEBUG: Processing directory "${subdir.name}"')
self.find_recursive(dir_id2, subdir_path, opts, mut results, current_depth + 1)!
}
}
}
println('DEBUG: find_recursive finished with ${results.len} results')
}
// get_dir_by_absolute_path resolves an absolute path to a directory ID
@@ -181,11 +226,15 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
// dir := tools.get_dir_by_absolute_path('/home/user/documents')!
// ```
pub fn (mut self FsTools) get_dir_by_absolute_path(path string) !FsDir {
println('DEBUG: get_dir_by_absolute_path called with path "${path}"')
normalized_path_ := normalize_path(path)
println('DEBUG: normalized_path_ = "${normalized_path_}"')
// Handle root directory case
if normalized_path_ == '/' {
println('DEBUG: Handling root directory case')
fs := self.factory.fs.get(self.fs_id)!
println('DEBUG: fs.root_dir_id = ${fs.root_dir_id}')
return self.factory.fs_dir.get(fs.root_dir_id)!
}

View File

@@ -0,0 +1,521 @@
module herofs
import freeflowuniverse.herolib.hero.db
fn test_basic_find() {
println('Testing FsTools find functionality...')
// Initialize the HeroFS factory
mut fs_factory := new()!
println('HeroFS factory initialized')
// Create a new filesystem
mut my_fs := fs_factory.fs.new(
name: 'test_filesystem_find'
description: 'Filesystem for testing FsTools find functionality'
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
)!
// Save the filesystem to get an ID
fs_id := fs_factory.fs.set(my_fs)!
println('Created test filesystem with ID: ${fs_id}')
// Create root directory
mut root_dir := fs_factory.fs_dir.new(
name: 'root'
fs_id: fs_id
parent_id: 0 // Root has no parent
description: 'Root directory for testing find'
)!
// Save the root directory
root_dir_id := fs_factory.fs_dir.set(root_dir)!
println('Created root directory with ID: ${root_dir_id}')
// Update the filesystem with the root directory ID
println('DEBUG: Before update, my_fs.root_dir_id = ${my_fs.root_dir_id}')
println('DEBUG: Before update, my_fs.id = ${my_fs.id}')
my_fs.root_dir_id = root_dir_id
my_fs.id = fs_id // Set the ID to ensure we update the existing object
println('DEBUG: Setting my_fs.root_dir_id to ${root_dir_id}')
mut fs_id2 := fs_factory.fs.set(my_fs)!
println('DEBUG: After update, fs_id2 = ${fs_id2}')
println('DEBUG: After update, my_fs.root_dir_id = ${my_fs.root_dir_id}')
// Retrieve the updated filesystem object
my_fs = fs_factory.fs.get(fs_id)!
println('DEBUG: After retrieval, fs.root_dir_id = ${my_fs.root_dir_id}')
// Create test directories
mut dir1 := fs_factory.fs_dir.new(
name: 'documents'
fs_id: fs_id
parent_id: root_dir_id
description: 'Documents directory'
)!
dir1_id := fs_factory.fs_dir.set(dir1)!
mut dir2 := fs_factory.fs_dir.new(
name: 'images'
fs_id: fs_id
parent_id: root_dir_id
description: 'Images directory'
)!
dir2_id := fs_factory.fs_dir.set(dir2)!
mut dir3 := fs_factory.fs_dir.new(
name: 'subdir'
fs_id: fs_id
parent_id: dir1_id
description: 'Subdirectory in documents'
)!
dir3_id := fs_factory.fs_dir.set(dir3)!
// Update parent directories with their children
// Update root_dir to include dir1 and dir2
println('DEBUG: Updating root_dir with children')
root_dir.directories = [dir1_id, dir2_id]
root_dir.id = root_dir_id // Set the ID to ensure we update the existing object
mut root_dir_id2 := fs_factory.fs_dir.set(root_dir)!
println('DEBUG: root_dir updated with ID ${root_dir_id2}')
// Update dir1 to include dir3
println('DEBUG: Updating dir1 with children')
dir1.directories = [dir3_id]
dir1.id = dir1_id // Set the ID to ensure we update the existing object
mut dir1_id2 := fs_factory.fs_dir.set(dir1)!
println('DEBUG: dir1 updated with ID ${dir1_id2}')
// Create test blobs for files
mut test_blob1 := fs_factory.fs_blob.new(
data: 'This is test content for file 1'.bytes()
)!
blob1_id := fs_factory.fs_blob.set(test_blob1)!
println('Created test blob with ID: ${blob1_id}')
mut test_blob2 := fs_factory.fs_blob.new(
data: 'This is test content for file 2'.bytes()
)!
blob2_id := fs_factory.fs_blob.set(test_blob2)!
println('Created test blob with ID: ${blob2_id}')
mut test_blob3 := fs_factory.fs_blob.new(
data: 'This is test content for file 3'.bytes()
)!
blob3_id := fs_factory.fs_blob.set(test_blob3)!
println('Created test blob with ID: ${blob3_id}')
// Create test files
mut file1 := fs_factory.fs_file.new(
name: 'document.txt'
fs_id: fs_id
directories: [dir1_id]
blobs: [blob1_id]
description: 'Text document'
mime_type: .txt
)!
file1_id := fs_factory.fs_file.set(file1)!
mut file2 := fs_factory.fs_file.new(
name: 'image.png'
fs_id: fs_id
directories: [dir2_id]
blobs: [blob2_id]
description: 'PNG image'
mime_type: .png
)!
file2_id := fs_factory.fs_file.set(file2)!
mut file3 := fs_factory.fs_file.new(
name: 'subfile.txt'
fs_id: fs_id
directories: [dir3_id]
blobs: [blob3_id]
description: 'Text file in subdirectory'
mime_type: .txt
)!
file3_id := fs_factory.fs_file.set(file3)!
// Create symlinks
mut symlink1 := fs_factory.fs_symlink.new(
name: 'doc_link.txt'
fs_id: fs_id
parent_id: root_dir_id
target_id: file1_id
target_type: .file
description: 'Symlink to document.txt'
)!
symlink1_id := fs_factory.fs_symlink.set(symlink1)!
mut symlink2 := fs_factory.fs_symlink.new(
name: 'images_link'
fs_id: fs_id
parent_id: root_dir_id
target_id: dir2_id
target_type: .directory
description: 'Symlink to images directory'
)!
symlink2_id := fs_factory.fs_symlink.set(symlink2)!
// Update directories with their children
// Update dir1 to include dir3 and file1
dir1.directories = [dir3_id]
dir1.files = [file1_id]
fs_factory.fs_dir.set(dir1)!
// Update dir2 to include file2
dir2.files = [file2_id]
fs_factory.fs_dir.set(dir2)!
// Update dir3 to include file3
dir3.files = [file3_id]
dir3.id = dir3_id // Set the ID to ensure we update the existing object
fs_factory.fs_dir.set(dir3)!
// Update root_dir to include dir1, dir2, symlink1, symlink2
root_dir.directories = [dir1_id, dir2_id]
root_dir.symlinks = [symlink1_id, symlink2_id]
fs_factory.fs_dir.set(root_dir)!
println('Created test directory structure:')
println('- root (ID: ${root_dir_id})')
println(' - documents (ID: ${dir1_id})')
println(' - subdir (ID: ${dir3_id})')
println(' - subfile.txt (ID: ${file3_id})')
println(' - document.txt (ID: ${file1_id})')
println(' - images (ID: ${dir2_id})')
println(' - image.png (ID: ${file2_id})')
println(' - doc_link.txt (ID: ${symlink1_id}) -> document.txt')
println(' - images_link (ID: ${symlink2_id}) -> images')
// Create FsTools instance
mut fs_tools := fs_factory.fs_tools(fs_id)
// Test basic find from root
println('\nTesting basic find from root...')
mut results := fs_tools.find('/', FindOptions{
recursive: true
})!
// Should find all items
assert results.len == 8
println(' Found all 8 items in recursive search')
// Check that we found the expected items
mut found_items := map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert found_items['/'] == .directory
assert found_items['/documents'] == .directory
assert found_items['/images'] == .directory
assert found_items['/documents/subdir'] == .directory
assert found_items['/documents/document.txt'] == .file
assert found_items['/images/image.png'] == .file
assert found_items['/documents/subdir/subfile.txt'] == .file
assert found_items['/doc_link.txt'] == .symlink
assert found_items['/images_link'] == .symlink
println(' All items found with correct paths and types')
// Test non-recursive find from root
println('\nTesting non-recursive find from root...')
results = fs_tools.find('/', FindOptions{
recursive: false
})!
// Should only find items directly in root
assert results.len == 5
println(' Found 5 items in non-recursive search')
// Check that we found the expected items
found_items = map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert found_items['/'] == .directory
assert found_items['/documents'] == .directory
assert found_items['/images'] == .directory
assert '/documents/subdir' !in found_items
println(' Non-recursive search only found direct children')
// Test find with include patterns
println('\nTesting find with include patterns...')
results = fs_tools.find('/', FindOptions{
recursive: true
include_patterns: ['*.txt']
})!
// Should find only .txt files
assert results.len == 2
println(' Found 2 .txt files with include pattern')
found_items = map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert found_items['/documents/document.txt'] == .file
assert found_items['/documents/subdir/subfile.txt'] == .file
println(' Include pattern correctly filtered results')
// Test find with exclude patterns
println('\nTesting find with exclude patterns...')
results = fs_tools.find('/', FindOptions{
recursive: true
exclude_patterns: ['*.png']
})!
// Should find all items except the .png file
assert results.len == 7
println(' Found 7 items excluding .png files')
found_items = map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert '/images/image.png' !in found_items
assert found_items['/images'] == .directory
println(' Exclude pattern correctly filtered results')
// Test find with max_depth
println('\nTesting find with max_depth...')
results = fs_tools.find('/', FindOptions{
recursive: true
max_depth: 1
})!
// Should find root and its direct children only
assert results.len == 6
println(' Found 6 items with max_depth=1')
found_items = map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert found_items['/'] == .directory
assert found_items['/documents'] == .directory
assert found_items['/images'] == .directory
assert '/documents/subdir' !in found_items
assert '/documents/subdir/subfile.txt' !in found_items
println(' Max depth correctly limited search depth')
// Test find from subdirectory
println('\nTesting find from subdirectory...')
results = fs_tools.find('/documents', FindOptions{
recursive: true
})!
// Should find items in /documents and its subdirectories
assert results.len == 4
println(' Found 4 items in subdirectory search')
found_items = map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert found_items['/documents'] == .directory
assert found_items['/documents/document.txt'] == .file
assert found_items['/documents/subdir'] == .directory
assert found_items['/documents/subdir/subfile.txt'] == .file
assert '/' !in found_items
println(' Subdirectory search correctly rooted at /documents')
println('\nFsTools find basic test completed successfully!')
}
fn test_symlink_find() {
println('\nTesting FsTools find with symlinks...')
// Initialize the HeroFS factory
mut fs_factory := new()!
// Create a new filesystem
mut my_fs := fs_factory.fs.new(
name: 'test_filesystem_symlink_find'
description: 'Filesystem for testing FsTools find with symlinks'
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
)!
// Save the filesystem to get an ID
fs_id := fs_factory.fs.set(my_fs)!
// Create root directory
mut root_dir := fs_factory.fs_dir.new(
name: 'root'
fs_id: fs_id
parent_id: 0 // Root has no parent
description: 'Root directory for testing symlink find'
)!
// Save the root directory
root_dir_id := fs_factory.fs_dir.set(root_dir)!
// Update the filesystem with the root directory ID
my_fs.root_dir_id = root_dir_id
my_fs.id = fs_id // Set the ID to ensure we update the existing object
fs_factory.fs.set(my_fs)!
// Retrieve the updated filesystem object
my_fs = fs_factory.fs.get(fs_id)!
// Create test directory
mut dir1 := fs_factory.fs_dir.new(
name: 'target_dir'
fs_id: fs_id
parent_id: root_dir_id
description: 'Target directory for symlink'
)!
dir1_id := fs_factory.fs_dir.set(dir1)!
// Create test blob
mut test_blob := fs_factory.fs_blob.new(
data: 'Symlink test content'.bytes()
)!
blob_id := fs_factory.fs_blob.set(test_blob)!
// Create test file
mut file1 := fs_factory.fs_file.new(
name: 'target_file.txt'
fs_id: fs_id
directories: [dir1_id]
blobs: [blob_id]
description: 'Target file for symlink'
mime_type: .txt
)!
file1_id := fs_factory.fs_file.set(file1)!
// Update dir1 with file1
dir1.files = [file1_id]
dir1.id = dir1_id // Set the ID to ensure we update the existing object
fs_factory.fs_dir.set(dir1)!
// Create symlinks
mut symlink1 := fs_factory.fs_symlink.new(
name: 'file_link.txt'
fs_id: fs_id
parent_id: root_dir_id
target_id: file1_id
target_type: .file
description: 'Symlink to target_file.txt'
)!
symlink1_id := fs_factory.fs_symlink.set(symlink1)!
mut symlink2 := fs_factory.fs_symlink.new(
name: 'dir_link'
fs_id: fs_id
parent_id: root_dir_id
target_id: dir1_id
target_type: .directory
description: 'Symlink to target_dir'
)!
symlink2_id := fs_factory.fs_symlink.set(symlink2)!
// Update root_dir with dir1 and symlinks
root_dir.directories = [dir1_id]
root_dir.symlinks = [symlink1_id, symlink2_id]
root_dir.id = root_dir_id // Set the ID to ensure we update the existing object
fs_factory.fs_dir.set(root_dir)!
// Create FsTools instance
mut fs_tools := fs_factory.fs_tools(fs_id)
// Test find without following symlinks
println('Testing find without following symlinks...')
mut results := fs_tools.find('/', FindOptions{
recursive: true
follow_symlinks: false
})!
// Should find root, target_dir, symlinks, and target_file.txt
assert results.len == 5
println(' Found 5 items without following symlinks')
mut found_items := map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert found_items['/'] == .directory
assert found_items['/target_dir'] == .directory
assert found_items['/file_link.txt'] == .symlink
assert found_items['/dir_link'] == .symlink
assert found_items['/target_dir/target_file.txt'] == .file
println(' Symlinks found as symlinks when follow_symlinks=false')
// Test find with following symlinks
println('Testing find with following symlinks...')
results = fs_tools.find('/', FindOptions{
recursive: true
follow_symlinks: true
})!
// Should find root, target_dir, and target_file.txt (but not the symlinks themselves)
assert results.len == 3
println(' Found 3 items when following symlinks')
found_items = map[string]FSItemType{}
for result in results {
found_items[result.path] = result.result_type
}
assert found_items['/'] == .directory
assert found_items['/target_dir'] == .directory
assert found_items['/target_dir/target_file.txt'] == .file
assert '/file_link.txt' !in found_items
assert '/dir_link' !in found_items
println(' Symlinks followed correctly when follow_symlinks=true')
println('FsTools find symlink test completed successfully!')
}
fn test_find_edge_cases() {
println('\nTesting FsTools find edge cases...')
// Initialize the HeroFS factory
mut fs_factory := new()!
// Create a new filesystem
mut my_fs := fs_factory.fs.new(
name: 'test_filesystem_find_edge'
description: 'Filesystem for testing FsTools find edge cases'
quota_bytes: 1024 * 1024 * 1024 // 1GB quota
)!
// Save the filesystem to get an ID
fs_id := fs_factory.fs.set(my_fs)!
// Create root directory
mut root_dir := fs_factory.fs_dir.new(
name: 'root'
fs_id: fs_id
parent_id: 0 // Root has no parent
description: 'Root directory for testing find edge cases'
)!
// Save the root directory
root_dir_id := fs_factory.fs_dir.set(root_dir)!
// Update the filesystem with the root directory ID
my_fs.root_dir_id = root_dir_id
fs_factory.fs.set(my_fs)!
// Create FsTools instance
mut fs_tools := fs_factory.fs_tools(fs_id)
// Test find with non-existent path
println('Testing find with non-existent path...')
mut result := fs_tools.find('/nonexistent', FindOptions{}) or {
println(' Find correctly failed with non-existent path')
return
}
// If we get here, the error handling didn't work as expected
panic('Find should have failed with non-existent path')
println('FsTools find edge cases test completed successfully!')
}