fix vfs_db and add metadata_db
This commit is contained in:
@@ -1,53 +1,10 @@
|
||||
module vfs_db
|
||||
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
// Factory method for creating a new DatabaseVFS instance
|
||||
@[params]
|
||||
pub struct VFSParams {
|
||||
pub:
|
||||
data_dir string // Directory to store DatabaseVFS data
|
||||
metadata_dir string // Directory to store metadata (defaults to data_dir if not specified)
|
||||
incremental_mode bool // Whether to enable incremental mode
|
||||
}
|
||||
|
||||
// Factory method for creating a new DatabaseVFS instance
|
||||
pub fn new(mut database Database, params VFSParams) !&DatabaseVFS {
|
||||
pathlib.get_dir(path: params.data_dir, create: true) or {
|
||||
return error('Failed to create data directory: ${err}')
|
||||
}
|
||||
|
||||
// Use the same database for both data and metadata if only one is provided
|
||||
pub fn new(mut data_db Database, mut metadata_db Database) !&DatabaseVFS {
|
||||
mut fs := &DatabaseVFS{
|
||||
root_id: 1
|
||||
block_size: 1024 * 4
|
||||
data_dir: params.data_dir
|
||||
metadata_dir: if params.metadata_dir.len > 0 { params.metadata_dir } else{ params.data_dir}
|
||||
db_data: database
|
||||
db_metadata: database
|
||||
}
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
// Factory method for creating a new DatabaseVFS instance with separate databases for data and metadata
|
||||
pub fn new_with_separate_dbs(mut data_db Database, mut metadata_db Database, params VFSParams) !&DatabaseVFS {
|
||||
pathlib.get_dir(path: params.data_dir, create: true) or {
|
||||
return error('Failed to create data directory: ${err}')
|
||||
}
|
||||
|
||||
if params.metadata_dir.len > 0 {
|
||||
pathlib.get_dir(path: params.metadata_dir, create: true) or {
|
||||
return error('Failed to create metadata directory: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
mut fs := &DatabaseVFS{
|
||||
root_id: 1
|
||||
block_size: 1024 * 4
|
||||
data_dir: params.data_dir
|
||||
metadata_dir: if params.metadata_dir.len > 0 { params.metadata_dir } else { params.data_dir}
|
||||
db_data: data_db
|
||||
db_metadata: metadata_db
|
||||
}
|
||||
|
||||
35
lib/vfs/vfs_db/factory_test.v
Normal file
35
lib/vfs/vfs_db/factory_test.v
Normal file
@@ -0,0 +1,35 @@
|
||||
module vfs_db
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import rand
|
||||
|
||||
fn test_new() {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_factory_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
defer {
|
||||
os.rmdir_all(test_data_dir) or {}
|
||||
}
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Test the factory function
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
|
||||
// Verify the VFS was created correctly
|
||||
assert vfs.root_id == 1
|
||||
assert vfs.block_size == 1024 * 4
|
||||
assert vfs.db_data == db_data
|
||||
assert vfs.db_metadata == db_metadata
|
||||
assert vfs.last_inserted_id == 0
|
||||
assert vfs.id_table.len == 0
|
||||
}
|
||||
77
lib/vfs/vfs_db/id_table_test.v
Normal file
77
lib/vfs/vfs_db/id_table_test.v
Normal file
@@ -0,0 +1,77 @@
|
||||
module vfs_db
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import rand
|
||||
|
||||
fn setup_vfs() !&DatabaseVFS {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_id_table_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Create VFS with separate databases for data and metadata
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
return vfs
|
||||
}
|
||||
|
||||
fn test_set_get_database_id() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Test setting and getting database IDs
|
||||
vfs_id := u32(1)
|
||||
db_id := u32(42)
|
||||
|
||||
// Set the database ID
|
||||
vfs.set_database_id(vfs_id, db_id)!
|
||||
|
||||
// Get the database ID and verify it matches
|
||||
retrieved_id := vfs.get_database_id(vfs_id)!
|
||||
assert retrieved_id == db_id
|
||||
}
|
||||
|
||||
fn test_get_nonexistent_id() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Try to get a database ID that doesn't exist
|
||||
if _ := vfs.get_database_id(999) {
|
||||
assert false, 'Expected error when getting non-existent ID'
|
||||
} else {
|
||||
assert err.msg() == 'VFS ID 999 not found.'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_multiple_ids() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Set multiple IDs
|
||||
vfs.set_database_id(1, 101)!
|
||||
vfs.set_database_id(2, 102)!
|
||||
vfs.set_database_id(3, 103)!
|
||||
|
||||
// Verify all IDs can be retrieved correctly
|
||||
assert vfs.get_database_id(1)! == 101
|
||||
assert vfs.get_database_id(2)! == 102
|
||||
assert vfs.get_database_id(3)! == 103
|
||||
}
|
||||
|
||||
fn test_update_id() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Set an ID
|
||||
vfs.set_database_id(1, 100)!
|
||||
assert vfs.get_database_id(1)! == 100
|
||||
|
||||
// Update the ID
|
||||
vfs.set_database_id(1, 200)!
|
||||
assert vfs.get_database_id(1)! == 200
|
||||
}
|
||||
135
lib/vfs/vfs_db/metadata_test.v
Normal file
135
lib/vfs/vfs_db/metadata_test.v
Normal file
@@ -0,0 +1,135 @@
|
||||
module vfs_db
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import rand
|
||||
|
||||
fn setup_vfs() !&DatabaseVFS {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_metadata_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Create VFS with separate databases for data and metadata
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
return vfs
|
||||
}
|
||||
|
||||
fn test_new_metadata_file() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Test creating file metadata
|
||||
metadata := vfs.new_metadata(
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 1024
|
||||
)
|
||||
|
||||
// Verify the metadata
|
||||
assert metadata.name == 'test_file.txt'
|
||||
assert metadata.file_type == .file
|
||||
assert metadata.size == 1024
|
||||
assert metadata.mode == 0o644 // Default mode
|
||||
assert metadata.owner == 'user' // Default owner
|
||||
assert metadata.group == 'user' // Default group
|
||||
assert metadata.id == 1 // First ID
|
||||
}
|
||||
|
||||
fn test_new_metadata_directory() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Test creating directory metadata
|
||||
metadata := vfs.new_metadata(
|
||||
name: 'test_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
)
|
||||
|
||||
// Verify the metadata
|
||||
assert metadata.name == 'test_dir'
|
||||
assert metadata.file_type == .directory
|
||||
assert metadata.size == 0
|
||||
assert metadata.mode == 0o644 // Default mode
|
||||
assert metadata.owner == 'user' // Default owner
|
||||
assert metadata.group == 'user' // Default group
|
||||
assert metadata.id == 1 // First ID
|
||||
}
|
||||
|
||||
fn test_new_metadata_symlink() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Test creating symlink metadata
|
||||
metadata := vfs.new_metadata(
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
)
|
||||
|
||||
// Verify the metadata
|
||||
assert metadata.name == 'test_link'
|
||||
assert metadata.file_type == .symlink
|
||||
assert metadata.size == 0
|
||||
assert metadata.mode == 0o644 // Default mode
|
||||
assert metadata.owner == 'user' // Default owner
|
||||
assert metadata.group == 'user' // Default group
|
||||
assert metadata.id == 1 // First ID
|
||||
}
|
||||
|
||||
fn test_new_metadata_custom_permissions() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Test creating metadata with custom permissions
|
||||
metadata := vfs.new_metadata(
|
||||
name: 'custom_file.txt'
|
||||
file_type: .file
|
||||
size: 2048
|
||||
mode: 0o755
|
||||
owner: 'admin'
|
||||
group: 'staff'
|
||||
)
|
||||
|
||||
// Verify the metadata
|
||||
assert metadata.name == 'custom_file.txt'
|
||||
assert metadata.file_type == .file
|
||||
assert metadata.size == 2048
|
||||
assert metadata.mode == 0o755
|
||||
assert metadata.owner == 'admin'
|
||||
assert metadata.group == 'staff'
|
||||
assert metadata.id == 1 // First ID
|
||||
}
|
||||
|
||||
fn test_new_metadata_sequential_ids() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Create multiple metadata objects and verify IDs are sequential
|
||||
metadata1 := vfs.new_metadata(
|
||||
name: 'file1.txt'
|
||||
file_type: .file
|
||||
size: 100
|
||||
)
|
||||
assert metadata1.id == 1
|
||||
|
||||
metadata2 := vfs.new_metadata(
|
||||
name: 'file2.txt'
|
||||
file_type: .file
|
||||
size: 200
|
||||
)
|
||||
assert metadata2.id == 2
|
||||
|
||||
metadata3 := vfs.new_metadata(
|
||||
name: 'file3.txt'
|
||||
file_type: .file
|
||||
size: 300
|
||||
)
|
||||
assert metadata3.id == 3
|
||||
}
|
||||
136
lib/vfs/vfs_db/model_directory_test.v
Normal file
136
lib/vfs/vfs_db/model_directory_test.v
Normal file
@@ -0,0 +1,136 @@
|
||||
module vfs_db
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
|
||||
fn test_directory_get_metadata() {
|
||||
// Create a directory with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
dir := Directory{
|
||||
metadata: metadata
|
||||
children: []
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test get_metadata
|
||||
retrieved_metadata := dir.get_metadata()
|
||||
assert retrieved_metadata.id == 1
|
||||
assert retrieved_metadata.name == 'test_dir'
|
||||
assert retrieved_metadata.file_type == .directory
|
||||
assert retrieved_metadata.size == 0
|
||||
assert retrieved_metadata.mode == 0o755
|
||||
assert retrieved_metadata.owner == 'user'
|
||||
assert retrieved_metadata.group == 'user'
|
||||
}
|
||||
|
||||
fn test_directory_get_path() {
|
||||
// Create a directory with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
dir := Directory{
|
||||
metadata: metadata
|
||||
children: []
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test get_path
|
||||
path := dir.get_path()
|
||||
assert path == 'test_dir'
|
||||
}
|
||||
|
||||
fn test_directory_is_dir() {
|
||||
// Create a directory with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
dir := Directory{
|
||||
metadata: metadata
|
||||
children: []
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test is_dir
|
||||
assert dir.is_dir() == true
|
||||
assert dir.is_file() == false
|
||||
assert dir.is_symlink() == false
|
||||
}
|
||||
|
||||
fn test_directory_with_children() {
|
||||
// Create a directory with children
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'parent_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
dir := Directory{
|
||||
metadata: metadata
|
||||
children: [u32(2), 3, 4]
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test children
|
||||
assert dir.children.len == 3
|
||||
assert dir.children[0] == 2
|
||||
assert dir.children[1] == 3
|
||||
assert dir.children[2] == 4
|
||||
}
|
||||
|
||||
fn test_directory_with_parent() {
|
||||
// Create a directory with a parent
|
||||
metadata := vfs.Metadata{
|
||||
id: 2
|
||||
name: 'child_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
dir := Directory{
|
||||
metadata: metadata
|
||||
children: []
|
||||
parent_id: 1
|
||||
}
|
||||
|
||||
// Test parent_id
|
||||
assert dir.parent_id == 1
|
||||
}
|
||||
@@ -64,6 +64,7 @@ pub:
|
||||
pub fn (mut fs DatabaseVFS) new_file(file NewFile) !&File {
|
||||
f := File{
|
||||
data: file.data
|
||||
parent_id: file.parent_id
|
||||
metadata: fs.new_metadata(NewMetadata{
|
||||
name: file.name
|
||||
mode: file.mode
|
||||
@@ -87,5 +88,6 @@ pub fn (mut fs DatabaseVFS) copy_file(file File) !&File {
|
||||
mode: file.metadata.mode
|
||||
owner: file.metadata.owner
|
||||
group: file.metadata.group
|
||||
parent_id: file.parent_id
|
||||
)
|
||||
}
|
||||
|
||||
220
lib/vfs/vfs_db/model_file_test.v
Normal file
220
lib/vfs/vfs_db/model_file_test.v
Normal file
@@ -0,0 +1,220 @@
|
||||
module vfs_db
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import rand
|
||||
|
||||
fn setup_vfs() !&DatabaseVFS {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_model_file_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Create VFS with separate databases for data and metadata
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
return vfs
|
||||
}
|
||||
|
||||
fn test_file_get_metadata() {
|
||||
// Create a file with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o644
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
file := File{
|
||||
metadata: metadata
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test get_metadata
|
||||
retrieved_metadata := file.get_metadata()
|
||||
assert retrieved_metadata.id == 1
|
||||
assert retrieved_metadata.name == 'test_file.txt'
|
||||
assert retrieved_metadata.file_type == .file
|
||||
assert retrieved_metadata.size == 13
|
||||
assert retrieved_metadata.mode == 0o644
|
||||
assert retrieved_metadata.owner == 'user'
|
||||
assert retrieved_metadata.group == 'user'
|
||||
}
|
||||
|
||||
fn test_file_get_path() {
|
||||
// Create a file with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o644
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
file := File{
|
||||
metadata: metadata
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test get_path
|
||||
path := file.get_path()
|
||||
assert path == 'test_file.txt'
|
||||
}
|
||||
|
||||
fn test_file_is_file() {
|
||||
// Create a file with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o644
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
file := File{
|
||||
metadata: metadata
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test is_file
|
||||
assert file.is_file() == true
|
||||
assert file.is_dir() == false
|
||||
assert file.is_symlink() == false
|
||||
}
|
||||
|
||||
fn test_file_write_read() {
|
||||
// Create a file with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o644
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
mut file := File{
|
||||
metadata: metadata
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test read
|
||||
content := file.read()
|
||||
assert content == 'Hello, World!'
|
||||
|
||||
// Test write
|
||||
file.write('New content')
|
||||
assert file.data == 'New content'
|
||||
assert file.metadata.size == 11 // 'New content'.len
|
||||
|
||||
// Test read after write
|
||||
new_content := file.read()
|
||||
assert new_content == 'New content'
|
||||
}
|
||||
|
||||
fn test_file_rename() {
|
||||
// Create a file with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o644
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
mut file := File{
|
||||
metadata: metadata
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test rename
|
||||
file.rename('renamed_file.txt')
|
||||
assert file.metadata.name == 'renamed_file.txt'
|
||||
}
|
||||
|
||||
fn test_new_file() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Test creating a new file
|
||||
mut file := vfs.new_file(
|
||||
name: 'test_file.txt'
|
||||
data: 'Hello, World!'
|
||||
)!
|
||||
|
||||
// Verify the file
|
||||
assert file.metadata.name == 'test_file.txt'
|
||||
assert file.metadata.file_type == .file
|
||||
assert file.metadata.size == 13
|
||||
assert file.metadata.mode == 0o644
|
||||
assert file.metadata.owner == 'user'
|
||||
assert file.metadata.group == 'user'
|
||||
assert file.data == 'Hello, World!'
|
||||
}
|
||||
|
||||
fn test_copy_file() ! {
|
||||
mut vfs := setup_vfs()!
|
||||
|
||||
// Create a file to copy
|
||||
original_file := File{
|
||||
metadata: vfs.Metadata{
|
||||
id: 1
|
||||
name: 'original.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o755
|
||||
owner: 'admin'
|
||||
group: 'staff'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test copying the file
|
||||
copied_file := vfs.copy_file(original_file)!
|
||||
|
||||
// Verify the copied file
|
||||
assert copied_file.metadata.name == 'original.txt'
|
||||
assert copied_file.metadata.file_type == .file
|
||||
assert copied_file.metadata.size == 13
|
||||
assert copied_file.metadata.mode == 0o755
|
||||
assert copied_file.metadata.owner == 'admin'
|
||||
assert copied_file.metadata.group == 'staff'
|
||||
assert copied_file.data == 'Hello, World!'
|
||||
assert copied_file.metadata.id != original_file.metadata.id // Should have a new ID
|
||||
}
|
||||
185
lib/vfs/vfs_db/model_fsentry_test.v
Normal file
185
lib/vfs/vfs_db/model_fsentry_test.v
Normal file
@@ -0,0 +1,185 @@
|
||||
module vfs_db
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
|
||||
fn test_fsentry_directory() {
|
||||
// Create a directory entry
|
||||
dir := Directory{
|
||||
metadata: vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
children: []
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Convert to FSEntry
|
||||
entry := FSEntry(dir)
|
||||
|
||||
// Test methods
|
||||
assert entry.get_metadata().id == 1
|
||||
assert entry.get_metadata().name == 'test_dir'
|
||||
assert entry.get_metadata().file_type == .directory
|
||||
assert entry.get_path() == 'test_dir'
|
||||
assert entry.is_dir() == true
|
||||
assert entry.is_file() == false
|
||||
assert entry.is_symlink() == false
|
||||
}
|
||||
|
||||
fn test_fsentry_file() {
|
||||
// Create a file entry
|
||||
file := File{
|
||||
metadata: vfs.Metadata{
|
||||
id: 2
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o644
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Convert to FSEntry
|
||||
entry := FSEntry(file)
|
||||
|
||||
// Test methods
|
||||
assert entry.get_metadata().id == 2
|
||||
assert entry.get_metadata().name == 'test_file.txt'
|
||||
assert entry.get_metadata().file_type == .file
|
||||
assert entry.get_path() == 'test_file.txt'
|
||||
assert entry.is_dir() == false
|
||||
assert entry.is_file() == true
|
||||
assert entry.is_symlink() == false
|
||||
}
|
||||
|
||||
fn test_fsentry_symlink() {
|
||||
// Create a symlink entry
|
||||
symlink := Symlink{
|
||||
metadata: vfs.Metadata{
|
||||
id: 3
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
target: '/path/to/target'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Convert to FSEntry
|
||||
entry := FSEntry(symlink)
|
||||
|
||||
// Test methods
|
||||
assert entry.get_metadata().id == 3
|
||||
assert entry.get_metadata().name == 'test_link'
|
||||
assert entry.get_metadata().file_type == .symlink
|
||||
assert entry.get_path() == 'test_link'
|
||||
assert entry.is_dir() == false
|
||||
assert entry.is_file() == false
|
||||
assert entry.is_symlink() == true
|
||||
}
|
||||
|
||||
fn test_fsentry_match() {
|
||||
// Create entries of different types
|
||||
dir := Directory{
|
||||
metadata: vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
children: []
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
file := File{
|
||||
metadata: vfs.Metadata{
|
||||
id: 2
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
size: 13
|
||||
mode: 0o644
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
data: 'Hello, World!'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
symlink := Symlink{
|
||||
metadata: vfs.Metadata{
|
||||
id: 3
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
target: '/path/to/target'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test match with directory
|
||||
dir_entry := FSEntry(dir)
|
||||
match dir_entry {
|
||||
Directory {
|
||||
assert it.metadata.id == 1
|
||||
assert it.metadata.name == 'test_dir'
|
||||
}
|
||||
File, Symlink {
|
||||
assert false, 'Expected Directory type'
|
||||
}
|
||||
}
|
||||
|
||||
// Test match with file
|
||||
file_entry := FSEntry(file)
|
||||
match file_entry {
|
||||
File {
|
||||
assert it.metadata.id == 2
|
||||
assert it.metadata.name == 'test_file.txt'
|
||||
assert it.data == 'Hello, World!'
|
||||
}
|
||||
Directory, Symlink {
|
||||
assert false, 'Expected File type'
|
||||
}
|
||||
}
|
||||
|
||||
// Test match with symlink
|
||||
symlink_entry := FSEntry(symlink)
|
||||
match symlink_entry {
|
||||
Symlink {
|
||||
assert it.metadata.id == 3
|
||||
assert it.metadata.name == 'test_link'
|
||||
assert it.target == '/path/to/target'
|
||||
}
|
||||
Directory, File {
|
||||
assert false, 'Expected Symlink type'
|
||||
}
|
||||
}
|
||||
}
|
||||
159
lib/vfs/vfs_db/model_symlink_test.v
Normal file
159
lib/vfs/vfs_db/model_symlink_test.v
Normal file
@@ -0,0 +1,159 @@
|
||||
module vfs_db
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
|
||||
fn test_symlink_get_metadata() {
|
||||
// Create a symlink with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
symlink := Symlink{
|
||||
metadata: metadata
|
||||
target: '/path/to/target'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test get_metadata
|
||||
retrieved_metadata := symlink.get_metadata()
|
||||
assert retrieved_metadata.id == 1
|
||||
assert retrieved_metadata.name == 'test_link'
|
||||
assert retrieved_metadata.file_type == .symlink
|
||||
assert retrieved_metadata.size == 0
|
||||
assert retrieved_metadata.mode == 0o777
|
||||
assert retrieved_metadata.owner == 'user'
|
||||
assert retrieved_metadata.group == 'user'
|
||||
}
|
||||
|
||||
fn test_symlink_get_path() {
|
||||
// Create a symlink with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
symlink := Symlink{
|
||||
metadata: metadata
|
||||
target: '/path/to/target'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test get_path
|
||||
path := symlink.get_path()
|
||||
assert path == 'test_link'
|
||||
}
|
||||
|
||||
fn test_symlink_is_symlink() {
|
||||
// Create a symlink with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
symlink := Symlink{
|
||||
metadata: metadata
|
||||
target: '/path/to/target'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test is_symlink
|
||||
assert symlink.is_symlink() == true
|
||||
assert symlink.is_dir() == false
|
||||
assert symlink.is_file() == false
|
||||
}
|
||||
|
||||
fn test_symlink_update_target() ! {
|
||||
// Create a symlink with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
mut symlink := Symlink{
|
||||
metadata: metadata
|
||||
target: '/path/to/target'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test update_target
|
||||
symlink.update_target('/new/target/path')!
|
||||
assert symlink.target == '/new/target/path'
|
||||
}
|
||||
|
||||
fn test_symlink_get_target() ! {
|
||||
// Create a symlink with metadata
|
||||
metadata := vfs.Metadata{
|
||||
id: 1
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
mut symlink := Symlink{
|
||||
metadata: metadata
|
||||
target: '/path/to/target'
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test get_target
|
||||
target := symlink.get_target()!
|
||||
assert target == '/path/to/target'
|
||||
}
|
||||
|
||||
fn test_symlink_with_parent() {
|
||||
// Create a symlink with a parent
|
||||
metadata := vfs.Metadata{
|
||||
id: 2
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
|
||||
symlink := Symlink{
|
||||
metadata: metadata
|
||||
target: '/path/to/target'
|
||||
parent_id: 1
|
||||
}
|
||||
|
||||
// Test parent_id
|
||||
assert symlink.parent_id == 1
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
module vfs_db
|
||||
|
||||
pub fn (mut fs DatabaseVFS) print() ! {
|
||||
println(fs.directory_printall(fs.root_get_as_dir()!, '')!)
|
||||
}
|
||||
|
||||
// str returns a formatted string of directory contents (non-recursive)
|
||||
pub fn (mut fs DatabaseVFS) directory_print(dir Directory) string {
|
||||
mut result := '${dir.metadata.name}/\n'
|
||||
|
||||
190
lib/vfs/vfs_db/print_test.v
Normal file
190
lib/vfs/vfs_db/print_test.v
Normal file
@@ -0,0 +1,190 @@
|
||||
module vfs_db
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import rand
|
||||
|
||||
fn setup_vfs() !(&DatabaseVFS, string) {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_print_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Create VFS with separate databases for data and metadata
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
return vfs, test_data_dir
|
||||
}
|
||||
|
||||
fn teardown_vfs(data_dir string) {
|
||||
os.rmdir_all(data_dir) or {}
|
||||
}
|
||||
|
||||
fn test_directory_print_empty() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create an empty directory
|
||||
mut dir := vfs.new_directory(
|
||||
name: 'test_dir'
|
||||
)!
|
||||
|
||||
// Test printing the empty directory
|
||||
output := vfs.directory_print(dir)
|
||||
|
||||
// Verify the output
|
||||
assert output == 'test_dir/\n'
|
||||
}
|
||||
|
||||
fn test_directory_print_with_contents() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a directory with various contents
|
||||
mut dir := vfs.new_directory(
|
||||
name: 'test_dir'
|
||||
)!
|
||||
|
||||
// Add a subdirectory
|
||||
mut subdir := vfs.directory_mkdir(mut dir, 'subdir')!
|
||||
|
||||
// Add a file
|
||||
mut file := vfs.directory_touch(dir, 'test_file.txt')!
|
||||
|
||||
// Add a symlink
|
||||
mut symlink := Symlink{
|
||||
metadata: vfs.Metadata{
|
||||
id: vfs.get_next_id()
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
target: '/path/to/target'
|
||||
parent_id: dir.metadata.id
|
||||
}
|
||||
vfs.directory_add_symlink(mut dir, mut symlink)!
|
||||
|
||||
// Test printing the directory
|
||||
output := vfs.directory_print(dir)
|
||||
|
||||
// Verify the output contains all entries
|
||||
assert output.contains('test_dir/')
|
||||
assert output.contains('📁 subdir/')
|
||||
assert output.contains('📄 test_file.txt')
|
||||
assert output.contains('🔗 test_link -> /path/to/target')
|
||||
}
|
||||
|
||||
fn test_directory_printall_simple() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a simple directory structure
|
||||
mut dir := vfs.new_directory(
|
||||
name: 'root_dir'
|
||||
)!
|
||||
|
||||
// Add a file
|
||||
mut file := vfs.directory_touch(dir, 'test_file.txt')!
|
||||
|
||||
// Test printing the directory recursively
|
||||
output := vfs.directory_printall(dir, '')!
|
||||
|
||||
// Verify the output
|
||||
assert output.contains('📁 root_dir/')
|
||||
assert output.contains('📄 test_file.txt')
|
||||
}
|
||||
|
||||
fn test_directory_printall_nested() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a nested directory structure
|
||||
mut root := vfs.new_directory(
|
||||
name: 'root'
|
||||
)!
|
||||
|
||||
// Add a subdirectory
|
||||
mut subdir1 := vfs.directory_mkdir(mut root, 'subdir1')!
|
||||
|
||||
// Add a file to the root
|
||||
mut root_file := vfs.directory_touch(root, 'root_file.txt')!
|
||||
|
||||
// Add a file to the subdirectory
|
||||
mut subdir_file := vfs.directory_touch(subdir1, 'subdir_file.txt')!
|
||||
|
||||
// Add a nested subdirectory
|
||||
mut subdir2 := vfs.directory_mkdir(mut subdir1, 'subdir2')!
|
||||
|
||||
// Add a file to the nested subdirectory
|
||||
mut nested_file := vfs.directory_touch(subdir2, 'nested_file.txt')!
|
||||
|
||||
// Add a symlink to the nested subdirectory
|
||||
mut symlink := Symlink{
|
||||
metadata: vfs.Metadata{
|
||||
id: vfs.get_next_id()
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
target: '/path/to/target'
|
||||
parent_id: subdir2.metadata.id
|
||||
}
|
||||
vfs.directory_add_symlink(mut subdir2, mut symlink)!
|
||||
|
||||
// Test printing the directory recursively
|
||||
output := vfs.directory_printall(root, '')!
|
||||
|
||||
// Verify the output contains all entries with proper indentation
|
||||
assert output.contains('📁 root/')
|
||||
assert output.contains(' 📄 root_file.txt')
|
||||
assert output.contains(' 📁 subdir1/')
|
||||
assert output.contains(' 📄 subdir_file.txt')
|
||||
assert output.contains(' 📁 subdir2/')
|
||||
assert output.contains(' 📄 nested_file.txt')
|
||||
assert output.contains(' 🔗 test_link -> /path/to/target')
|
||||
}
|
||||
|
||||
fn test_directory_printall_empty() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create an empty directory
|
||||
mut dir := vfs.new_directory(
|
||||
name: 'empty_dir'
|
||||
)!
|
||||
|
||||
// Test printing the empty directory recursively
|
||||
output := vfs.directory_printall(dir, '')!
|
||||
|
||||
// Verify the output
|
||||
assert output == '📁 empty_dir/\n'
|
||||
}
|
||||
@@ -11,8 +11,6 @@ pub struct DatabaseVFS {
|
||||
pub mut:
|
||||
root_id u32 // ID of root directory
|
||||
block_size u32 // Size of data blocks in bytes
|
||||
data_dir string // Directory to store DatabaseVFS data
|
||||
metadata_dir string // Directory where we store the metadata
|
||||
db_data &Database @[str: skip] // Database instance for file data storage
|
||||
db_metadata &Database @[str: skip] // Database instance for metadata storage
|
||||
last_inserted_id u32
|
||||
|
||||
@@ -1,79 +1,7 @@
|
||||
module vfs_db
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
|
||||
// // write creates a new file or writes to an existing file
|
||||
// pub fn (mut fs DatabaseVFS) directory_write(dir_ Directory, name string, content string) !&File {
|
||||
// mut dir := dir_
|
||||
// mut file := &File{}
|
||||
// mut is_new := true
|
||||
|
||||
// // Check if file exists
|
||||
// for child_id in dir.children {
|
||||
// mut entry := fs.load_entry(child_id)!
|
||||
// if entry.metadata.name == name {
|
||||
// if mut entry is File {
|
||||
// mut d := entry
|
||||
// file = &d
|
||||
// is_new = false
|
||||
// break
|
||||
// } else {
|
||||
// return error('${name} exists but is not a file')
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if is_new {
|
||||
// // Create new file
|
||||
// current_time := time.now().unix()
|
||||
// file = &File{
|
||||
// metadata: vfs.Metadata{
|
||||
// id: fs.get_next_id()
|
||||
// name: name
|
||||
// file_type: .file
|
||||
// size: u64(content.len)
|
||||
// created_at: current_time
|
||||
// modified_at: current_time
|
||||
// accessed_at: current_time
|
||||
// mode: 0o644
|
||||
// owner: 'user'
|
||||
// group: 'user'
|
||||
// }
|
||||
// data: content
|
||||
// parent_id: dir.metadata.id
|
||||
// }
|
||||
|
||||
// // Save new file to DB
|
||||
// fs.save_entry(file)!
|
||||
|
||||
// // Update children list
|
||||
// dir.children << file.metadata.id
|
||||
// fs.save_entry(dir)!
|
||||
// } else {
|
||||
// // Update existing file
|
||||
// file.write(content)
|
||||
// fs.save_entry(file)!
|
||||
// }
|
||||
|
||||
// return file
|
||||
// }
|
||||
|
||||
// // read reads content from a file
|
||||
// pub fn (mut dir Directory) directory_read(name string) !string {
|
||||
// // Find file
|
||||
// for child_id in dir.children {
|
||||
// if mut entry := dir.myvfs.load_entry(child_id) {
|
||||
// if entry.metadata.name == name {
|
||||
// if mut entry is File {
|
||||
// return entry.read()
|
||||
// } else {
|
||||
// return error('${name} is not a file')
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return error('File ${name} not found')
|
||||
// }
|
||||
import freeflowuniverse.herolib.vfs { Metadata }
|
||||
import time
|
||||
|
||||
// mkdir creates a new directory with default permissions
|
||||
pub fn (mut fs DatabaseVFS) directory_mkdir(mut dir Directory, name string) !&Directory {
|
||||
@@ -121,20 +49,42 @@ pub fn (mut fs DatabaseVFS) new_directory(dir NewDirectory) !&Directory {
|
||||
return &d
|
||||
}
|
||||
|
||||
// mkdir creates a new directory with default permissions
|
||||
// copy_directory creates a new directory with the same metadata as the source
|
||||
pub fn (mut fs DatabaseVFS) copy_directory(dir Directory) !&Directory {
|
||||
return fs.new_directory(
|
||||
name: dir.metadata.name
|
||||
mode: dir.metadata.mode
|
||||
owner: dir.metadata.owner
|
||||
group: dir.metadata.group
|
||||
)
|
||||
// Ensure we get a new ID that's different from the original
|
||||
mut new_id := fs.get_next_id()
|
||||
|
||||
// Make sure the new ID is different from the original
|
||||
if new_id == dir.metadata.id {
|
||||
new_id = fs.get_next_id() // Get another ID if they happen to be the same
|
||||
}
|
||||
|
||||
new_dir := Directory{
|
||||
metadata: Metadata{
|
||||
...dir.metadata
|
||||
id: new_id
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
children: []u32{}
|
||||
parent_id: dir.parent_id
|
||||
}
|
||||
fs.save_entry(new_dir)!
|
||||
return &new_dir
|
||||
}
|
||||
|
||||
// touch creates a new empty file with default permissions
|
||||
pub fn (mut fs DatabaseVFS) directory_touch(dir_ Directory, name string) !&File {
|
||||
mut dir := dir_
|
||||
|
||||
// First, make sure we're working with the latest version of the directory
|
||||
if updated_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
dir = updated_dir
|
||||
}
|
||||
}
|
||||
|
||||
// Check if file already exists
|
||||
for child_id in dir.children {
|
||||
if entry := fs.load_entry(child_id) {
|
||||
@@ -143,14 +93,30 @@ pub fn (mut fs DatabaseVFS) directory_touch(dir_ Directory, name string) !&File
|
||||
}
|
||||
}
|
||||
}
|
||||
new_file := fs.new_file(
|
||||
|
||||
// Create new file with correct parent_id
|
||||
mut new_file := fs.new_file(
|
||||
parent_id: dir.metadata.id
|
||||
name: name
|
||||
name: name
|
||||
)!
|
||||
|
||||
// Ensure parent_id is set correctly
|
||||
if new_file.parent_id != dir.metadata.id {
|
||||
new_file.parent_id = dir.metadata.id
|
||||
fs.save_entry(new_file)!
|
||||
}
|
||||
|
||||
// Update children list
|
||||
dir.children << new_file.metadata.id
|
||||
fs.save_entry(dir)!
|
||||
|
||||
// Reload the directory to ensure we have the latest version
|
||||
if updated_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
dir = updated_dir
|
||||
}
|
||||
}
|
||||
|
||||
return new_file
|
||||
}
|
||||
|
||||
@@ -160,6 +126,13 @@ pub fn (mut fs DatabaseVFS) directory_rm(mut dir Directory, name string) ! {
|
||||
mut found_id := u32(0)
|
||||
mut found_idx := 0
|
||||
|
||||
// First, make sure we're working with the latest version of the directory
|
||||
if updated_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
dir = updated_dir
|
||||
}
|
||||
}
|
||||
|
||||
for i, child_id in dir.children {
|
||||
if entry := fs.load_entry(child_id) {
|
||||
if entry.metadata.name == name {
|
||||
@@ -191,9 +164,16 @@ pub fn (mut fs DatabaseVFS) directory_rm(mut dir Directory, name string) ! {
|
||||
|
||||
fs.db_metadata.delete(file.metadata.id) or { return error('Failed to delete entry: ${err}') }
|
||||
|
||||
// Update children list
|
||||
// Update children list - make sure we don't remove the wrong child
|
||||
dir.children.delete(found_idx)
|
||||
fs.save_entry(dir)!
|
||||
|
||||
// Reload the directory to ensure we have the latest version
|
||||
if updated_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
dir = updated_dir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MoveDirArgs {
|
||||
@@ -207,6 +187,14 @@ pub fn (mut fs DatabaseVFS) directory_move(dir_ Directory, args_ MoveDirArgs) !&
|
||||
mut dir := dir_
|
||||
mut args := args_
|
||||
mut found := false
|
||||
mut child_id_to_remove := u32(0)
|
||||
|
||||
// First, make sure we're working with the latest version of the directory
|
||||
if updated_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
dir = updated_dir
|
||||
}
|
||||
}
|
||||
|
||||
for child_id in dir.children {
|
||||
if mut entry := fs.load_entry(child_id) {
|
||||
@@ -220,15 +208,12 @@ pub fn (mut fs DatabaseVFS) directory_move(dir_ Directory, args_ MoveDirArgs) !&
|
||||
}
|
||||
|
||||
found = true
|
||||
child_id_to_remove = child_id
|
||||
mut entry_ := entry as Directory
|
||||
entry_.metadata.name = args.dst_entry_name
|
||||
entry_.metadata.modified()
|
||||
entry_.metadata.modified_at = time.now().unix()
|
||||
entry_.parent_id = args.dst_parent_dir.metadata.id
|
||||
|
||||
// Remove from old parent's children
|
||||
dir.children = dir.children.filter(it != child_id)
|
||||
fs.save_entry(dir)!
|
||||
|
||||
// Recursively update all child paths in moved directory
|
||||
fs.move_children_recursive(mut entry_)!
|
||||
|
||||
@@ -237,9 +222,20 @@ pub fn (mut fs DatabaseVFS) directory_move(dir_ Directory, args_ MoveDirArgs) !&
|
||||
args.dst_parent_dir.children << entry_.metadata.id
|
||||
}
|
||||
|
||||
// Remove from old parent's children before saving the entry
|
||||
dir.children = dir.children.filter(it != child_id_to_remove)
|
||||
fs.save_entry(dir)!
|
||||
|
||||
fs.save_entry(entry_)!
|
||||
fs.save_entry(args.dst_parent_dir)!
|
||||
|
||||
// Reload the source directory to ensure we have the latest version
|
||||
if updated_src_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_src_dir is Directory {
|
||||
dir = updated_src_dir
|
||||
}
|
||||
}
|
||||
|
||||
return &entry_
|
||||
}
|
||||
}
|
||||
@@ -280,6 +276,13 @@ pub fn (mut fs DatabaseVFS) directory_copy(mut dir Directory, args_ CopyDirArgs)
|
||||
mut found := false
|
||||
mut args := args_
|
||||
|
||||
// First, make sure we're working with the latest version of the directory
|
||||
if updated_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
dir = updated_dir
|
||||
}
|
||||
}
|
||||
|
||||
for child_id in dir.children {
|
||||
if mut entry := fs.load_entry(child_id) {
|
||||
if entry.metadata.name == args.src_entry_name {
|
||||
@@ -294,14 +297,21 @@ pub fn (mut fs DatabaseVFS) directory_copy(mut dir Directory, args_ CopyDirArgs)
|
||||
found = true
|
||||
mut src_dir := entry as Directory
|
||||
|
||||
// Make sure we have the latest version of the source directory
|
||||
if updated_src_dir := fs.load_entry(src_dir.metadata.id) {
|
||||
if updated_src_dir is Directory {
|
||||
src_dir = updated_src_dir
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new directory with copied metadata
|
||||
mut new_dir := fs.copy_directory(Directory{
|
||||
...src_dir
|
||||
metadata: vfs.Metadata{
|
||||
metadata: Metadata{
|
||||
...src_dir.metadata
|
||||
name: args.dst_entry_name
|
||||
}
|
||||
parent_id: args.dst_parent_dir.metadata.id
|
||||
children: []u32{}
|
||||
})!
|
||||
|
||||
// Recursively copy children
|
||||
@@ -311,6 +321,24 @@ pub fn (mut fs DatabaseVFS) directory_copy(mut dir Directory, args_ CopyDirArgs)
|
||||
fs.save_entry(new_dir)!
|
||||
args.dst_parent_dir.children << new_dir.metadata.id
|
||||
fs.save_entry(args.dst_parent_dir)!
|
||||
|
||||
// Make sure to save the source directory too
|
||||
fs.save_entry(src_dir)!
|
||||
|
||||
// Reload the source directory to ensure we have the latest version
|
||||
if updated_src_dir := fs.load_entry(src_dir.metadata.id) {
|
||||
if updated_src_dir is Directory {
|
||||
src_dir = updated_src_dir
|
||||
}
|
||||
}
|
||||
|
||||
// Reload the parent directory to ensure we have the latest version
|
||||
if updated_parent_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_parent_dir is Directory {
|
||||
dir = updated_parent_dir
|
||||
}
|
||||
}
|
||||
|
||||
return new_dir
|
||||
}
|
||||
}
|
||||
@@ -330,8 +358,11 @@ fn (mut fs DatabaseVFS) copy_children_recursive(mut src_dir Directory, mut dst_d
|
||||
Directory {
|
||||
mut entry_ := entry as Directory
|
||||
mut new_subdir := fs.copy_directory(Directory{
|
||||
...entry_
|
||||
children: []u32{}
|
||||
metadata: Metadata{
|
||||
...entry_.metadata
|
||||
id: fs.get_next_id()
|
||||
}
|
||||
children: []u32{}
|
||||
parent_id: dst_dir.metadata.id
|
||||
})!
|
||||
|
||||
@@ -342,7 +373,11 @@ fn (mut fs DatabaseVFS) copy_children_recursive(mut src_dir Directory, mut dst_d
|
||||
File {
|
||||
mut entry_ := entry as File
|
||||
mut new_file := fs.copy_file(File{
|
||||
...entry_
|
||||
metadata: Metadata{
|
||||
...entry_.metadata
|
||||
id: fs.get_next_id()
|
||||
}
|
||||
data: entry_.data
|
||||
parent_id: dst_dir.metadata.id
|
||||
})!
|
||||
dst_dir.children << new_file.metadata.id
|
||||
@@ -350,15 +385,11 @@ fn (mut fs DatabaseVFS) copy_children_recursive(mut src_dir Directory, mut dst_d
|
||||
Symlink {
|
||||
mut entry_ := entry as Symlink
|
||||
mut new_symlink := Symlink{
|
||||
metadata: fs.new_metadata(
|
||||
name: entry_.metadata.name
|
||||
file_type: .symlink
|
||||
size: u64(0)
|
||||
mode: entry_.metadata.mode
|
||||
owner: entry_.metadata.owner
|
||||
group: entry_.metadata.group
|
||||
)
|
||||
target: entry_.target
|
||||
metadata: Metadata{
|
||||
...entry_.metadata
|
||||
id: fs.get_next_id()
|
||||
}
|
||||
target: entry_.target
|
||||
parent_id: dst_dir.metadata.id
|
||||
}
|
||||
fs.save_entry(new_symlink)!
|
||||
@@ -373,17 +404,23 @@ fn (mut fs DatabaseVFS) copy_children_recursive(mut src_dir Directory, mut dst_d
|
||||
|
||||
pub fn (mut fs DatabaseVFS) directory_rename(dir Directory, src_name string, dst_name string) !&Directory {
|
||||
mut found := false
|
||||
mut dir_ := dir
|
||||
|
||||
for child_id in dir.children {
|
||||
if mut entry := fs.load_entry(child_id) {
|
||||
if entry.metadata.name == src_name {
|
||||
if entry is File {
|
||||
return error('${src_name} is a file')
|
||||
}
|
||||
if entry is Symlink {
|
||||
return error('${src_name} is a symlink')
|
||||
}
|
||||
|
||||
found = true
|
||||
entry.metadata.name = dst_name
|
||||
entry.metadata.modified()
|
||||
fs.save_entry(entry)!
|
||||
get_dir := entry as Directory
|
||||
return &get_dir
|
||||
mut dir_entry := entry as Directory
|
||||
dir_entry.metadata.name = dst_name
|
||||
dir_entry.metadata.modified_at = time.now().unix()
|
||||
fs.save_entry(dir_entry)!
|
||||
return &dir_entry
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -392,39 +429,32 @@ pub fn (mut fs DatabaseVFS) directory_rename(dir Directory, src_name string, dst
|
||||
return error('${src_name} not found')
|
||||
}
|
||||
|
||||
return &dir_
|
||||
return error('Unexpected rename failure')
|
||||
}
|
||||
|
||||
// get_children returns all immediate children as FSEntry objects
|
||||
pub fn (mut fs DatabaseVFS) directory_children(mut dir Directory, recursive bool) ![]FSEntry {
|
||||
mut entries := []FSEntry{}
|
||||
|
||||
// Make sure we're working with the latest version of the directory
|
||||
if updated_dir := fs.load_entry(dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
dir = updated_dir
|
||||
}
|
||||
}
|
||||
|
||||
for child_id in dir.children {
|
||||
entry := fs.load_entry(child_id)!
|
||||
entries << entry
|
||||
if recursive {
|
||||
if entry is Directory {
|
||||
mut d := entry
|
||||
if entry := fs.load_entry(child_id) {
|
||||
entries << entry
|
||||
if recursive && entry is Directory {
|
||||
mut d := entry as Directory
|
||||
entries << fs.directory_children(mut d, true)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
return entries.clone()
|
||||
}
|
||||
|
||||
// pub fn (mut dir Directory) delete() ! {
|
||||
// // Delete all children first
|
||||
// for child_id in dir.children {
|
||||
// dir.myvfs.delete_entry(child_id) or {}
|
||||
// }
|
||||
|
||||
// // Clear children list
|
||||
// dir.children.clear()
|
||||
|
||||
// // Save the updated directory
|
||||
// dir.myvfs.save_entry(dir) or { return error('Failed to save directory: ${err}') }
|
||||
// }
|
||||
|
||||
// add_symlink adds an existing symlink to this directory
|
||||
pub fn (mut fs DatabaseVFS) directory_add_symlink(mut dir Directory, mut symlink Symlink) ! {
|
||||
// Check if name already exists
|
||||
|
||||
520
lib/vfs/vfs_db/vfs_directory_test.v
Normal file
520
lib/vfs/vfs_db/vfs_directory_test.v
Normal file
@@ -0,0 +1,520 @@
|
||||
module vfs_db
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.vfs { Metadata }
|
||||
import rand
|
||||
|
||||
fn setup_vfs() !(&DatabaseVFS, string) {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_directory_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Create VFS with separate databases for data and metadata
|
||||
mut fs := new(mut db_data, mut db_metadata)!
|
||||
return fs, test_data_dir
|
||||
}
|
||||
|
||||
fn teardown_vfs(data_dir string) {
|
||||
os.rmdir_all(data_dir) or {}
|
||||
}
|
||||
|
||||
fn test_new_directory() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test creating a new directory
|
||||
mut dir := fs.new_directory(
|
||||
name: 'test_dir'
|
||||
)!
|
||||
|
||||
// Verify the directory
|
||||
assert dir.metadata.name == 'test_dir'
|
||||
assert dir.metadata.file_type == .directory
|
||||
assert dir.metadata.size == 0
|
||||
assert dir.metadata.mode == 0o755 // Default mode for directories
|
||||
assert dir.metadata.owner == 'user'
|
||||
assert dir.metadata.group == 'user'
|
||||
assert dir.children.len == 0
|
||||
}
|
||||
|
||||
fn test_new_directory_with_custom_permissions() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test creating a directory with custom permissions
|
||||
mut dir := fs.new_directory(
|
||||
name: 'custom_dir'
|
||||
mode: 0o700
|
||||
owner: 'admin'
|
||||
group: 'staff'
|
||||
)!
|
||||
|
||||
// Verify the directory
|
||||
assert dir.metadata.name == 'custom_dir'
|
||||
assert dir.metadata.file_type == .directory
|
||||
assert dir.metadata.size == 0
|
||||
assert dir.metadata.mode == 0o700
|
||||
assert dir.metadata.owner == 'admin'
|
||||
assert dir.metadata.group == 'staff'
|
||||
}
|
||||
|
||||
fn test_copy_directory() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a directory to copy
|
||||
original_dir := Directory{
|
||||
metadata: Metadata{
|
||||
id: 1
|
||||
name: 'original_dir'
|
||||
file_type: .directory
|
||||
size: 0
|
||||
mode: 0o755
|
||||
owner: 'admin'
|
||||
group: 'staff'
|
||||
created_at: 0
|
||||
modified_at: 0
|
||||
accessed_at: 0
|
||||
}
|
||||
children: []
|
||||
parent_id: 0
|
||||
}
|
||||
|
||||
// Test copying the directory
|
||||
copied_dir := fs.copy_directory(original_dir)!
|
||||
|
||||
// Verify the copied directory
|
||||
assert copied_dir.metadata.name == 'original_dir'
|
||||
assert copied_dir.metadata.file_type == .directory
|
||||
assert copied_dir.metadata.size == 0
|
||||
assert copied_dir.metadata.mode == 0o755
|
||||
assert copied_dir.metadata.owner == 'admin'
|
||||
assert copied_dir.metadata.group == 'staff'
|
||||
assert copied_dir.children.len == 0
|
||||
assert copied_dir.metadata.id != original_dir.metadata.id // Should have a new ID
|
||||
}
|
||||
|
||||
fn test_directory_mkdir() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a parent directory
|
||||
mut parent_dir := fs.new_directory(
|
||||
name: 'parent_dir'
|
||||
)!
|
||||
|
||||
// Test creating a subdirectory
|
||||
mut subdir := fs.directory_mkdir(mut parent_dir, 'subdir')!
|
||||
|
||||
// Verify the subdirectory
|
||||
assert subdir.metadata.name == 'subdir'
|
||||
assert subdir.metadata.file_type == .directory
|
||||
assert subdir.parent_id == parent_dir.metadata.id
|
||||
|
||||
// Verify the parent directory's children
|
||||
assert parent_dir.children.len == 1
|
||||
assert parent_dir.children[0] == subdir.metadata.id
|
||||
|
||||
// Test creating a duplicate directory (should fail)
|
||||
if _ := fs.directory_mkdir(mut parent_dir, 'subdir') {
|
||||
assert false, 'Expected error when creating duplicate directory'
|
||||
} else {
|
||||
assert err.msg().contains('already exists')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_directory_touch() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a parent directory
|
||||
mut parent_dir := fs.new_directory(
|
||||
name: 'parent_dir'
|
||||
)!
|
||||
|
||||
// Test creating a file
|
||||
mut file := fs.directory_touch(parent_dir, 'test_file.txt')!
|
||||
|
||||
// Reload the parent directory to get the latest version
|
||||
if updated_dir := fs.load_entry(parent_dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
parent_dir.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the file
|
||||
assert file.metadata.name == 'test_file.txt'
|
||||
assert file.metadata.file_type == .file
|
||||
assert file.parent_id == parent_dir.metadata.id
|
||||
assert file.data == '' // Should be empty
|
||||
|
||||
// Verify the parent directory's children
|
||||
assert parent_dir.children.len == 1
|
||||
assert parent_dir.children[0] == file.metadata.id
|
||||
|
||||
// Test creating a duplicate file (should fail)
|
||||
if _ := fs.directory_touch(parent_dir, 'test_file.txt') {
|
||||
assert false, 'Expected error when creating duplicate file'
|
||||
} else {
|
||||
assert err.msg().contains('already exists')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_directory_rm() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a parent directory
|
||||
mut parent_dir := fs.new_directory(
|
||||
name: 'parent_dir'
|
||||
)!
|
||||
|
||||
// Create a file to remove
|
||||
mut file := fs.directory_touch(parent_dir, 'test_file.txt')!
|
||||
|
||||
// Reload the parent directory to get the latest version
|
||||
if updated_dir := fs.load_entry(parent_dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
parent_dir.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the parent directory's children
|
||||
assert parent_dir.children.len == 1
|
||||
|
||||
// Test removing the file
|
||||
fs.directory_rm(mut parent_dir, 'test_file.txt')!
|
||||
|
||||
// Reload the parent directory to get the latest version
|
||||
if updated_dir := fs.load_entry(parent_dir.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
parent_dir.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the parent directory's children
|
||||
assert parent_dir.children.len == 0
|
||||
|
||||
// Test removing a non-existent file (should fail)
|
||||
if _ := fs.directory_rm(mut parent_dir, 'nonexistent.txt') {
|
||||
assert false, 'Expected error when removing non-existent file'
|
||||
} else {
|
||||
assert err.msg().contains('not found')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_directory_rename() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a parent directory
|
||||
mut parent_dir := fs.new_directory(
|
||||
name: 'parent_dir'
|
||||
)!
|
||||
|
||||
// Create a subdirectory to rename
|
||||
mut subdir := fs.directory_mkdir(mut parent_dir, 'old_name')!
|
||||
|
||||
// Test renaming the subdirectory
|
||||
renamed_dir := fs.directory_rename(parent_dir, 'old_name', 'new_name')!
|
||||
|
||||
// Verify the renamed directory
|
||||
assert renamed_dir.metadata.name == 'new_name'
|
||||
|
||||
// Test renaming a non-existent directory (should fail)
|
||||
if _ := fs.directory_rename(parent_dir, 'nonexistent', 'new_name') {
|
||||
assert false, 'Expected error when renaming non-existent directory'
|
||||
} else {
|
||||
assert err.msg().contains('not found')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_directory_children() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a parent directory
|
||||
mut parent_dir := fs.new_directory(
|
||||
name: 'parent_dir'
|
||||
)!
|
||||
|
||||
ch := fs.directory_children(mut parent_dir, false)!
|
||||
panic(ch)
|
||||
|
||||
// Create subdirectories and files
|
||||
mut subdir1 := fs.directory_mkdir(mut parent_dir, 'subdir1')!
|
||||
mut subdir2 := fs.directory_mkdir(mut parent_dir, 'subdir2')!
|
||||
mut file1 := fs.directory_touch(parent_dir, 'file1.txt')!
|
||||
|
||||
// Create a nested file
|
||||
mut nested_file := fs.directory_touch(subdir1, 'nested.txt')!
|
||||
|
||||
// Test getting non-recursive children
|
||||
children := fs.directory_children(mut parent_dir, false)!
|
||||
assert children.len == 3
|
||||
|
||||
// Verify children types
|
||||
mut dir_count := 0
|
||||
mut file_count := 0
|
||||
for child in children {
|
||||
if child is Directory {
|
||||
dir_count++
|
||||
} else if child is File {
|
||||
file_count++
|
||||
}
|
||||
}
|
||||
assert dir_count == 2
|
||||
assert file_count == 1
|
||||
|
||||
// Test getting recursive children
|
||||
recursive_children := fs.directory_children(mut parent_dir, true)!
|
||||
assert recursive_children.len == 4 // parent_dir's 3 children + nested_file
|
||||
}
|
||||
|
||||
fn test_directory_move() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create source and destination parent directories
|
||||
mut src_parent := fs.new_directory(name: 'src_parent')!
|
||||
mut dst_parent := fs.new_directory(name: 'dst_parent')!
|
||||
|
||||
// Create a directory to move with nested structure
|
||||
mut dir_to_move := fs.directory_mkdir(mut src_parent, 'dir_to_move')!
|
||||
mut nested_dir := fs.directory_mkdir(mut dir_to_move, 'nested_dir')!
|
||||
mut nested_file := fs.directory_touch(dir_to_move, 'nested_file.txt')!
|
||||
|
||||
// Reload the directories to get the latest versions
|
||||
if updated_dir := fs.load_entry(src_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
src_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
if updated_dir := fs.load_entry(dst_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
dst_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
if updated_dir := fs.load_entry(dir_to_move.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
dir_to_move.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
// Test moving the directory
|
||||
moved_dir := fs.directory_move(src_parent, MoveDirArgs{
|
||||
src_entry_name: 'dir_to_move'
|
||||
dst_entry_name: 'moved_dir'
|
||||
dst_parent_dir: dst_parent
|
||||
})!
|
||||
|
||||
// Reload the directories to get the latest versions
|
||||
if updated_dir := fs.load_entry(src_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
src_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
if updated_dir := fs.load_entry(dst_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
dst_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the moved directory
|
||||
assert moved_dir.metadata.name == 'moved_dir'
|
||||
assert moved_dir.parent_id == dst_parent.metadata.id
|
||||
assert moved_dir.children.len == 2
|
||||
|
||||
// Verify source parent no longer has the directory
|
||||
assert src_parent.children.len == 0
|
||||
|
||||
// Verify destination parent has the moved directory
|
||||
assert dst_parent.children.len == 1
|
||||
assert dst_parent.children[0] == moved_dir.metadata.id
|
||||
|
||||
// Test moving non-existent directory (should fail)
|
||||
if _ := fs.directory_move(src_parent, MoveDirArgs{
|
||||
src_entry_name: 'nonexistent'
|
||||
dst_entry_name: 'new_name'
|
||||
dst_parent_dir: dst_parent
|
||||
}) {
|
||||
assert false, 'Expected error when moving non-existent directory'
|
||||
} else {
|
||||
assert err.msg().contains('not found')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_directory_copy() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create source and destination parent directories
|
||||
mut src_parent := fs.new_directory(name: 'src_parent')!
|
||||
mut dst_parent := fs.new_directory(name: 'dst_parent')!
|
||||
|
||||
// Create a directory to copy with nested structure
|
||||
mut dir_to_copy := fs.directory_mkdir(mut src_parent, 'dir_to_copy')!
|
||||
mut nested_dir := fs.directory_mkdir(mut dir_to_copy, 'nested_dir')!
|
||||
mut nested_file := fs.directory_touch(dir_to_copy, 'nested_file.txt')!
|
||||
|
||||
// Reload the directories to get the latest versions
|
||||
if updated_dir := fs.load_entry(src_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
src_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
if updated_dir := fs.load_entry(dst_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
dst_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
if updated_dir := fs.load_entry(dir_to_copy.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
dir_to_copy.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
// Test copying the directory
|
||||
copied_dir := fs.directory_copy(mut src_parent, CopyDirArgs{
|
||||
src_entry_name: 'dir_to_copy'
|
||||
dst_entry_name: 'copied_dir'
|
||||
dst_parent_dir: dst_parent
|
||||
})!
|
||||
|
||||
// Reload the directories to get the latest versions
|
||||
if updated_dir := fs.load_entry(src_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
src_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
if updated_dir := fs.load_entry(dst_parent.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
dst_parent.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
if updated_dir := fs.load_entry(dir_to_copy.metadata.id) {
|
||||
if updated_dir is Directory {
|
||||
mut dir := updated_dir as Directory
|
||||
dir_to_copy.children = dir.children
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the copied directory
|
||||
assert copied_dir.metadata.name == 'copied_dir'
|
||||
assert copied_dir.parent_id == dst_parent.metadata.id
|
||||
assert copied_dir.children.len == 2
|
||||
|
||||
// Verify source directory still exists with its children
|
||||
assert src_parent.children.len == 1
|
||||
assert dir_to_copy.children.len == 2
|
||||
|
||||
// Verify destination parent has the copied directory
|
||||
assert dst_parent.children.len == 1
|
||||
assert dst_parent.children[0] == copied_dir.metadata.id
|
||||
|
||||
// Test copying non-existent directory (should fail)
|
||||
if _ := fs.directory_copy(mut src_parent, CopyDirArgs{
|
||||
src_entry_name: 'nonexistent'
|
||||
dst_entry_name: 'new_copy'
|
||||
dst_parent_dir: dst_parent
|
||||
}) {
|
||||
assert false, 'Expected error when copying non-existent directory'
|
||||
} else {
|
||||
assert err.msg().contains('not found')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_directory_add_symlink() ! {
|
||||
mut fs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a parent directory
|
||||
mut parent_dir := fs.new_directory(
|
||||
name: 'parent_dir'
|
||||
)!
|
||||
|
||||
// Create a symlink
|
||||
mut symlink := Symlink{
|
||||
metadata: Metadata{
|
||||
id: fs.get_next_id()
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
size: 0
|
||||
mode: 0o777
|
||||
owner: 'user'
|
||||
group: 'user'
|
||||
created_at: 0
|
||||
modified_at: 0
|
||||
accessed_at: 0
|
||||
}
|
||||
target: '/path/to/target'
|
||||
parent_id: parent_dir.metadata.id
|
||||
}
|
||||
|
||||
// Test adding the symlink to the directory
|
||||
fs.directory_add_symlink(mut parent_dir, mut symlink)!
|
||||
|
||||
// Verify the parent directory's children
|
||||
assert parent_dir.children.len == 1
|
||||
assert parent_dir.children[0] == symlink.metadata.id
|
||||
|
||||
// Test adding a duplicate symlink (should fail)
|
||||
if _ := fs.directory_add_symlink(mut parent_dir, mut symlink) {
|
||||
assert false, 'Expected error when adding duplicate symlink'
|
||||
} else {
|
||||
assert err.msg().contains('already exists')
|
||||
}
|
||||
}
|
||||
223
lib/vfs/vfs_db/vfs_getters_test.v
Normal file
223
lib/vfs/vfs_db/vfs_getters_test.v
Normal file
@@ -0,0 +1,223 @@
|
||||
module vfs_db
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import rand
|
||||
|
||||
fn setup_vfs() !(&DatabaseVFS, string) {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_getters_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Create VFS with separate databases for data and metadata
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
return vfs, test_data_dir
|
||||
}
|
||||
|
||||
fn teardown_vfs(data_dir string) {
|
||||
os.rmdir_all(data_dir) or {}
|
||||
}
|
||||
|
||||
fn test_root_get_as_dir() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test getting the root directory
|
||||
mut root := vfs.root_get_as_dir()!
|
||||
|
||||
// Verify the root directory
|
||||
assert root.metadata.name == ''
|
||||
assert root.metadata.file_type == .directory
|
||||
assert root.metadata.mode == 0o755
|
||||
assert root.metadata.owner == 'user'
|
||||
assert root.metadata.group == 'user'
|
||||
assert root.parent_id == 0
|
||||
|
||||
// Test getting the root directory again (should be the same)
|
||||
mut root2 := vfs.root_get_as_dir()!
|
||||
assert root2.metadata.id == root.metadata.id
|
||||
}
|
||||
|
||||
fn test_get_entry_root() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test getting the root entry with different path formats
|
||||
root1 := vfs.get_entry('/')!
|
||||
root2 := vfs.get_entry('')!
|
||||
root3 := vfs.get_entry('.')!
|
||||
|
||||
// Verify all paths return the root directory
|
||||
assert root1 is Directory
|
||||
assert root2 is Directory
|
||||
assert root3 is Directory
|
||||
|
||||
if root1 is Directory {
|
||||
assert root1.metadata.name == ''
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_entry_file() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a file in the root directory
|
||||
mut root := vfs.root_get_as_dir()!
|
||||
mut file := vfs.directory_touch(root, 'test_file.txt')!
|
||||
|
||||
// Test getting the file entry
|
||||
entry := vfs.get_entry('/test_file.txt')!
|
||||
|
||||
// Verify the entry is a file
|
||||
assert entry is File
|
||||
|
||||
if entry is File {
|
||||
assert entry.metadata.name == 'test_file.txt'
|
||||
assert entry.metadata.file_type == .file
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_entry_directory() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a directory in the root directory
|
||||
mut root := vfs.root_get_as_dir()!
|
||||
mut dir := vfs.directory_mkdir(mut root, 'test_dir')!
|
||||
|
||||
// Test getting the directory entry
|
||||
entry := vfs.get_entry('/test_dir')!
|
||||
|
||||
// Verify the entry is a directory
|
||||
assert entry is Directory
|
||||
|
||||
if entry is Directory {
|
||||
assert entry.metadata.name == 'test_dir'
|
||||
assert entry.metadata.file_type == .directory
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_entry_nested() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a nested directory structure
|
||||
mut root := vfs.root_get_as_dir()!
|
||||
mut dir1 := vfs.directory_mkdir(mut root, 'dir1')!
|
||||
mut dir2 := vfs.directory_mkdir(mut dir1, 'dir2')!
|
||||
mut file := vfs.directory_touch(dir2, 'nested_file.txt')!
|
||||
|
||||
// Test getting the nested file entry
|
||||
entry := vfs.get_entry('/dir1/dir2/nested_file.txt')!
|
||||
|
||||
// Verify the entry is a file
|
||||
assert entry is File
|
||||
|
||||
if entry is File {
|
||||
assert entry.metadata.name == 'nested_file.txt'
|
||||
assert entry.metadata.file_type == .file
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_entry_not_found() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test getting a non-existent entry
|
||||
if _ := vfs.get_entry('/nonexistent') {
|
||||
assert false, 'Expected error when getting non-existent entry'
|
||||
} else {
|
||||
assert err.msg().contains('Path not found')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_entry_not_a_directory() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a file in the root directory
|
||||
mut root := vfs.root_get_as_dir()!
|
||||
mut file := vfs.directory_touch(root, 'test_file.txt')!
|
||||
|
||||
// Test getting an entry through a file (should fail)
|
||||
if _ := vfs.get_entry('/test_file.txt/something') {
|
||||
assert false, 'Expected error when traversing through a file'
|
||||
} else {
|
||||
assert err.msg().contains('Not a directory')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_directory() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a directory in the root directory
|
||||
mut root := vfs.root_get_as_dir()!
|
||||
mut dir := vfs.directory_mkdir(mut root, 'test_dir')!
|
||||
|
||||
// Test getting the directory
|
||||
retrieved_dir := vfs.get_directory('/test_dir')!
|
||||
|
||||
// Verify the directory
|
||||
assert retrieved_dir.metadata.name == 'test_dir'
|
||||
assert retrieved_dir.metadata.file_type == .directory
|
||||
}
|
||||
|
||||
fn test_get_directory_root() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test getting the root directory
|
||||
root := vfs.get_directory('/')!
|
||||
|
||||
// Verify the root directory
|
||||
assert root.metadata.name == ''
|
||||
assert root.metadata.file_type == .directory
|
||||
}
|
||||
|
||||
fn test_get_directory_not_a_directory() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a file in the root directory
|
||||
mut root := vfs.root_get_as_dir()!
|
||||
mut file := vfs.directory_touch(root, 'test_file.txt')!
|
||||
|
||||
// Test getting a file as a directory (should fail)
|
||||
if _ := vfs.get_directory('/test_file.txt') {
|
||||
assert false, 'Expected error when getting a file as a directory'
|
||||
} else {
|
||||
assert err.msg().contains('Not a directory')
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
module vfs_db
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import log
|
||||
import os
|
||||
import time
|
||||
|
||||
@@ -10,6 +12,7 @@ pub fn (mut fs DatabaseVFS) root_get() !vfs.FSEntry {
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) file_create(path string) !vfs.FSEntry {
|
||||
log.info('[DatabaseVFS] Creating file ${path}')
|
||||
// Get parent directory
|
||||
parent_path := os.dir(path)
|
||||
file_name := os.base(path)
|
||||
@@ -19,6 +22,7 @@ pub fn (mut self DatabaseVFS) file_create(path string) !vfs.FSEntry {
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) file_read(path string) ![]u8 {
|
||||
log.info('[DatabaseVFS] Reading file ${path}')
|
||||
mut entry := self.get_entry(path)!
|
||||
if mut entry is File {
|
||||
return entry.read().bytes()
|
||||
@@ -27,12 +31,18 @@ pub fn (mut self DatabaseVFS) file_read(path string) ![]u8 {
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) file_write(path string, data []u8) ! {
|
||||
mut entry := self.get_entry(path)!
|
||||
if mut entry is File {
|
||||
entry.write(data.bytestr())
|
||||
self.save_entry(entry)!
|
||||
if mut entry := self.get_entry(path) {
|
||||
if mut entry is File {
|
||||
log.info('[DatabaseVFS] Writing file ${path}')
|
||||
log.info('[DatabaseVFS] Writing data ${data.bytestr()}')
|
||||
entry.write(data.bytestr())
|
||||
self.save_entry(entry)!
|
||||
} else {
|
||||
panic('handle error')
|
||||
}
|
||||
} else {
|
||||
return error('Not a file: ${path}')
|
||||
self.file_create(path)!
|
||||
self.file_write(path, data)!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +55,7 @@ pub fn (mut self DatabaseVFS) file_delete(path string) ! {
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) dir_create(path string) !vfs.FSEntry {
|
||||
log.info('[DatabaseVFS] Creating Directory ${path}')
|
||||
parent_path := os.dir(path)
|
||||
dir_name := os.base(path)
|
||||
|
||||
@@ -53,8 +64,19 @@ pub fn (mut self DatabaseVFS) dir_create(path string) !vfs.FSEntry {
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) dir_list(path string) ![]vfs.FSEntry {
|
||||
log.info('[DatabaseVFS] Listing Directory ${path}')
|
||||
mut dir := self.get_directory(path)!
|
||||
return self.directory_children(mut dir, false)!.map(vfs.FSEntry(it))
|
||||
mut entries := []vfs.FSEntry{}
|
||||
for child in self.directory_children(mut dir, false)! {
|
||||
if child is File {
|
||||
entries << vfs.FSEntry(child)
|
||||
} else if child is Directory {
|
||||
entries << vfs.FSEntry(child)
|
||||
} else if child is Symlink {
|
||||
entries << vfs.FSEntry(child)
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) dir_delete(path string) ! {
|
||||
@@ -125,6 +147,7 @@ pub fn (mut fs DatabaseVFS) get(path string) !vfs.FSEntry {
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) rename(old_path string, new_path string) !vfs.FSEntry {
|
||||
log.info('[DatabaseVFS] Renaming ${old_path} to ${new_path}')
|
||||
src_parent_path := os.dir(old_path)
|
||||
src_name := os.base(old_path)
|
||||
dst_name := os.base(new_path)
|
||||
@@ -163,8 +186,11 @@ pub fn (mut self DatabaseVFS) copy(src_path string, dst_path string) !vfs.FSEntr
|
||||
}
|
||||
|
||||
pub fn (mut self DatabaseVFS) move(src_path string, dst_path string) !vfs.FSEntry {
|
||||
log.info('[DatabaseVFS] Moving ${src_path} to ${dst_path}')
|
||||
|
||||
src_parent_path := os.dir(src_path)
|
||||
dst_parent_path := os.dir(dst_path)
|
||||
log.info('${src_parent_path}')
|
||||
|
||||
if !self.exists(src_parent_path) {
|
||||
return error('${src_parent_path} does not exist')
|
||||
|
||||
@@ -10,11 +10,16 @@ fn setup_vfs() !(&DatabaseVFS, string) {
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
mut db_data := ourdb.new(
|
||||
path: test_data_dir
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut vfs := new(mut db_data, data_dir: test_data_dir)!
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
return vfs, test_data_dir
|
||||
}
|
||||
|
||||
@@ -152,8 +157,95 @@ fn test_deletion_operations() ! {
|
||||
assert vfs.exists('/test_dir') == false
|
||||
}
|
||||
|
||||
// Add more test functions for other operations like:
|
||||
// - test_directory_copy()
|
||||
// - test_symlink_operations()
|
||||
// - test_directory_rename()
|
||||
// - test_file_metadata()
|
||||
fn test_symlink_operations() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
vfs.dir_create('/test_dir')!
|
||||
vfs.file_create('/test_dir/target.txt')!
|
||||
|
||||
// Test symlink creation
|
||||
mut symlink := vfs.link_create('/test_dir/target.txt', '/test_link')!
|
||||
assert symlink.get_metadata().name == 'test_link'
|
||||
assert symlink.get_metadata().file_type == .symlink
|
||||
assert vfs.exists('/test_link') == true
|
||||
|
||||
// Test symlink reading
|
||||
target := vfs.link_read('/test_link')!
|
||||
assert target == '/test_dir/target.txt'
|
||||
|
||||
// Test symlink deletion
|
||||
vfs.link_delete('/test_link')!
|
||||
assert vfs.exists('/test_link') == false
|
||||
}
|
||||
|
||||
fn test_rename_operations() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test file rename
|
||||
vfs.file_create('/test_file.txt')!
|
||||
renamed_file := vfs.rename('/test_file.txt', '/renamed_file.txt')!
|
||||
assert renamed_file.get_metadata().name == 'renamed_file.txt'
|
||||
assert vfs.exists('/test_file.txt') == false
|
||||
assert vfs.exists('/renamed_file.txt') == true
|
||||
|
||||
// Test directory rename
|
||||
vfs.dir_create('/test_dir')!
|
||||
renamed_dir := vfs.rename('/test_dir', '/renamed_dir')!
|
||||
assert renamed_dir.get_metadata().name == 'renamed_dir'
|
||||
assert vfs.exists('/test_dir') == false
|
||||
assert vfs.exists('/renamed_dir') == true
|
||||
}
|
||||
|
||||
fn test_exists_function() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test root exists
|
||||
assert vfs.exists('/') == true
|
||||
|
||||
// Test non-existent path
|
||||
assert vfs.exists('/nonexistent') == false
|
||||
|
||||
// Create and test file exists
|
||||
vfs.file_create('/test_file.txt')!
|
||||
assert vfs.exists('/test_file.txt') == true
|
||||
|
||||
// Create and test directory exists
|
||||
vfs.dir_create('/test_dir')!
|
||||
assert vfs.exists('/test_dir') == true
|
||||
|
||||
// Test with and without leading slash
|
||||
assert vfs.exists('test_dir') == true
|
||||
}
|
||||
|
||||
fn test_get_function() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test getting root
|
||||
root := vfs.get('/')!
|
||||
assert root.get_metadata().name == ''
|
||||
assert root.get_metadata().file_type == .directory
|
||||
|
||||
// Test getting file
|
||||
vfs.file_create('/test_file.txt')!
|
||||
file := vfs.get('/test_file.txt')!
|
||||
assert file.get_metadata().name == 'test_file.txt'
|
||||
assert file.get_metadata().file_type == .file
|
||||
|
||||
// Test getting directory
|
||||
vfs.dir_create('/test_dir')!
|
||||
dir := vfs.get('/test_dir')!
|
||||
assert dir.get_metadata().name == 'test_dir'
|
||||
assert dir.get_metadata().file_type == .directory
|
||||
}
|
||||
|
||||
196
lib/vfs/vfs_db/vfs_test.v
Normal file
196
lib/vfs/vfs_db/vfs_test.v
Normal file
@@ -0,0 +1,196 @@
|
||||
module vfs_db
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import rand
|
||||
|
||||
fn setup_vfs() !(&DatabaseVFS, string) {
|
||||
test_data_dir := os.join_path(os.temp_dir(), 'vfsourdb_vfs_test_${rand.string(3)}')
|
||||
os.mkdir_all(test_data_dir)!
|
||||
|
||||
// Create separate databases for data and metadata
|
||||
mut db_data := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'data')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
mut db_metadata := ourdb.new(
|
||||
path: os.join_path(test_data_dir, 'metadata')
|
||||
incremental_mode: false
|
||||
)!
|
||||
|
||||
// Create VFS with separate databases for data and metadata
|
||||
mut vfs := new(mut db_data, mut db_metadata)!
|
||||
return vfs, test_data_dir
|
||||
}
|
||||
|
||||
fn teardown_vfs(data_dir string) {
|
||||
os.rmdir_all(data_dir) or {}
|
||||
}
|
||||
|
||||
fn test_get_next_id() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Test that get_next_id increments correctly
|
||||
assert vfs.last_inserted_id == 0
|
||||
|
||||
id1 := vfs.get_next_id()
|
||||
assert id1 == 1
|
||||
assert vfs.last_inserted_id == 1
|
||||
|
||||
id2 := vfs.get_next_id()
|
||||
assert id2 == 2
|
||||
assert vfs.last_inserted_id == 2
|
||||
|
||||
id3 := vfs.get_next_id()
|
||||
assert id3 == 3
|
||||
assert vfs.last_inserted_id == 3
|
||||
}
|
||||
|
||||
fn test_save_load_entry() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a directory entry
|
||||
mut dir := Directory{
|
||||
metadata: Metadata{
|
||||
id: 1
|
||||
name: 'test_dir'
|
||||
file_type: .directory
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
entries: []
|
||||
}
|
||||
|
||||
// Save the directory
|
||||
saved_id := vfs.save_entry(dir)!
|
||||
assert saved_id == 1
|
||||
|
||||
// Load the directory
|
||||
loaded_entry := vfs.load_entry(1)!
|
||||
|
||||
// Verify it's the same directory
|
||||
loaded_dir := loaded_entry as Directory
|
||||
assert loaded_dir.metadata.id == dir.metadata.id
|
||||
assert loaded_dir.metadata.name == dir.metadata.name
|
||||
assert loaded_dir.metadata.file_type == dir.metadata.file_type
|
||||
}
|
||||
|
||||
fn test_save_load_file_with_data() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a file entry with data
|
||||
mut file := File{
|
||||
metadata: Metadata{
|
||||
id: 2
|
||||
name: 'test_file.txt'
|
||||
file_type: .file
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
data: 'Hello, World!'
|
||||
}
|
||||
|
||||
// Save the file
|
||||
saved_id := vfs.save_entry(file)!
|
||||
assert saved_id == 2
|
||||
|
||||
// Load the file
|
||||
loaded_entry := vfs.load_entry(2)!
|
||||
|
||||
// Verify it's the same file with the same data
|
||||
loaded_file := loaded_entry as File
|
||||
assert loaded_file.metadata.id == file.metadata.id
|
||||
assert loaded_file.metadata.name == file.metadata.name
|
||||
assert loaded_file.metadata.file_type == file.metadata.file_type
|
||||
assert loaded_file.data == file.data
|
||||
}
|
||||
|
||||
fn test_save_load_file_without_data() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a file entry without data
|
||||
mut file := File{
|
||||
metadata: Metadata{
|
||||
id: 3
|
||||
name: 'empty_file.txt'
|
||||
file_type: .file
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
data: ''
|
||||
}
|
||||
|
||||
// Save the file
|
||||
saved_id := vfs.save_entry(file)!
|
||||
assert saved_id == 3
|
||||
|
||||
// Load the file
|
||||
loaded_entry := vfs.load_entry(3)!
|
||||
|
||||
// Verify it's the same file with empty data
|
||||
loaded_file := loaded_entry as File
|
||||
assert loaded_file.metadata.id == file.metadata.id
|
||||
assert loaded_file.metadata.name == file.metadata.name
|
||||
assert loaded_file.metadata.file_type == file.metadata.file_type
|
||||
assert loaded_file.data == ''
|
||||
}
|
||||
|
||||
fn test_save_load_symlink() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Create a symlink entry
|
||||
mut symlink := Symlink{
|
||||
metadata: Metadata{
|
||||
id: 4
|
||||
name: 'test_link'
|
||||
file_type: .symlink
|
||||
created: 0
|
||||
modified: 0
|
||||
}
|
||||
target: '/path/to/target'
|
||||
}
|
||||
|
||||
// Save the symlink
|
||||
saved_id := vfs.save_entry(symlink)!
|
||||
assert saved_id == 4
|
||||
|
||||
// Load the symlink
|
||||
loaded_entry := vfs.load_entry(4)!
|
||||
|
||||
// Verify it's the same symlink
|
||||
loaded_symlink := loaded_entry as Symlink
|
||||
assert loaded_symlink.metadata.id == symlink.metadata.id
|
||||
assert loaded_symlink.metadata.name == symlink.metadata.name
|
||||
assert loaded_symlink.metadata.file_type == symlink.metadata.file_type
|
||||
assert loaded_symlink.target == symlink.target
|
||||
}
|
||||
|
||||
fn test_load_nonexistent_entry() ! {
|
||||
mut vfs, data_dir := setup_vfs()!
|
||||
defer {
|
||||
teardown_vfs(data_dir)
|
||||
}
|
||||
|
||||
// Try to load an entry that doesn't exist
|
||||
if _ := vfs.load_entry(999) {
|
||||
assert false, 'Expected error when loading non-existent entry'
|
||||
} else {
|
||||
assert err.msg() == 'Entry not found'
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module vfsnested
|
||||
module vfs_nested
|
||||
|
||||
import freeflowuniverse.herolib.vfs.vfs_local
|
||||
import os
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module vfsnested
|
||||
module vfs_nested
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
|
||||
@@ -33,12 +33,10 @@ fn (self &NestedVFS) find_vfs(path string) !(vfs.VFSImplementation, string) {
|
||||
mut prefixes := self.vfs_map.keys()
|
||||
prefixes.sort(a.len > b.len)
|
||||
|
||||
println('debugzone ${path} ${prefixes}')
|
||||
for prefix in prefixes {
|
||||
if path.starts_with(prefix) {
|
||||
relative_path := path[prefix.len..]
|
||||
if relative_path.starts_with('/') {
|
||||
return self.vfs_map[prefix], relative_path[1..]
|
||||
}
|
||||
return self.vfs_map[prefix], relative_path
|
||||
}
|
||||
}
|
||||
@@ -73,10 +71,22 @@ pub fn (mut self NestedVFS) link_delete(path string) ! {
|
||||
|
||||
pub fn (mut self NestedVFS) file_create(path string) !vfs.FSEntry {
|
||||
mut impl, rel_path := self.find_vfs(path)!
|
||||
return impl.file_create(rel_path)
|
||||
sub_entry := impl.file_create(rel_path)!
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(sub_entry, prefix)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) file_read(path string) ![]u8 {
|
||||
println('debuzone- File read ${path}')
|
||||
mut impl, rel_path := self.find_vfs(path)!
|
||||
return impl.file_read(rel_path)
|
||||
}
|
||||
@@ -93,7 +103,18 @@ pub fn (mut self NestedVFS) file_delete(path string) ! {
|
||||
|
||||
pub fn (mut self NestedVFS) dir_create(path string) !vfs.FSEntry {
|
||||
mut impl, rel_path := self.find_vfs(path)!
|
||||
return impl.dir_create(rel_path)
|
||||
sub_entry := impl.dir_create(rel_path)!
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(sub_entry, prefix)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) dir_list(path string) ![]vfs.FSEntry {
|
||||
@@ -119,7 +140,24 @@ pub fn (mut self NestedVFS) dir_list(path string) ![]vfs.FSEntry {
|
||||
}
|
||||
|
||||
mut impl, rel_path := self.find_vfs(path)!
|
||||
return impl.dir_list(rel_path)
|
||||
sub_entries := impl.dir_list(rel_path)!
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Convert all entries to nested entries
|
||||
mut entries := []vfs.FSEntry{}
|
||||
for sub_entry in sub_entries {
|
||||
entries << self.nester_entry(sub_entry, prefix)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) dir_delete(path string) ! {
|
||||
@@ -141,7 +179,29 @@ pub fn (mut self NestedVFS) get(path string) !vfs.FSEntry {
|
||||
return self.root_get()
|
||||
}
|
||||
mut impl, rel_path := self.find_vfs(path)!
|
||||
return impl.get(rel_path)
|
||||
|
||||
// now must convert entry of nested fvs to entry of nester
|
||||
sub_entry := impl.get(rel_path)!
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(sub_entry, prefix)
|
||||
}
|
||||
|
||||
// nester_entry converts an FSEntry from a sub VFS to an FSEntry for the nester VFS
|
||||
// by prefixing the nested VFS's path onto the FSEntry's path
|
||||
fn (self &NestedVFS) nester_entry(entry vfs.FSEntry, prefix string) vfs.FSEntry {
|
||||
return &NestedEntry{
|
||||
original: entry
|
||||
prefix: prefix
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) rename(old_path string, new_path string) !vfs.FSEntry {
|
||||
@@ -153,7 +213,17 @@ pub fn (mut self NestedVFS) rename(old_path string, new_path string) !vfs.FSEntr
|
||||
}
|
||||
|
||||
renamed_file := old_impl.rename(old_rel_path, new_rel_path)!
|
||||
return renamed_file
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == old_impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(renamed_file, prefix)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) copy(src_path string, dst_path string) !vfs.FSEntry {
|
||||
@@ -161,7 +231,18 @@ pub fn (mut self NestedVFS) copy(src_path string, dst_path string) !vfs.FSEntry
|
||||
mut dst_impl, dst_rel_path := self.find_vfs(dst_path)!
|
||||
|
||||
if src_impl == dst_impl {
|
||||
return src_impl.copy(src_rel_path, dst_rel_path)
|
||||
copied_file := src_impl.copy(src_rel_path, dst_rel_path)!
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == src_impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(copied_file, prefix)
|
||||
}
|
||||
|
||||
// Copy across different VFS implementations
|
||||
@@ -169,18 +250,50 @@ pub fn (mut self NestedVFS) copy(src_path string, dst_path string) !vfs.FSEntry
|
||||
data := src_impl.file_read(src_rel_path)!
|
||||
new_file := dst_impl.file_create(dst_rel_path)!
|
||||
dst_impl.file_write(dst_rel_path, data)!
|
||||
return new_file
|
||||
|
||||
// Find the prefix for the destination VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == dst_impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(new_file, prefix)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) move(src_path string, dst_path string) !vfs.FSEntry {
|
||||
mut src_impl, src_rel_path := self.find_vfs(src_path)!
|
||||
_, dst_rel_path := self.find_vfs(dst_path)!
|
||||
return src_impl.move(src_rel_path, dst_rel_path)
|
||||
moved_file := src_impl.move(src_rel_path, dst_rel_path)!
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == src_impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(moved_file, prefix)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) link_create(target_path string, link_path string) !vfs.FSEntry {
|
||||
mut impl, rel_path := self.find_vfs(link_path)!
|
||||
return impl.link_create(target_path, rel_path)
|
||||
link_entry := impl.link_create(target_path, rel_path)!
|
||||
|
||||
// Find the prefix for this VFS implementation
|
||||
mut prefix := ''
|
||||
for p, v in self.vfs_map {
|
||||
if v == impl {
|
||||
prefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return self.nester_entry(link_entry, prefix)
|
||||
}
|
||||
|
||||
pub fn (mut self NestedVFS) link_read(path string) !string {
|
||||
@@ -250,3 +363,37 @@ pub fn (self &MountEntry) is_file() bool {
|
||||
pub fn (self &MountEntry) is_symlink() bool {
|
||||
return self.metadata.file_type == .symlink
|
||||
}
|
||||
|
||||
// NestedEntry wraps an FSEntry from a sub VFS and prefixes its path
|
||||
pub struct NestedEntry {
|
||||
pub mut:
|
||||
original vfs.FSEntry
|
||||
prefix string
|
||||
}
|
||||
|
||||
fn (e &NestedEntry) get_metadata() vfs.Metadata {
|
||||
return e.original.get_metadata()
|
||||
}
|
||||
|
||||
fn (e &NestedEntry) get_path() string {
|
||||
original_path := e.original.get_path()
|
||||
if original_path == '/' {
|
||||
return e.prefix
|
||||
}
|
||||
return e.prefix + original_path
|
||||
}
|
||||
|
||||
// is_dir returns true if the entry is a directory
|
||||
pub fn (self &NestedEntry) is_dir() bool {
|
||||
return self.original.is_dir()
|
||||
}
|
||||
|
||||
// is_file returns true if the entry is a file
|
||||
pub fn (self &NestedEntry) is_file() bool {
|
||||
return self.original.is_file()
|
||||
}
|
||||
|
||||
// is_symlink returns true if the entry is a symlink
|
||||
pub fn (self &NestedEntry) is_symlink() bool {
|
||||
return self.original.is_symlink()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user