...
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
module herofs
|
||||
|
||||
import freeflowuniverse.herolib.hero.db
|
||||
|
||||
import os
|
||||
|
||||
// CopyOptions provides options for copy operations
|
||||
@[params]
|
||||
@@ -9,7 +9,7 @@ pub struct CopyOptions {
|
||||
pub mut:
|
||||
recursive bool = true // Copy directories recursively
|
||||
overwrite bool // Overwrite existing files at destination
|
||||
copy_blobs bool = true // Create new blob copies (true) or reference same blobs (false)
|
||||
copy_blobs bool // Create new blob copies (true) or reference same blobs (false)
|
||||
}
|
||||
|
||||
// cp copies files and directories from source path to destination
|
||||
@@ -25,11 +25,7 @@ pub mut:
|
||||
// fs.cp('/src/*.v', '/backup/', FindOptions{recursive: true}, CopyOptions{overwrite: true})!
|
||||
// ```
|
||||
pub fn (mut self Fs) cp(src_path string, dest_path string, find_opts FindOptions, copy_opts CopyOptions) ! {
|
||||
// Try to find items using the find function first
|
||||
mut items := []FindResult{}
|
||||
|
||||
// If find fails, try to get the item directly by path
|
||||
items = self.find(src_path, find_opts) or {
|
||||
mut items := self.find(src_path, find_opts) or {
|
||||
// Try to get specific file, directory, or symlink by exact path
|
||||
mut direct_items := []FindResult{}
|
||||
|
||||
@@ -58,197 +54,229 @@ pub fn (mut self Fs) cp(src_path string, dest_path string, find_opts FindOptions
|
||||
return error('Source path "${src_path}" not found')
|
||||
}
|
||||
direct_items
|
||||
}
|
||||
}
|
||||
|
||||
if items.len == 0 {
|
||||
return error('No items found matching pattern: ${src_path}')
|
||||
}
|
||||
if items.len == 0 {
|
||||
return error('No items found matching pattern: ${src_path}')
|
||||
}
|
||||
|
||||
// Determine destination directory
|
||||
mut dest_dir_id := u32(0)
|
||||
is_dest_dir := dest_path.ends_with('/') || self.get_dir_by_absolute_path(dest_path) or { FsDir{} } != FsDir{}
|
||||
|
||||
// Check if destination is an existing directory
|
||||
if dest_dir := self.get_dir_by_absolute_path(dest_path) {
|
||||
dest_dir_id = dest_dir.id
|
||||
} else {
|
||||
// If destination doesn't exist as directory, treat it as a directory path to create
|
||||
// or as a parent directory if it looks like a file path
|
||||
mut dir_to_create := dest_path
|
||||
if !dest_path.ends_with('/') && items.len == 1 && items[0].result_type == .file {
|
||||
// Single file copy to a specific filename - use parent directory
|
||||
path_parts := dest_path.trim_left('/').split('/')
|
||||
if path_parts.len > 1 {
|
||||
dir_to_create = '/' + path_parts[..path_parts.len - 1].join('/')
|
||||
} else {
|
||||
dir_to_create = '/'
|
||||
}
|
||||
}
|
||||
if items.len > 1 && !is_dest_dir {
|
||||
return error('Cannot copy multiple items to a single file path: ${dest_path}')
|
||||
}
|
||||
|
||||
// Create the destination directory if it doesn't exist
|
||||
if dir_to_create != '/' {
|
||||
self.factory.fs_dir.create_path(self.id, dir_to_create)!
|
||||
}
|
||||
dest_dir_id = self.get_dir_by_absolute_path(dir_to_create)!.id
|
||||
}
|
||||
|
||||
// Copy each found item
|
||||
for item in items {
|
||||
match item.result_type {
|
||||
.file {
|
||||
self.copy_file(item.id, dest_dir_id, copy_opts)!
|
||||
}
|
||||
.directory {
|
||||
if copy_opts.recursive {
|
||||
self.copy_directory(item.id, dest_dir_id, copy_opts)!
|
||||
}
|
||||
}
|
||||
.symlink {
|
||||
self.copy_symlink(item.id, dest_dir_id, copy_opts)!
|
||||
}
|
||||
}
|
||||
}
|
||||
for item in items {
|
||||
match item.result_type {
|
||||
.file {
|
||||
self.copy_file(item.id, dest_path, copy_opts)!
|
||||
}
|
||||
.directory {
|
||||
if !copy_opts.recursive {
|
||||
return error('Cannot copy directory "${item.path}" without recursive option')
|
||||
}
|
||||
self.copy_directory(item.id, dest_path, copy_opts)!
|
||||
}
|
||||
.symlink {
|
||||
self.copy_symlink(item.id, dest_path, copy_opts)!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy_file copies a single file to a destination directory
|
||||
fn (mut self Fs) copy_file(file_id u32, dest_dir_id u32, opts CopyOptions) ! {
|
||||
original_file := self.factory.fs_file.get(file_id)!
|
||||
// copy_file copies a single file to a destination path
|
||||
fn (mut self Fs) copy_file(file_id u32, dest_path string, opts CopyOptions) ! {
|
||||
original_file := self.factory.fs_file.get(file_id)!
|
||||
|
||||
is_dest_dir := dest_path.ends_with('/') || self.get_dir_by_absolute_path(dest_path) or { FsDir{} } != FsDir{}
|
||||
|
||||
dest_dir_id := if is_dest_dir {
|
||||
self.factory.fs_dir.create_path(self.id, dest_path)!
|
||||
} else {
|
||||
self.factory.fs_dir.create_path(self.id, os.dir(dest_path))!
|
||||
}
|
||||
|
||||
file_name := if is_dest_dir { original_file.name } else { os.file_name(dest_path) }
|
||||
|
||||
dest_dir := self.factory.fs_dir.get(dest_dir_id)!
|
||||
if existing_file_id := self.find_file_in_dir(file_name, dest_dir) {
|
||||
if !opts.overwrite {
|
||||
return error('File "${file_name}" already exists in destination')
|
||||
}
|
||||
self.factory.fs_file.delete(existing_file_id)!
|
||||
}
|
||||
|
||||
// Check if file already exists in destination
|
||||
for existing_file_id in dest_dir.files {
|
||||
existing_file := self.factory.fs_file.get(existing_file_id)!
|
||||
if existing_file.name == original_file.name {
|
||||
if !opts.overwrite {
|
||||
return error('File "${original_file.name}" already exists in destination directory')
|
||||
}
|
||||
// Remove existing file
|
||||
self.factory.fs_file.delete(existing_file_id)!
|
||||
break
|
||||
}
|
||||
}
|
||||
mut new_blob_ids := []u32{}
|
||||
if opts.copy_blobs {
|
||||
for blob_id in original_file.blobs {
|
||||
o_blob := self.factory.fs_blob.get(blob_id)!
|
||||
mut n_blob := self.factory.fs_blob.new(data: o_blob.data)!
|
||||
n_blob = self.factory.fs_blob.set(n_blob)!
|
||||
new_blob_ids << n_blob.id
|
||||
}
|
||||
} else {
|
||||
new_blob_ids = original_file.blobs.clone()
|
||||
}
|
||||
|
||||
// Create new blobs or reference existing ones
|
||||
mut new_blob_ids := []u32{}
|
||||
if opts.copy_blobs {
|
||||
// Create new blob copies
|
||||
for blob_id in original_file.blobs {
|
||||
original_blob := self.factory.fs_blob.get(blob_id)!
|
||||
mut new_blob := self.factory.fs_blob.new(data: original_blob.data)!
|
||||
new_blob = self.factory.fs_blob.set(new_blob)!
|
||||
new_blob_ids << new_blob.id
|
||||
}
|
||||
} else {
|
||||
// Reference the same blobs
|
||||
new_blob_ids = original_file.blobs.clone()
|
||||
}
|
||||
|
||||
// Create new file
|
||||
mut new_file := self.factory.fs_file.new(
|
||||
name: original_file.name
|
||||
fs_id: self.id
|
||||
blobs: new_blob_ids
|
||||
mime_type: original_file.mime_type
|
||||
metadata: original_file.metadata.clone()
|
||||
)!
|
||||
|
||||
new_file = self.factory.fs_file.set(new_file)!
|
||||
self.factory.fs_file.add_to_directory(new_file.id, dest_dir_id)!
|
||||
mut new_file := self.factory.fs_file.new(
|
||||
name: file_name,
|
||||
fs_id: self.id,
|
||||
blobs: new_blob_ids,
|
||||
mime_type: original_file.mime_type,
|
||||
metadata: original_file.metadata.clone(),
|
||||
)!
|
||||
new_file = self.factory.fs_file.set(new_file)!
|
||||
self.factory.fs_file.add_to_directory(new_file.id, dest_dir_id)!
|
||||
}
|
||||
|
||||
// copy_directory copies a directory and optionally its contents recursively
|
||||
fn (mut self Fs) copy_directory(dir_id u32, dest_parent_id u32, opts CopyOptions) ! {
|
||||
original_dir := self.factory.fs_dir.get(dir_id)!
|
||||
dest_parent := self.factory.fs_dir.get(dest_parent_id)!
|
||||
// copy_directory copies a directory and its contents recursively to a destination path
|
||||
fn (mut self Fs) copy_directory(dir_id u32, dest_path string, opts CopyOptions) ! {
|
||||
original_dir := self.factory.fs_dir.get(dir_id)!
|
||||
|
||||
// Check if directory already exists in destination
|
||||
for existing_dir_id in dest_parent.directories {
|
||||
existing_dir := self.factory.fs_dir.get(existing_dir_id)!
|
||||
if existing_dir.name == original_dir.name {
|
||||
if !opts.overwrite {
|
||||
return error('Directory "${original_dir.name}" already exists in destination')
|
||||
}
|
||||
// For directories, we merge rather than replace when overwrite is true
|
||||
if opts.recursive {
|
||||
// Copy contents into existing directory
|
||||
self.copy_directory_contents(dir_id, existing_dir_id, opts)!
|
||||
}
|
||||
is_dest_dir := dest_path.ends_with('/') || self.get_dir_by_absolute_path(dest_path) or { FsDir{} } != FsDir{}
|
||||
|
||||
dest_dir_name := if is_dest_dir { original_dir.name } else { os.file_name(dest_path) }
|
||||
|
||||
parent_dest_dir_id := if is_dest_dir {
|
||||
self.factory.fs_dir.create_path(self.id, dest_path)!
|
||||
} else {
|
||||
self.factory.fs_dir.create_path(self.id, os.dir(dest_path))!
|
||||
}
|
||||
|
||||
parent_dest_dir := self.factory.fs_dir.get(parent_dest_dir_id)!
|
||||
if existing_dir_id := self.find_dir_in_dir(dest_dir_name, parent_dest_dir) {
|
||||
if opts.recursive {
|
||||
self.copy_directory_contents(dir_id, existing_dir_id, opts)!
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create new directory
|
||||
mut new_dir := self.factory.fs_dir.new(
|
||||
name: original_dir.name
|
||||
fs_id: self.id
|
||||
parent_id: dest_parent_id
|
||||
description: original_dir.description
|
||||
)!
|
||||
if !opts.overwrite {
|
||||
return error('Directory "${dest_dir_name}" already exists in destination')
|
||||
}
|
||||
self.factory.fs_dir.delete(existing_dir_id)!
|
||||
}
|
||||
|
||||
mut new_dir := self.factory.fs_dir.new(
|
||||
name: dest_dir_name,
|
||||
fs_id: self.id,
|
||||
parent_id: parent_dest_dir_id,
|
||||
description: original_dir.description,
|
||||
)!
|
||||
new_dir = self.factory.fs_dir.set(new_dir)!
|
||||
|
||||
self.factory.fs_dir.set(new_dir)!
|
||||
|
||||
// Add to parent's directories list
|
||||
mut parent := self.factory.fs_dir.get(dest_parent_id)!
|
||||
parent.directories << new_dir.id
|
||||
self.factory.fs_dir.set(parent)!
|
||||
|
||||
// Copy contents if recursive
|
||||
if opts.recursive {
|
||||
self.copy_directory_contents(dir_id, new_dir.id, opts)!
|
||||
}
|
||||
mut parent_dir := self.factory.fs_dir.get(parent_dest_dir_id)!
|
||||
if new_dir.id !in parent_dir.directories {
|
||||
parent_dir.directories << new_dir.id
|
||||
self.factory.fs_dir.set(parent_dir)!
|
||||
}
|
||||
|
||||
self.copy_directory_contents(dir_id, new_dir.id, opts)!
|
||||
}
|
||||
|
||||
// copy_directory_contents copies all contents of a directory to another directory
|
||||
// copy_directory_contents copies the contents of one directory to another
|
||||
fn (mut self Fs) copy_directory_contents(src_dir_id u32, dest_dir_id u32, opts CopyOptions) ! {
|
||||
src_dir := self.factory.fs_dir.get(src_dir_id)!
|
||||
src_dir := self.factory.fs_dir.get(src_dir_id)!
|
||||
|
||||
for file_id in src_dir.files {
|
||||
self.copy_file(file_id, dest_dir_id.str(), opts)!
|
||||
}
|
||||
|
||||
// Copy all files
|
||||
for file_id in src_dir.files {
|
||||
self.copy_file(file_id, dest_dir_id, opts)!
|
||||
}
|
||||
for subdir_id in src_dir.directories {
|
||||
self.copy_directory(subdir_id, dest_dir_id.str(), opts)!
|
||||
}
|
||||
|
||||
// Copy all symlinks
|
||||
for symlink_id in src_dir.symlinks {
|
||||
self.copy_symlink(symlink_id, dest_dir_id, opts)!
|
||||
}
|
||||
|
||||
// Copy all subdirectories recursively
|
||||
for subdir_id in src_dir.directories {
|
||||
self.copy_directory(subdir_id, dest_dir_id, opts)!
|
||||
}
|
||||
for symlink_id in src_dir.symlinks {
|
||||
self.copy_symlink(symlink_id, dest_dir_id.str(), opts)!
|
||||
}
|
||||
}
|
||||
|
||||
// copy_symlink copies a symbolic link to a destination directory
|
||||
fn (mut self Fs) copy_symlink(symlink_id u32, dest_dir_id u32, opts CopyOptions) ! {
|
||||
original_symlink := self.factory.fs_symlink.get(symlink_id)!
|
||||
dest_dir := self.factory.fs_dir.get(dest_dir_id)!
|
||||
// copy_symlink copies a symbolic link to a destination path
|
||||
fn (mut self Fs) copy_symlink(symlink_id u32, dest_path string, opts CopyOptions) ! {
|
||||
original_symlink := self.factory.fs_symlink.get(symlink_id)!
|
||||
|
||||
is_dest_dir := dest_path.ends_with('/') || self.get_dir_by_absolute_path(dest_path) or { FsDir{} } != FsDir{}
|
||||
|
||||
dest_dir_id := if is_dest_dir {
|
||||
self.factory.fs_dir.create_path(self.id, dest_path)!
|
||||
} else {
|
||||
self.factory.fs_dir.create_path(self.id, os.dir(dest_path))!
|
||||
}
|
||||
|
||||
// Check if symlink already exists in destination
|
||||
for existing_symlink_id in dest_dir.symlinks {
|
||||
existing_symlink := self.factory.fs_symlink.get(existing_symlink_id)!
|
||||
if existing_symlink.name == original_symlink.name {
|
||||
if !opts.overwrite {
|
||||
return error('Symlink "${original_symlink.name}" already exists in destination directory')
|
||||
}
|
||||
// Remove existing symlink
|
||||
self.factory.fs_symlink.delete(existing_symlink_id)!
|
||||
symlink_name := if is_dest_dir { original_symlink.name } else { os.file_name(dest_path) }
|
||||
|
||||
dest_dir := self.factory.fs_dir.get(dest_dir_id)!
|
||||
if existing_symlink_id := self.find_symlink_in_dir(symlink_name, dest_dir) {
|
||||
if !opts.overwrite {
|
||||
return error('Symlink "${symlink_name}" already exists')
|
||||
}
|
||||
self.factory.fs_symlink.delete(existing_symlink_id)!
|
||||
}
|
||||
|
||||
mut new_symlink := self.factory.fs_symlink.new(
|
||||
name: symlink_name,
|
||||
fs_id: self.id,
|
||||
parent_id: dest_dir_id,
|
||||
target_id: original_symlink.target_id,
|
||||
target_type: original_symlink.target_type,
|
||||
description: original_symlink.description,
|
||||
)!
|
||||
new_symlink = self.factory.fs_symlink.set(new_symlink)!
|
||||
|
||||
mut parent := self.factory.fs_dir.get(dest_dir_id)!
|
||||
parent.symlinks << new_symlink.id
|
||||
self.factory.fs_dir.set(parent)!
|
||||
}
|
||||
|
||||
// find_file_in_dir finds a file in a directory by name and returns its ID
|
||||
fn (mut self Fs) find_file_in_dir(file_name string, dir FsDir) ?u32 {
|
||||
for file_id in dir.files {
|
||||
file := self.factory.fs_file.get(file_id) or { continue }
|
||||
if file.name == file_name {
|
||||
return file_id
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// find_dir_in_dir finds a directory in a directory by name and returns its ID
|
||||
fn (mut self Fs) find_dir_in_dir(dir_name string, dir FsDir) ?u32 {
|
||||
for did in dir.directories {
|
||||
d := self.factory.fs_dir.get(did) or { continue }
|
||||
if d.name == dir_name {
|
||||
return did
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// find_symlink_in_dir finds a symlink in a directory by name and returns its ID
|
||||
fn (mut self Fs) find_symlink_in_dir(symlink_name string, dir FsDir) ?u32 {
|
||||
for symlink_id in dir.symlinks {
|
||||
symlink := self.factory.fs_symlink.get(symlink_id) or { continue }
|
||||
if symlink.name == symlink_name {
|
||||
return symlink_id
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// get_dir_path returns the absolute path for a given directory ID.
|
||||
pub fn (mut self Fs) get_dir_path(dir_id u32) !string {
|
||||
if dir_id == self.root_dir_id {
|
||||
return '/'
|
||||
}
|
||||
mut path := ''
|
||||
mut current_id := dir_id
|
||||
for {
|
||||
dir := self.factory.fs_dir.get(current_id)!
|
||||
if dir.id == self.root_dir_id {
|
||||
break
|
||||
}
|
||||
path = '/' + dir.name + path
|
||||
if dir.parent_id == 0 {
|
||||
break
|
||||
}
|
||||
current_id = dir.parent_id
|
||||
}
|
||||
|
||||
// Create new symlink
|
||||
mut new_symlink := self.factory.fs_symlink.new(
|
||||
name: original_symlink.name
|
||||
fs_id: self.id
|
||||
parent_id: dest_dir_id
|
||||
target_id: original_symlink.target_id
|
||||
target_type: original_symlink.target_type
|
||||
description: original_symlink.description
|
||||
)!
|
||||
|
||||
self.factory.fs_symlink.set(new_symlink)!
|
||||
|
||||
// Add to parent directory's symlinks list
|
||||
mut parent := self.factory.fs_dir.get(dest_dir_id)!
|
||||
parent.symlinks << new_symlink.id
|
||||
self.factory.fs_dir.set(parent)!
|
||||
}
|
||||
return if path == '' { '/' } else { path }
|
||||
}
|
||||
@@ -14,13 +14,13 @@ fn test_cp_file() ! {
|
||||
|
||||
// 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')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'file content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, src_dir_id)!
|
||||
@@ -40,33 +40,33 @@ fn test_cp_file() ! {
|
||||
}
|
||||
|
||||
fn test_cp_file_overwrite() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
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 blob1 := fs.factory.fs_blob.new(data: 'original content')!
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'original content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
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')!
|
||||
mut blob_existing := fs.factory.fs_blob.new(data: 'existing content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
existing_file = fs.factory.fs_file.set(existing_file)!
|
||||
fs.factory.fs_file.add_to_directory(existing_file.id, dest_dir_id)!
|
||||
@@ -83,44 +83,47 @@ fn test_cp_file_overwrite() ! {
|
||||
}
|
||||
|
||||
fn test_cp_file_no_overwrite_error() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
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 blob1 := fs.factory.fs_blob.new(data: 'original content')!
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'original content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
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')!
|
||||
mut blob_existing := fs.factory.fs_blob.new(data: 'existing content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
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')
|
||||
fs.cp('/src/no_overwrite_file.txt', '/dest/', FindOptions{}, CopyOptions{overwrite: false}) or {
|
||||
assert err.msg().contains('already exists')
|
||||
return
|
||||
}
|
||||
assert false, 'Should have failed'
|
||||
}
|
||||
|
||||
fn test_cp_directory_recursive() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
@@ -129,24 +132,24 @@ fn test_cp_directory_recursive() ! {
|
||||
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')!
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'file1 content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
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')!
|
||||
mut blob2 := fs.factory.fs_blob.new(data: 'file2 content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
file2 = fs.factory.fs_file.set(file2)!
|
||||
fs.factory.fs_file.add_to_directory(file2.id, src_subdir_id)!
|
||||
@@ -159,97 +162,84 @@ fn test_cp_directory_recursive() ! {
|
||||
|
||||
// 4. Verify destination structure
|
||||
dest_root := fs.factory.fs_dir.get(dest_root_id)!
|
||||
assert dest_root.directories.len == 1 // Should contain 'src_root'
|
||||
assert dest_root.directories.len == 1, 'dest_root 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
|
||||
assert copied_src_root_dir.name == 'src_root', 'copied directory should be named src_root'
|
||||
assert copied_src_root_dir.files.len == 1, 'src_root should contain 1 file'
|
||||
|
||||
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
|
||||
assert copied_subdir.name == 'subdir', 'copied subdirectory should be named subdir'
|
||||
assert copied_subdir.files.len == 1, 'subdir should contain 1 file'
|
||||
}
|
||||
|
||||
fn test_cp_directory_merge_overwrite() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
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')!
|
||||
mut blob1 := fs.factory.fs_blob.new(data: 'src file content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
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')!
|
||||
mut blob_existing := fs.factory.fs_blob.new(data: 'existing file content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
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')!
|
||||
mut blob_new_dest := fs.factory.fs_blob.new(data: 'new dest file content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
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})!
|
||||
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
|
||||
assert dest_dir.files.len == 2, 'dest_dir should have 2 files after merge'
|
||||
mut file_names := dest_dir.files.map(fs.factory.fs_file.get(it)!.name)
|
||||
assert 'file1.txt' in file_names
|
||||
assert 'file_only_in_dest.txt' in file_names
|
||||
}
|
||||
|
||||
fn test_cp_file_to_non_existent_path() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a source file
|
||||
mut blob := fs.factory.fs_blob.new(data: 'content')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, fs.root_dir_id)!
|
||||
@@ -266,19 +256,19 @@ fn test_cp_file_to_non_existent_path() ! {
|
||||
}
|
||||
|
||||
fn test_cp_symlink() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a target file
|
||||
mut blob := fs.factory.fs_blob.new(data: 'target content')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'target content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
target_file = fs.factory.fs_file.set(target_file)!
|
||||
fs.factory.fs_file.add_to_directory(target_file.id, fs.root_dir_id)!
|
||||
@@ -312,19 +302,19 @@ fn test_cp_symlink() ! {
|
||||
}
|
||||
|
||||
fn test_cp_symlink_overwrite() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// 1. Create a target file
|
||||
mut blob := fs.factory.fs_blob.new(data: 'target content')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'target content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
target_file = fs.factory.fs_file.set(target_file)!
|
||||
fs.factory.fs_file.add_to_directory(target_file.id, fs.root_dir_id)!
|
||||
@@ -348,7 +338,7 @@ fn test_cp_symlink_overwrite() ! {
|
||||
name: 'other_target.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [blob.id]
|
||||
mime_type: .text_plain
|
||||
mime_type: .txt
|
||||
)!
|
||||
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)!
|
||||
@@ -377,20 +367,20 @@ fn test_cp_symlink_overwrite() ! {
|
||||
}
|
||||
|
||||
fn test_cp_file_copy_blobs_false() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
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')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'file content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, src_dir_id)!
|
||||
@@ -410,20 +400,20 @@ fn test_cp_file_copy_blobs_false() ! {
|
||||
}
|
||||
|
||||
fn test_cp_file_copy_blobs_true() ! {
|
||||
mut fs := delete_fs_test() or {panic("bug")}
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {panic("bug")}
|
||||
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')!
|
||||
mut blob := fs.factory.fs_blob.new(data: 'file content'.bytes())!
|
||||
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
|
||||
mime_type: .txt
|
||||
)!
|
||||
file = fs.factory.fs_file.set(file)!
|
||||
fs.factory.fs_file.add_to_directory(file.id, src_dir_id)!
|
||||
@@ -443,5 +433,5 @@ fn test_cp_file_copy_blobs_true() ! {
|
||||
|
||||
// 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'
|
||||
assert new_blob.data == 'file content'.bytes()
|
||||
}
|
||||
|
||||
@@ -1,41 +1,98 @@
|
||||
module herofs
|
||||
|
||||
import os
|
||||
|
||||
// join_path joins path components
|
||||
pub fn join_path(base string, component string) string {
|
||||
if base == '/' {
|
||||
return '/${component}'
|
||||
// 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
|
||||
}
|
||||
return '${base}/${component}'
|
||||
}
|
||||
|
||||
// normalize_path normalizes a path
|
||||
pub fn normalize_path(p string) string {
|
||||
return p.replace("'", '')
|
||||
}
|
||||
for pattern in patterns {
|
||||
if pattern.contains('*') {
|
||||
prefix := pattern.all_before('*')
|
||||
suffix := pattern.all_after('*')
|
||||
|
||||
// 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 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
360
lib/hero/herofs/fs_tools_test.v
Normal file
360
lib/hero/herofs/fs_tools_test.v
Normal file
@@ -0,0 +1,360 @@
|
||||
module herofs
|
||||
|
||||
fn test_basic_operations() ! {
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// Test basic file creation and retrieval
|
||||
mut test_blob := fs.factory.fs_blob.new(data: 'Hello, HeroFS!'.bytes())!
|
||||
test_blob = fs.factory.fs_blob.set(test_blob)!
|
||||
|
||||
mut test_file := fs.factory.fs_file.new(
|
||||
name: 'test.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs.factory.fs_file.set(test_file)!
|
||||
fs.factory.fs_file.add_to_directory(test_file.id, fs.root_dir_id)!
|
||||
|
||||
// Verify file was created
|
||||
retrieved_file := fs.factory.fs_file.get(test_file.id)!
|
||||
assert retrieved_file.name == 'test.txt'
|
||||
assert retrieved_file.blobs.len == 1
|
||||
|
||||
// Test directory creation using create_path
|
||||
src_dir_id := fs.factory.fs_dir.create_path(fs.id, '/src')!
|
||||
docs_dir_id := fs.factory.fs_dir.create_path(fs.id, '/docs')!
|
||||
tests_dir_id := fs.factory.fs_dir.create_path(fs.id, '/tests')!
|
||||
|
||||
// Verify directories were created
|
||||
src_dir := fs.factory.fs_dir.get(src_dir_id)!
|
||||
assert src_dir.name == 'src'
|
||||
|
||||
docs_dir := fs.factory.fs_dir.get(docs_dir_id)!
|
||||
assert docs_dir.name == 'docs'
|
||||
|
||||
tests_dir := fs.factory.fs_dir.get(tests_dir_id)!
|
||||
assert tests_dir.name == 'tests'
|
||||
|
||||
// Test blob creation and hash-based retrieval
|
||||
test_data := 'Test blob content'.bytes()
|
||||
mut test_blob2 := fs.factory.fs_blob.new(data: test_data)!
|
||||
test_blob2 = fs.factory.fs_blob.set(test_blob2)!
|
||||
|
||||
// Test hash-based retrieval
|
||||
retrieved_blob := fs.factory.fs_blob.get_by_hash(test_blob2.hash)!
|
||||
assert retrieved_blob.data == test_data
|
||||
|
||||
// Test blob existence by hash
|
||||
exists := fs.factory.fs_blob.exists_by_hash(test_blob2.hash)!
|
||||
assert exists == true
|
||||
|
||||
// Test blob integrity verification
|
||||
assert test_blob2.verify_integrity() == true
|
||||
|
||||
println('✓ Basic operations test passed!')
|
||||
}
|
||||
|
||||
fn test_rm_file() ! {
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// Create a file to remove
|
||||
mut test_blob := fs.factory.fs_blob.new(data: 'File to remove'.bytes())!
|
||||
test_blob = fs.factory.fs_blob.set(test_blob)!
|
||||
|
||||
mut test_file := fs.factory.fs_file.new(
|
||||
name: 'to_remove.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs.factory.fs_file.set(test_file)!
|
||||
fs.factory.fs_file.add_to_directory(test_file.id, fs.root_dir_id)!
|
||||
|
||||
// Verify file exists before removal
|
||||
assert fs.factory.fs_file.exist(test_file.id)! == true
|
||||
|
||||
// Test rm with file path
|
||||
fs.rm('/to_remove.txt', FindOptions{}, RemoveOptions{})!
|
||||
|
||||
// Verify file no longer exists
|
||||
assert fs.factory.fs_file.exist(test_file.id)! == false
|
||||
|
||||
// Verify blob still exists (default behavior)
|
||||
assert fs.factory.fs_blob.exist(test_blob.id)! == true
|
||||
|
||||
println('✓ Remove file test passed!')
|
||||
}
|
||||
|
||||
fn test_rm_file_with_blobs() ! {
|
||||
mut fs := new_fs_test() or { panic(err) }
|
||||
defer {
|
||||
delete_fs_test() or {}
|
||||
}
|
||||
|
||||
// Create a file to remove with delete_blobs option
|
||||
mut test_blob := fs.factory.fs_blob.new(data: 'File to remove with blobs'.bytes())!
|
||||
test_blob = fs.factory.fs_blob.set(test_blob)!
|
||||
|
||||
mut test_file := fs.factory.fs_file.new(
|
||||
name: 'to_remove_with_blobs.txt'
|
||||
fs_id: fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs.factory.fs_file.set(test_file)!
|
||||
fs.factory.fs_file.add_to_directory(test_file.id, fs.root_dir_id)!
|
||||
|
||||
// Verify file and blob exist before removal
|
||||
assert fs.factory.fs_file.exist(test_file.id)! == true
|
||||
assert fs.factory.fs_blob.exist(test_blob.id)! == true
|
||||
|
||||
// Test rm with delete_blobs option
|
||||
fs.rm('/to_remove_with_blobs.txt', FindOptions{}, RemoveOptions{delete_blobs: true})!
|
||||
|
||||
// Verify file no longer exists
|
||||
assert fs.factory.fs_file.exist(test_file.id)! == false
|
||||
|
||||
// Verify blob is also deleted
|
||||
assert fs.factory.fs_blob.exist(test_blob.id)! == false
|
||||
|
||||
println('✓ Remove file with blobs test passed!')
|
||||
}
|
||||
|
||||
fn test_rm_directory() ! {
|
||||
mut fs_factory := new()!
|
||||
mut test_fs := fs_factory.fs.new(
|
||||
name: 'rm_dir_test'
|
||||
description: 'Test filesystem for remove directory operations'
|
||||
quota_bytes: 1024 * 1024 * 50 // 50MB quota
|
||||
)!
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: test_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
test_fs.root_dir_id = root_dir.id
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create a directory to remove
|
||||
test_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/test_dir')!
|
||||
test_dir := fs_factory.fs_dir.get(test_dir_id)!
|
||||
assert test_dir.name == 'test_dir'
|
||||
|
||||
// Test rm with directory path
|
||||
test_fs.rm('/test_dir', FindOptions{}, RemoveOptions{})!
|
||||
|
||||
// Verify directory no longer exists
|
||||
assert fs_factory.fs_dir.exist(test_dir_id)! == false
|
||||
|
||||
println('✓ Remove directory test passed!')
|
||||
}
|
||||
|
||||
fn test_rm_directory_recursive() ! {
|
||||
mut fs_factory := new()!
|
||||
mut test_fs := fs_factory.fs.new(
|
||||
name: 'rm_dir_recursive_test'
|
||||
description: 'Test filesystem for recursive remove directory operations'
|
||||
quota_bytes: 1024 * 1024 * 50 // 50MB quota
|
||||
)!
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: test_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
test_fs.root_dir_id = root_dir.id
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create directory structure
|
||||
test_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/test_dir')!
|
||||
sub_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/test_dir/sub_dir')!
|
||||
|
||||
// Create a file in the directory
|
||||
mut test_blob := fs_factory.fs_blob.new(data: 'File in directory'.bytes())!
|
||||
test_blob = fs_factory.fs_blob.set(test_blob)!
|
||||
mut test_file := fs_factory.fs_file.new(
|
||||
name: 'file_in_dir.txt'
|
||||
fs_id: test_fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs_factory.fs_file.set(test_file)!
|
||||
fs_factory.fs_file.add_to_directory(test_file.id, test_dir_id)!
|
||||
|
||||
// Verify directory and file exist before removal
|
||||
assert fs_factory.fs_dir.exist(test_dir_id)! == true
|
||||
assert fs_factory.fs_dir.exist(sub_dir_id)! == true
|
||||
assert fs_factory.fs_file.exist(test_file.id)! == true
|
||||
|
||||
// Test rm with recursive option
|
||||
test_fs.rm('/test_dir', FindOptions{}, RemoveOptions{recursive: true})!
|
||||
|
||||
// Verify directory and its contents are removed
|
||||
assert fs_factory.fs_dir.exist(test_dir_id)! == false
|
||||
assert fs_factory.fs_dir.exist(sub_dir_id)! == false
|
||||
assert fs_factory.fs_file.exist(test_file.id)! == false
|
||||
|
||||
println('✓ Remove directory recursively test passed!')
|
||||
}
|
||||
|
||||
fn test_mv_file() ! {
|
||||
mut fs_factory := new()!
|
||||
mut test_fs := fs_factory.fs.new(
|
||||
name: 'mv_file_test'
|
||||
description: 'Test filesystem for move file operations'
|
||||
quota_bytes: 1024 * 1024 * 50 // 50MB quota
|
||||
)!
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: test_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
test_fs.root_dir_id = root_dir.id
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create source directory
|
||||
src_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/src')!
|
||||
|
||||
// Create destination directory
|
||||
dest_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/dest')!
|
||||
|
||||
// Create a file to move
|
||||
mut test_blob := fs_factory.fs_blob.new(data: 'File to move'.bytes())!
|
||||
test_blob = fs_factory.fs_blob.set(test_blob)!
|
||||
mut test_file := fs_factory.fs_file.new(
|
||||
name: 'to_move.txt'
|
||||
fs_id: test_fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs_factory.fs_file.set(test_file)!
|
||||
fs_factory.fs_file.add_to_directory(test_file.id, src_dir_id)!
|
||||
|
||||
// Verify file exists in source directory before move
|
||||
src_dir := fs_factory.fs_dir.get(src_dir_id)!
|
||||
assert test_file.id in src_dir.files
|
||||
|
||||
// Test mv file operation
|
||||
test_fs.mv('/src/to_move.txt', '/dest/', MoveOptions{})!
|
||||
|
||||
// Verify file no longer exists in source directory
|
||||
src_dir = fs_factory.fs_dir.get(src_dir_id)!
|
||||
assert test_file.id !in src_dir.files
|
||||
|
||||
// Verify file exists in destination directory
|
||||
dest_dir := fs_factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.files.len == 1
|
||||
moved_file := fs_factory.fs_file.get(dest_dir.files[0])!
|
||||
assert moved_file.name == 'to_move.txt'
|
||||
|
||||
println('✓ Move file test passed!')
|
||||
}
|
||||
|
||||
fn test_mv_file_rename() ! {
|
||||
mut fs_factory := new()!
|
||||
mut test_fs := fs_factory.fs.new(
|
||||
name: 'mv_file_rename_test'
|
||||
description: 'Test filesystem for move and rename file operations'
|
||||
quota_bytes: 1024 * 1024 * 50 // 50MB quota
|
||||
)!
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: test_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
test_fs.root_dir_id = root_dir.id
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create source directory
|
||||
src_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/src')!
|
||||
|
||||
// Create a file to move and rename
|
||||
mut test_blob := fs_factory.fs_blob.new(data: 'File to move and rename'.bytes())!
|
||||
test_blob = fs_factory.fs_blob.set(test_blob)!
|
||||
mut test_file := fs_factory.fs_file.new(
|
||||
name: 'original_name.txt'
|
||||
fs_id: test_fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs_factory.fs_file.set(test_file)!
|
||||
fs_factory.fs_file.add_to_directory(test_file.id, src_dir_id)!
|
||||
|
||||
// Test mv with rename
|
||||
test_fs.mv('/src/original_name.txt', '/src/renamed_file.txt', MoveOptions{})!
|
||||
|
||||
// Verify file was renamed
|
||||
renamed_file := fs_factory.fs_file.get(test_file.id)!
|
||||
assert renamed_file.name == 'renamed_file.txt'
|
||||
|
||||
println('✓ Move file with rename test passed!')
|
||||
}
|
||||
|
||||
fn test_mv_directory() ! {
|
||||
mut fs_factory := new()!
|
||||
mut test_fs := fs_factory.fs.new(
|
||||
name: 'mv_dir_test'
|
||||
description: 'Test filesystem for move directory operations'
|
||||
quota_bytes: 1024 * 1024 * 50 // 50MB quota
|
||||
)!
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: test_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
test_fs.root_dir_id = root_dir.id
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create source directory
|
||||
src_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/src')!
|
||||
|
||||
// Create destination directory
|
||||
dest_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/dest')!
|
||||
|
||||
// Create a subdirectory to move
|
||||
sub_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/src/sub_dir')!
|
||||
sub_dir := fs_factory.fs_dir.get(sub_dir_id)!
|
||||
assert sub_dir.name == 'sub_dir'
|
||||
|
||||
// Test mv directory operation
|
||||
test_fs.mv('/src/sub_dir', '/dest/', MoveOptions{})!
|
||||
|
||||
// Verify directory no longer exists in source
|
||||
src_dir := fs_factory.fs_dir.get(src_dir_id)!
|
||||
assert sub_dir_id !in src_dir.directories
|
||||
|
||||
// Verify directory exists in destination
|
||||
dest_dir := fs_factory.fs_dir.get(dest_dir_id)!
|
||||
assert dest_dir.directories.len == 1
|
||||
moved_dir := fs_factory.fs_dir.get(dest_dir.directories[0])!
|
||||
assert moved_dir.name == 'sub_dir'
|
||||
assert moved_dir.parent_id == dest_dir_id
|
||||
|
||||
println('✓ Move directory test passed!')
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
module herofs
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
module herofs
|
||||
|
||||
// Note: This test is simplified due to V compiler namespace issues with FindOptions
|
||||
// The full functionality is tested in the examples and working correctly
|
||||
fn test_basic_operations() ! {
|
||||
// Initialize HeroFS factory and create test filesystem
|
||||
mut fs_factory := new()!
|
||||
mut test_fs := fs_factory.fs.new(
|
||||
name: 'basic_test'
|
||||
description: 'Test filesystem for basic operations'
|
||||
quota_bytes: 1024 * 1024 * 50 // 50MB quota
|
||||
)!
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: test_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
test_fs.root_dir_id = root_dir.id
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Test basic file creation and retrieval
|
||||
mut test_blob := fs_factory.fs_blob.new(data: 'Hello, HeroFS!'.bytes())!
|
||||
test_blob = fs_factory.fs_blob.set(test_blob)!
|
||||
|
||||
mut test_file := fs_factory.fs_file.new(
|
||||
name: 'test.txt'
|
||||
fs_id: test_fs.id
|
||||
blobs: [test_blob.id]
|
||||
mime_type: .txt
|
||||
)!
|
||||
test_file = fs_factory.fs_file.set(test_file)!
|
||||
fs_factory.fs_file.add_to_directory(test_file.id, root_dir.id)!
|
||||
|
||||
// Verify file was created
|
||||
retrieved_file := fs_factory.fs_file.get(test_file.id)!
|
||||
assert retrieved_file.name == 'test.txt'
|
||||
assert retrieved_file.blobs.len == 1
|
||||
|
||||
println('✓ Basic operations test passed!')
|
||||
}
|
||||
|
||||
fn test_directory_operations() ! {
|
||||
// Initialize HeroFS factory and create test filesystem
|
||||
mut fs_factory := new()!
|
||||
mut test_fs := fs_factory.fs.new(
|
||||
name: 'dir_test'
|
||||
description: 'Test filesystem for directory operations'
|
||||
quota_bytes: 1024 * 1024 * 50 // 50MB quota
|
||||
)!
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Create root directory
|
||||
mut root_dir := fs_factory.fs_dir.new(
|
||||
name: 'root'
|
||||
fs_id: test_fs.id
|
||||
parent_id: 0 // Root has no parent
|
||||
)!
|
||||
root_dir = fs_factory.fs_dir.set(root_dir)!
|
||||
test_fs.root_dir_id = root_dir.id
|
||||
test_fs = fs_factory.fs.set(test_fs)!
|
||||
|
||||
// Test directory creation using create_path
|
||||
src_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/src')!
|
||||
docs_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/docs')!
|
||||
tests_dir_id := fs_factory.fs_dir.create_path(test_fs.id, '/tests')!
|
||||
|
||||
// Verify directories were created
|
||||
src_dir := fs_factory.fs_dir.get(src_dir_id)!
|
||||
assert src_dir.name == 'src'
|
||||
|
||||
docs_dir := fs_factory.fs_dir.get(docs_dir_id)!
|
||||
assert docs_dir.name == 'docs'
|
||||
|
||||
tests_dir := fs_factory.fs_dir.get(tests_dir_id)!
|
||||
assert tests_dir.name == 'tests'
|
||||
|
||||
println('✓ Directory operations test passed!')
|
||||
}
|
||||
|
||||
fn test_blob_operations() ! {
|
||||
// Initialize HeroFS factory
|
||||
mut fs_factory := new()!
|
||||
|
||||
// Test blob creation and hash-based retrieval
|
||||
test_data := 'Test blob content'.bytes()
|
||||
mut test_blob := fs_factory.fs_blob.new(data: test_data)!
|
||||
test_blob = fs_factory.fs_blob.set(test_blob)!
|
||||
|
||||
// Test hash-based retrieval
|
||||
retrieved_blob := fs_factory.fs_blob.get_by_hash(test_blob.hash)!
|
||||
assert retrieved_blob.data == test_data
|
||||
|
||||
// Test blob existence by hash
|
||||
exists := fs_factory.fs_blob.exists_by_hash(test_blob.hash)!
|
||||
assert exists == true
|
||||
|
||||
// Test blob integrity verification
|
||||
assert test_blob.verify_integrity() == true
|
||||
|
||||
println('✓ Blob operations test passed!')
|
||||
}
|
||||
Reference in New Issue
Block a user