...
This commit is contained in:
@@ -1,737 +0,0 @@
|
||||
module herofs
|
||||
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
|
||||
// FindResult represents the result of a filesystem search
|
||||
pub struct FindResult {
|
||||
pub mut:
|
||||
result_type FindResultType
|
||||
id u32
|
||||
path string
|
||||
name string
|
||||
}
|
||||
|
||||
// FindResultType indicates what type of filesystem object was found
|
||||
pub enum FindResultType {
|
||||
file
|
||||
directory
|
||||
symlink
|
||||
}
|
||||
|
||||
// FindOptions provides options for filesystem search operations
|
||||
@[params]
|
||||
pub struct FindOptions {
|
||||
pub mut:
|
||||
recursive bool = true
|
||||
include_patterns []string // File/directory name patterns to include (e.g. ['*.v', 'doc*'])
|
||||
exclude_patterns []string // File/directory name patterns to exclude
|
||||
max_depth int = -1 // Maximum depth to search (-1 for unlimited)
|
||||
follow_symlinks bool // Whether to follow symbolic links during search
|
||||
}
|
||||
|
||||
// CopyOptions provides options for copy operations
|
||||
@[params]
|
||||
pub struct CopyOptions {
|
||||
pub mut:
|
||||
recursive bool = true // Copy directories recursively
|
||||
preserve_links bool = true // Preserve symbolic links as links
|
||||
overwrite bool // Overwrite existing files
|
||||
follow_symlinks bool // Follow symlinks instead of copying them
|
||||
}
|
||||
|
||||
// RemoveOptions provides options for remove operations
|
||||
@[params]
|
||||
pub struct RemoveOptions {
|
||||
pub mut:
|
||||
recursive bool // Remove directories and their contents
|
||||
delete_blobs bool // Delete underlying blob data (default: false)
|
||||
force bool // Force removal even if files are in multiple directories
|
||||
}
|
||||
|
||||
// MoveOptions provides options for move operations
|
||||
@[params]
|
||||
pub struct MoveOptions {
|
||||
pub mut:
|
||||
overwrite bool // Overwrite existing files at destination
|
||||
follow_symlinks bool // Follow symlinks instead of moving them
|
||||
}
|
||||
|
||||
// FsTools provides high-level filesystem operations
|
||||
pub struct FsTools {
|
||||
pub mut:
|
||||
factory &FsFactory @[skip; str: skip]
|
||||
}
|
||||
|
||||
// Create a new FsTools instance
|
||||
pub fn (factory &FsFactory) tools() FsTools {
|
||||
return FsTools{
|
||||
factory: factory
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if name matches include/exclude patterns
|
||||
fn matches_pattern(name string, patterns []string) bool {
|
||||
if patterns.len == 0 {
|
||||
return true // No patterns means include everything
|
||||
}
|
||||
|
||||
for pattern in patterns {
|
||||
if pattern.contains('*') {
|
||||
prefix := pattern.all_before('*')
|
||||
suffix := pattern.all_after('*')
|
||||
|
||||
if prefix == '' && suffix == '' {
|
||||
return true // Pattern is just "*"
|
||||
} else if prefix == '' {
|
||||
if name.ends_with(suffix) {
|
||||
return true
|
||||
}
|
||||
} else if suffix == '' {
|
||||
if name.starts_with(prefix) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if name.starts_with(prefix) && name.ends_with(suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if name == pattern {
|
||||
return true // Exact match
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if item should be included based on patterns
|
||||
fn should_include(name string, include_patterns []string, exclude_patterns []string) bool {
|
||||
// First apply include patterns (if empty, include everything)
|
||||
if !matches_pattern(name, include_patterns) && include_patterns.len > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Then apply exclude patterns
|
||||
if matches_pattern(name, exclude_patterns) && exclude_patterns.len > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Normalize path by removing trailing slashes and handling edge cases
|
||||
fn normalize_path(path string) string {
|
||||
if path == '' || path == '/' {
|
||||
return '/'
|
||||
}
|
||||
return path.trim_right('/')
|
||||
}
|
||||
|
||||
// Split path into directory and filename parts
|
||||
fn split_path(path string) (string, string) {
|
||||
normalized := normalize_path(path)
|
||||
if normalized == '/' {
|
||||
return '/', ''
|
||||
}
|
||||
|
||||
mut dir_path := normalized.all_before_last('/')
|
||||
filename := normalized.all_after_last('/')
|
||||
|
||||
if dir_path == '' {
|
||||
dir_path = '/'
|
||||
}
|
||||
|
||||
return dir_path, filename
|
||||
}
|
||||
|
||||
// Get the parent path of a given path
|
||||
fn parent_path(path string) string {
|
||||
normalized := normalize_path(path)
|
||||
if normalized == '/' {
|
||||
return '/'
|
||||
}
|
||||
|
||||
parent := normalized.all_before_last('/')
|
||||
if parent == '' {
|
||||
return '/'
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
// Join path components
|
||||
fn join_path(base string, component string) string {
|
||||
normalized_base := normalize_path(base)
|
||||
if normalized_base == '/' {
|
||||
return '/' + component
|
||||
}
|
||||
return normalized_base + '/' + component
|
||||
}
|
||||
|
||||
// Find filesystem objects starting from a given path
|
||||
pub fn (mut self FsTools) find(fs_id u32, start_path string, opts FindOptions) ![]FindResult {
|
||||
mut results := []FindResult{}
|
||||
|
||||
// Get the starting directory
|
||||
start_dir := self.get_dir_by_absolute_path(fs_id, start_path)!
|
||||
|
||||
// Start recursive search
|
||||
self.find_recursive(fs_id, start_dir.id, start_path, opts, mut results, 0)!
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// Internal recursive function for find operation
|
||||
fn (mut self FsTools) find_recursive(fs_id u32, dir_id u32, current_path string, opts FindOptions, mut results []FindResult, current_depth int) ! {
|
||||
// Check depth limit
|
||||
if opts.max_depth >= 0 && current_depth > opts.max_depth {
|
||||
return
|
||||
}
|
||||
|
||||
// Get current directory info
|
||||
current_dir := self.factory.fs_dir.get(dir_id)!
|
||||
|
||||
// Check if current directory matches search criteria
|
||||
if should_include(current_dir.name, opts.include_patterns, opts.exclude_patterns) {
|
||||
results << FindResult{
|
||||
result_type: .directory
|
||||
id: dir_id
|
||||
path: current_path
|
||||
name: current_dir.name
|
||||
}
|
||||
}
|
||||
|
||||
// Get files in current directory
|
||||
files := self.list_files_in_dir(dir_id)!
|
||||
for file in files {
|
||||
if should_include(file.name, opts.include_patterns, opts.exclude_patterns) {
|
||||
file_path := join_path(current_path, file.name)
|
||||
results << FindResult{
|
||||
result_type: .file
|
||||
id: file.id
|
||||
path: file_path
|
||||
name: file.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get symlinks in current directory
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(dir_id)!
|
||||
for symlink in symlinks {
|
||||
if should_include(symlink.name, opts.include_patterns, opts.exclude_patterns) {
|
||||
symlink_path := join_path(current_path, symlink.name)
|
||||
results << FindResult{
|
||||
result_type: .symlink
|
||||
id: symlink.id
|
||||
path: symlink_path
|
||||
name: symlink.name
|
||||
}
|
||||
}
|
||||
|
||||
// Follow symlinks if requested and they point to directories
|
||||
if opts.follow_symlinks && opts.recursive && symlink.target_type == .directory {
|
||||
// Check if symlink is not broken
|
||||
if !self.factory.fs_symlink.is_broken(symlink.id)! {
|
||||
symlink_path := join_path(current_path, symlink.name)
|
||||
self.find_recursive(fs_id, symlink.target_id, symlink_path, opts, mut
|
||||
results, current_depth + 1)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process subdirectories if recursive
|
||||
if opts.recursive {
|
||||
subdirs := self.list_child_dirs(dir_id)!
|
||||
for subdir in subdirs {
|
||||
subdir_path := join_path(current_path, subdir.name)
|
||||
self.find_recursive(fs_id, subdir.id, subdir_path, opts, mut results, current_depth + 1)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove filesystem objects starting from a given path
|
||||
pub fn (mut self FsTools) rm(fs_id u32, target_path string, opts RemoveOptions) ! {
|
||||
normalized_path := normalize_path(target_path)
|
||||
|
||||
// Try to find what we're removing (file, directory, or symlink)
|
||||
dir_path, filename := split_path(normalized_path)
|
||||
|
||||
if filename == '' {
|
||||
// We're removing a directory by its path
|
||||
self.rm_directory_by_path(fs_id, normalized_path, opts)!
|
||||
} else {
|
||||
// We're removing a specific item within a directory
|
||||
parent_dir := self.get_dir_by_absolute_path(fs_id, dir_path)!
|
||||
|
||||
// Try to find what we're removing
|
||||
mut found := false
|
||||
|
||||
// Try file first
|
||||
if file := self.get_file_by_path(parent_dir.id, filename) {
|
||||
self.rm_file(file.id, opts)!
|
||||
found = true
|
||||
}
|
||||
|
||||
// Try symlink if file not found
|
||||
if !found {
|
||||
// Direct implementation since get_by_path doesn't exist for symlinks
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(parent_dir.id)!
|
||||
for symlink in symlinks {
|
||||
if symlink.name == filename {
|
||||
self.rm_symlink(symlink.id)!
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try directory if neither file nor symlink found
|
||||
if !found {
|
||||
if subdir := self.find_child_dir_by_name(parent_dir.id, filename) {
|
||||
self.rm_directory(subdir.id, opts)!
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return error('Path "${target_path}" not found')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a file by ID
|
||||
fn (mut self FsTools) rm_file(file_id u32, opts RemoveOptions) ! {
|
||||
file := self.factory.fs_file.get(file_id)!
|
||||
|
||||
// If file is in multiple directories and force is not set, only remove from directories
|
||||
if file.directories.len > 1 && !opts.force {
|
||||
return error('File "${file.name}" exists in multiple directories. Use force=true to delete completely or remove from specific directories.')
|
||||
}
|
||||
|
||||
// Collect blob IDs before deleting the file
|
||||
blob_ids := file.blobs.clone()
|
||||
|
||||
// Delete the file
|
||||
self.factory.fs_file.delete(file_id)!
|
||||
|
||||
// Delete blobs if requested
|
||||
if opts.delete_blobs {
|
||||
for blob_id in blob_ids {
|
||||
// Check if blob is used by other files before deleting
|
||||
if self.is_blob_used_by_other_files(blob_id, file_id)! {
|
||||
println('Warning: Blob ${blob_id} is used by other files, not deleting')
|
||||
continue
|
||||
}
|
||||
self.factory.fs_blob.delete(blob_id)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a directory by ID
|
||||
fn (mut self FsTools) rm_directory(dir_id u32, opts RemoveOptions) ! {
|
||||
// Check if directory has children
|
||||
if self.dir_has_children(dir_id)! {
|
||||
if !opts.recursive {
|
||||
dir := self.factory.fs_dir.get(dir_id)!
|
||||
return error('Directory "${dir.name}" is not empty. Use recursive=true to remove contents.')
|
||||
}
|
||||
|
||||
// Remove all children recursively
|
||||
self.rm_directory_contents(dir_id, opts)!
|
||||
}
|
||||
|
||||
// Remove the directory itself
|
||||
self.factory.fs_dir.delete(dir_id)!
|
||||
}
|
||||
|
||||
// Remove a directory by path
|
||||
fn (mut self FsTools) rm_directory_by_path(fs_id u32, dir_path string, opts RemoveOptions) ! {
|
||||
dir := self.get_dir_by_absolute_path(fs_id, dir_path)!
|
||||
self.rm_directory(dir.id, opts)!
|
||||
}
|
||||
|
||||
// Remove all contents of a directory
|
||||
fn (mut self FsTools) rm_directory_contents(dir_id u32, opts RemoveOptions) ! {
|
||||
// Remove all files in the directory
|
||||
files := self.list_files_in_dir(dir_id)!
|
||||
for file in files {
|
||||
self.rm_file(file.id, opts)!
|
||||
}
|
||||
|
||||
// Remove all symlinks in the directory
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(dir_id)!
|
||||
for symlink in symlinks {
|
||||
self.rm_symlink(symlink.id)!
|
||||
}
|
||||
|
||||
// Remove all subdirectories recursively
|
||||
subdirs := self.list_child_dirs(dir_id)!
|
||||
for subdir in subdirs {
|
||||
self.rm_directory(subdir.id, opts)!
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a symlink by ID
|
||||
fn (mut self FsTools) rm_symlink(symlink_id u32) ! {
|
||||
self.factory.fs_symlink.delete(symlink_id)!
|
||||
}
|
||||
|
||||
// Check if a blob is used by other files (excluding the specified file_id)
|
||||
fn (mut self FsTools) is_blob_used_by_other_files(blob_id u32, exclude_file_id u32) !bool {
|
||||
// This is a simple but potentially expensive check
|
||||
// In a production system, you might want to maintain reverse indices
|
||||
all_files := self.list_all_files()!
|
||||
for file in all_files {
|
||||
if file.id != exclude_file_id && blob_id in file.blobs {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Copy filesystem objects from source path to destination path
|
||||
pub fn (mut self FsTools) cp(fs_id u32, source_path string, dest_path string, opts CopyOptions) ! {
|
||||
normalized_source := normalize_path(source_path)
|
||||
normalized_dest := normalize_path(dest_path)
|
||||
|
||||
// Determine what we're copying
|
||||
source_dir_path, source_filename := split_path(normalized_source)
|
||||
|
||||
if source_filename == '' {
|
||||
// We're copying a directory
|
||||
source_dir := self.get_dir_by_absolute_path(fs_id, normalized_source)!
|
||||
self.cp_directory(fs_id, source_dir.id, normalized_source, normalized_dest, opts)!
|
||||
} else {
|
||||
// We're copying a specific item
|
||||
source_parent_dir := self.get_dir_by_absolute_path(fs_id, source_dir_path)!
|
||||
|
||||
// Try to find what we're copying
|
||||
mut found := false
|
||||
|
||||
// Try file first
|
||||
if file := self.get_file_by_path(source_parent_dir.id, source_filename) {
|
||||
self.cp_file(fs_id, file.id, normalized_dest, opts)!
|
||||
found = true
|
||||
}
|
||||
|
||||
// Try symlink if file not found
|
||||
if !found {
|
||||
// Direct implementation since get_by_path doesn't exist for symlinks
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(source_parent_dir.id)!
|
||||
for symlink in symlinks {
|
||||
if symlink.name == source_filename {
|
||||
self.cp_symlink(fs_id, symlink.id, normalized_dest, opts)!
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try directory if neither file nor symlink found
|
||||
if !found {
|
||||
if subdir := self.find_child_dir_by_name(source_parent_dir.id, source_filename) {
|
||||
self.cp_directory(fs_id, subdir.id, normalized_source, normalized_dest,
|
||||
opts)!
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return error('Source path "${source_path}" not found')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy a file to destination path
|
||||
fn (mut self FsTools) cp_file(fs_id u32, file_id u32, dest_path string, opts CopyOptions) ! {
|
||||
source_file := self.factory.fs_file.get(file_id)!
|
||||
|
||||
// Determine destination directory and filename
|
||||
dest_dir_path, mut dest_filename := split_path(dest_path)
|
||||
if dest_filename == '' {
|
||||
dest_filename = source_file.name
|
||||
}
|
||||
|
||||
// Ensure destination directory exists (create if needed)
|
||||
dest_dir_id := self.create_dir_path(fs_id, dest_dir_path)!
|
||||
|
||||
// Check if destination file already exists
|
||||
if existing_file := self.get_file_by_path(dest_dir_id, dest_filename) {
|
||||
if !opts.overwrite {
|
||||
return error('Destination file "${dest_path}" already exists. Use overwrite=true to replace.')
|
||||
}
|
||||
// Remove existing file
|
||||
self.factory.fs_file.delete(existing_file.id)!
|
||||
}
|
||||
|
||||
// Create new file with same content (reuse blobs)
|
||||
new_file := self.factory.fs_file.new(
|
||||
name: dest_filename
|
||||
fs_id: fs_id
|
||||
directories: [dest_dir_id]
|
||||
blobs: source_file.blobs.clone()
|
||||
mime_type: source_file.mime_type
|
||||
checksum: source_file.checksum
|
||||
metadata: source_file.metadata.clone()
|
||||
description: source_file.description
|
||||
)!
|
||||
|
||||
self.factory.fs_file.set(new_file)!
|
||||
}
|
||||
|
||||
// Copy a symlink to destination path
|
||||
fn (mut self FsTools) cp_symlink(fs_id u32, symlink_id u32, dest_path string, opts CopyOptions) ! {
|
||||
source_symlink := self.factory.fs_symlink.get(symlink_id)!
|
||||
|
||||
if opts.follow_symlinks {
|
||||
// Follow the symlink and copy its target instead
|
||||
if source_symlink.target_type == .file {
|
||||
self.cp_file(fs_id, source_symlink.target_id, dest_path, opts)!
|
||||
} else if source_symlink.target_type == .directory {
|
||||
self.cp_directory(fs_id, source_symlink.target_id, '', dest_path, opts)!
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Copy the symlink itself
|
||||
dest_dir_path, mut dest_filename := split_path(dest_path)
|
||||
if dest_filename == '' {
|
||||
dest_filename = source_symlink.name
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
dest_dir_id := self.create_dir_path(fs_id, dest_dir_path)!
|
||||
|
||||
// Check if destination symlink already exists
|
||||
// Direct implementation since get_by_path doesn't exist for symlinks
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(dest_dir_id)!
|
||||
for existing_symlink in symlinks {
|
||||
if existing_symlink.name == dest_filename {
|
||||
if !opts.overwrite {
|
||||
return error('Destination symlink "${dest_path}" already exists. Use overwrite=true to replace.')
|
||||
}
|
||||
self.factory.fs_symlink.delete(existing_symlink.id)!
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Create new symlink
|
||||
new_symlink := self.factory.fs_symlink.new(
|
||||
name: dest_filename
|
||||
fs_id: fs_id
|
||||
parent_id: dest_dir_id
|
||||
target_id: source_symlink.target_id
|
||||
target_type: source_symlink.target_type
|
||||
description: source_symlink.description
|
||||
)!
|
||||
|
||||
self.factory.fs_symlink.set(new_symlink)!
|
||||
}
|
||||
|
||||
// Copy a directory to destination path
|
||||
fn (mut self FsTools) cp_directory(fs_id u32, source_dir_id u32, source_path string, dest_path string, opts CopyOptions) ! {
|
||||
source_dir := self.factory.fs_dir.get(source_dir_id)!
|
||||
|
||||
// Create destination directory
|
||||
dest_dir_id := self.create_dir_path(fs_id, dest_path)!
|
||||
|
||||
if !opts.recursive {
|
||||
return
|
||||
}
|
||||
|
||||
// Copy all files in the source directory
|
||||
files := self.list_files_in_dir(source_dir_id)!
|
||||
for file in files {
|
||||
file_dest_path := join_path(dest_path, file.name)
|
||||
self.cp_file(fs_id, file.id, file_dest_path, opts)!
|
||||
}
|
||||
|
||||
// Copy all symlinks in the source directory
|
||||
if opts.preserve_links {
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(source_dir_id)!
|
||||
for symlink in symlinks {
|
||||
symlink_dest_path := join_path(dest_path, symlink.name)
|
||||
self.cp_symlink(fs_id, symlink.id, symlink_dest_path, opts)!
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all subdirectories recursively
|
||||
subdirs := self.list_child_dirs(source_dir_id)!
|
||||
for subdir in subdirs {
|
||||
subdir_source_path := if source_path == '' {
|
||||
subdir.name
|
||||
} else {
|
||||
join_path(source_path, subdir.name)
|
||||
}
|
||||
subdir_dest_path := join_path(dest_path, subdir.name)
|
||||
self.cp_directory(fs_id, subdir.id, subdir_source_path, subdir_dest_path, opts)!
|
||||
}
|
||||
}
|
||||
|
||||
// Move filesystem objects from source path to destination path
|
||||
pub fn (mut self FsTools) mv(fs_id u32, source_path string, dest_path string, opts MoveOptions) ! {
|
||||
normalized_source := normalize_path(source_path)
|
||||
normalized_dest := normalize_path(dest_path)
|
||||
|
||||
// Determine what we're moving
|
||||
source_dir_path, source_filename := split_path(normalized_source)
|
||||
|
||||
if source_filename == '' {
|
||||
// We're moving a directory
|
||||
source_dir := self.get_dir_by_absolute_path(fs_id, normalized_source)!
|
||||
self.mv_directory(fs_id, source_dir.id, normalized_dest)!
|
||||
} else {
|
||||
// We're moving a specific item
|
||||
source_parent_dir := self.get_dir_by_absolute_path(fs_id, source_dir_path)!
|
||||
|
||||
// Try to find what we're moving
|
||||
mut found := false
|
||||
|
||||
// Try file first
|
||||
if file := self.get_file_by_path(source_parent_dir.id, source_filename) {
|
||||
self.mv_file(fs_id, file.id, normalized_dest, opts)!
|
||||
found = true
|
||||
}
|
||||
|
||||
// Try symlink if file not found
|
||||
if !found {
|
||||
// Direct implementation since get_by_path doesn't exist for symlinks
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(source_parent_dir.id)!
|
||||
for symlink in symlinks {
|
||||
if symlink.name == source_filename {
|
||||
self.mv_symlink(fs_id, symlink.id, normalized_dest, opts)!
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try directory if neither file nor symlink found
|
||||
if !found {
|
||||
if subdir := self.find_child_dir_by_name(source_parent_dir.id, source_filename) {
|
||||
self.mv_directory(fs_id, subdir.id, normalized_dest)!
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return error('Source path "${source_path}" not found')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move a file to destination path
|
||||
fn (mut self FsTools) mv_file(fs_id u32, file_id u32, dest_path string, opts MoveOptions) ! {
|
||||
source_file := self.factory.fs_file.get(file_id)!
|
||||
|
||||
// Determine destination directory and filename
|
||||
dest_dir_path, mut dest_filename := split_path(dest_path)
|
||||
if dest_filename == '' {
|
||||
dest_filename = source_file.name
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
dest_dir_id := self.create_dir_path(fs_id, dest_dir_path)!
|
||||
|
||||
// Check if destination file already exists
|
||||
if existing_file := self.get_file_by_path(dest_dir_id, dest_filename) {
|
||||
if !opts.overwrite {
|
||||
return error('Destination file "${dest_path}" already exists. Use overwrite=true to replace.')
|
||||
}
|
||||
// Remove existing file
|
||||
self.factory.fs_file.delete(existing_file.id)!
|
||||
}
|
||||
|
||||
// Update file name if it's different
|
||||
// Direct implementation since rename doesn't exist for files
|
||||
if dest_filename != source_file.name {
|
||||
source_file.name = dest_filename
|
||||
self.factory.fs_file.set(source_file)!
|
||||
}
|
||||
|
||||
// Move file to new directory (replace all directory associations)
|
||||
// Direct implementation since move doesn't exist for files
|
||||
source_file.directories = [dest_dir_id]
|
||||
self.factory.fs_file.set(source_file)!
|
||||
}
|
||||
|
||||
// Move a symlink to destination path
|
||||
fn (mut self FsTools) mv_symlink(fs_id u32, symlink_id u32, dest_path string, opts MoveOptions) ! {
|
||||
source_symlink := self.factory.fs_symlink.get(symlink_id)!
|
||||
|
||||
if opts.follow_symlinks {
|
||||
// Follow the symlink and move its target instead
|
||||
if source_symlink.target_type == .file {
|
||||
self.mv_file(fs_id, source_symlink.target_id, dest_path, opts)!
|
||||
} else if source_symlink.target_type == .directory {
|
||||
self.mv_directory(fs_id, source_symlink.target_id, dest_path)!
|
||||
}
|
||||
// Remove the original symlink
|
||||
self.factory.fs_symlink.delete(symlink_id)!
|
||||
return
|
||||
}
|
||||
|
||||
// Move the symlink itself
|
||||
dest_dir_path, mut dest_filename := split_path(dest_path)
|
||||
if dest_filename == '' {
|
||||
dest_filename = source_symlink.name
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
dest_dir_id := self.create_dir_path(fs_id, dest_dir_path)!
|
||||
|
||||
// Check if destination symlink already exists
|
||||
// Direct implementation since get_by_path doesn't exist for symlinks
|
||||
symlinks := self.factory.fs_symlink.list_by_parent(dest_dir_id)!
|
||||
for existing_symlink in symlinks {
|
||||
if existing_symlink.name == dest_filename {
|
||||
if !opts.overwrite {
|
||||
return error('Destination symlink "${dest_path}" already exists. Use overwrite=true to replace.')
|
||||
}
|
||||
self.factory.fs_symlink.delete(existing_symlink.id)!
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Update symlink name if it's different
|
||||
// Direct implementation since rename doesn't exist for symlinks
|
||||
if dest_filename != source_symlink.name {
|
||||
source_symlink.name = dest_filename
|
||||
self.factory.fs_symlink.set(source_symlink)!
|
||||
}
|
||||
|
||||
// Move symlink to new parent directory
|
||||
// Direct implementation since move doesn't exist for symlinks
|
||||
source_symlink.parent_id = dest_dir_id
|
||||
self.factory.fs_symlink.set(source_symlink)!
|
||||
}
|
||||
|
||||
// Move a directory to destination path
|
||||
fn (mut self FsTools) mv_directory(fs_id u32, source_dir_id u32, dest_path string) ! {
|
||||
source_dir := self.factory.fs_dir.get(source_dir_id)!
|
||||
|
||||
// Parse destination path
|
||||
dest_parent_path, mut dest_dirname := split_path(dest_path)
|
||||
if dest_dirname == '' {
|
||||
dest_dirname = source_dir.name
|
||||
}
|
||||
|
||||
// Ensure destination parent directory exists
|
||||
dest_parent_id := if dest_parent_path == '/' {
|
||||
// Moving to root level, find root directory
|
||||
fs := self.factory.fs.get(fs_id)!
|
||||
fs.root_dir_id
|
||||
} else {
|
||||
self.create_dir_path(fs_id, dest_parent_path)!
|
||||
}
|
||||
|
||||
// Update directory name if it's different
|
||||
// Direct implementation since rename doesn't exist for directories
|
||||
if dest_dirname != source_dir.name {
|
||||
source_dir.name = dest_dirname
|
||||
self.factory.fs_dir.set(source_dir)!
|
||||
}
|
||||
|
||||
// Move directory to new parent
|
||||
// Direct implementation since move doesn't exist for directories
|
||||
source_dir.parent_id = dest_parent_id
|
||||
self.factory.fs_dir.set(source_dir)!
|
||||
}
|
||||
@@ -27,14 +27,14 @@ pub mut:
|
||||
}
|
||||
|
||||
// find searches for filesystem objects starting from a given path
|
||||
//
|
||||
//
|
||||
// Parameters:
|
||||
// - start_path: The path to start searching from
|
||||
// - opts: FindOptions struct with search parameters
|
||||
//
|
||||
//
|
||||
// Returns:
|
||||
// - []FindResult: Array of found filesystem objects
|
||||
//
|
||||
//
|
||||
// Example:
|
||||
// ```
|
||||
// results := tools.find('/', FindOptions{
|
||||
@@ -56,14 +56,14 @@ pub fn (mut self FsTools) find(start_path string, opts FindOptions) ![]FindResul
|
||||
}
|
||||
|
||||
// find_recursive is an internal function that recursively searches for filesystem objects
|
||||
//
|
||||
//
|
||||
// Parameters:
|
||||
// - dir_id: The ID of the current directory being searched
|
||||
// - current_path: The current path in the filesystem
|
||||
// - opts: FindOptions struct with search parameters
|
||||
// - results: Mutable array to store found filesystem objects
|
||||
// - current_depth: Current depth in the directory tree
|
||||
//
|
||||
//
|
||||
// This function handles three types of filesystem objects:
|
||||
// - Files: Direct files in the current directory
|
||||
// - Symlinks: Symbolic links in the current directory (handled according to opts.follow_symlinks)
|
||||
@@ -99,21 +99,20 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get symlinks in current directory
|
||||
|
||||
for symlink_id in current_dir.symlinks {
|
||||
symlink := self.factory.fs_symlink.get(symlink_id)!
|
||||
if should_include(symlink.name, opts.include_patterns, opts.exclude_patterns) {
|
||||
symlink_path := join_path(current_path, symlink.name)
|
||||
//only add symlink if not following them
|
||||
// only add symlink if not following them
|
||||
if !opts.follow_symlinks {
|
||||
results << FindResult{
|
||||
result_type: .symlink
|
||||
id: symlink.id
|
||||
path: symlink_path
|
||||
}
|
||||
}else{
|
||||
} 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)!
|
||||
@@ -123,8 +122,8 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
|
||||
id: target_file.id
|
||||
path: target_file_path
|
||||
}
|
||||
}else{
|
||||
//dangling symlink, just add the symlink itself
|
||||
} else {
|
||||
// dangling symlink, just add the symlink itself
|
||||
return error('Dangling symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.fs_id}')
|
||||
}
|
||||
}
|
||||
@@ -139,20 +138,20 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
|
||||
path: target_dir_path
|
||||
}
|
||||
if opts.recursive {
|
||||
self.find_recursive(symlink.target_id, target_dir_path, opts, mut results, current_depth + 1)!
|
||||
self.find_recursive(symlink.target_id, target_dir_path, opts, mut
|
||||
results, current_depth + 1)!
|
||||
}
|
||||
}else{
|
||||
//dangling symlink, just add the symlink itself
|
||||
} else {
|
||||
// dangling symlink, just add the symlink itself
|
||||
return error('Dangling dir symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.fs_id}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for dir_id in current_dir.directories {
|
||||
subdir := self.factory.fs_dir.get(dir_id)!
|
||||
for dir_id2 in current_dir.directories {
|
||||
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{
|
||||
@@ -167,5 +166,55 @@ fn (mut self FsTools) find_recursive(dir_id u32, current_path string, opts FindO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// get_dir_by_absolute_path resolves an absolute path to a directory ID
|
||||
//
|
||||
// Parameters:
|
||||
// - path: The absolute path to resolve (e.g., "/", "/home", "/home/user/documents")
|
||||
//
|
||||
// Returns:
|
||||
// - FsDir: The directory object at the specified path
|
||||
//
|
||||
// Example:
|
||||
// ```
|
||||
// dir := tools.get_dir_by_absolute_path('/home/user/documents')!
|
||||
// ```
|
||||
pub fn (mut self FsTools) get_dir_by_absolute_path(path string) !FsDir {
|
||||
normalized_path_ := normalize_path(path)
|
||||
|
||||
// Handle root directory case
|
||||
if normalized_path_ == '/' {
|
||||
fs := self.factory.fs.get(self.fs_id)!
|
||||
return self.factory.fs_dir.get(fs.root_dir_id)!
|
||||
}
|
||||
|
||||
// Split path into components, removing empty parts
|
||||
path_components := normalized_path_.trim_left('/').split('/').filter(it != '')
|
||||
|
||||
// Start from root directory
|
||||
fs := self.factory.fs.get(self.fs_id)!
|
||||
mut current_dir_id := fs.root_dir_id
|
||||
|
||||
// Navigate through each path component
|
||||
for component in path_components {
|
||||
current_dir := self.factory.fs_dir.get(current_dir_id)!
|
||||
|
||||
// Look for the component in the current directory's children
|
||||
mut found := false
|
||||
for child_dir_id in current_dir.directories {
|
||||
child_dir := self.factory.fs_dir.get(child_dir_id)!
|
||||
if child_dir.name == component {
|
||||
current_dir_id = child_dir_id
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return error('Directory "${component}" not found in path "${path}"')
|
||||
}
|
||||
}
|
||||
|
||||
return self.factory.fs_dir.get(current_dir_id)!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user