From 913b0cb790a63e95e8ecb6963188c2e73db88ea5 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Tue, 4 Mar 2025 00:49:57 +0100 Subject: [PATCH] fix vfs_db and add metadata_db --- lib/vfs/vfs_db/factory.v | 45 +- lib/vfs/vfs_db/factory_test.v | 35 ++ lib/vfs/vfs_db/id_table_test.v | 77 ++++ lib/vfs/vfs_db/metadata_test.v | 135 ++++++ lib/vfs/vfs_db/model_directory_test.v | 136 ++++++ lib/vfs/vfs_db/model_file.v | 2 + lib/vfs/vfs_db/model_file_test.v | 220 ++++++++++ lib/vfs/vfs_db/model_fsentry_test.v | 185 ++++++++ lib/vfs/vfs_db/model_symlink_test.v | 159 +++++++ lib/vfs/vfs_db/print.v | 4 + lib/vfs/vfs_db/print_test.v | 190 +++++++++ lib/vfs/vfs_db/vfs.v | 2 - lib/vfs/vfs_db/vfs_directory.v | 290 +++++++------ lib/vfs/vfs_db/vfs_directory_test.v | 520 +++++++++++++++++++++++ lib/vfs/vfs_db/vfs_getters_test.v | 223 ++++++++++ lib/vfs/vfs_db/vfs_implementation.v | 38 +- lib/vfs/vfs_db/vfs_implementation_test.v | 106 ++++- lib/vfs/vfs_db/vfs_test.v | 196 +++++++++ lib/vfs/vfs_nested/nested_test.v | 2 +- lib/vfs/vfs_nested/vfsnested.v | 173 +++++++- 20 files changed, 2535 insertions(+), 203 deletions(-) create mode 100644 lib/vfs/vfs_db/factory_test.v create mode 100644 lib/vfs/vfs_db/id_table_test.v create mode 100644 lib/vfs/vfs_db/metadata_test.v create mode 100644 lib/vfs/vfs_db/model_directory_test.v create mode 100644 lib/vfs/vfs_db/model_file_test.v create mode 100644 lib/vfs/vfs_db/model_fsentry_test.v create mode 100644 lib/vfs/vfs_db/model_symlink_test.v create mode 100644 lib/vfs/vfs_db/print_test.v create mode 100644 lib/vfs/vfs_db/vfs_directory_test.v create mode 100644 lib/vfs/vfs_db/vfs_getters_test.v create mode 100644 lib/vfs/vfs_db/vfs_test.v diff --git a/lib/vfs/vfs_db/factory.v b/lib/vfs/vfs_db/factory.v index e790c969..dc840259 100644 --- a/lib/vfs/vfs_db/factory.v +++ b/lib/vfs/vfs_db/factory.v @@ -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 } diff --git a/lib/vfs/vfs_db/factory_test.v b/lib/vfs/vfs_db/factory_test.v new file mode 100644 index 00000000..b869ba34 --- /dev/null +++ b/lib/vfs/vfs_db/factory_test.v @@ -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 +} diff --git a/lib/vfs/vfs_db/id_table_test.v b/lib/vfs/vfs_db/id_table_test.v new file mode 100644 index 00000000..c516f277 --- /dev/null +++ b/lib/vfs/vfs_db/id_table_test.v @@ -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 +} diff --git a/lib/vfs/vfs_db/metadata_test.v b/lib/vfs/vfs_db/metadata_test.v new file mode 100644 index 00000000..6f3afd8f --- /dev/null +++ b/lib/vfs/vfs_db/metadata_test.v @@ -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 +} diff --git a/lib/vfs/vfs_db/model_directory_test.v b/lib/vfs/vfs_db/model_directory_test.v new file mode 100644 index 00000000..ea091238 --- /dev/null +++ b/lib/vfs/vfs_db/model_directory_test.v @@ -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 +} diff --git a/lib/vfs/vfs_db/model_file.v b/lib/vfs/vfs_db/model_file.v index 724db2da..1fba783c 100644 --- a/lib/vfs/vfs_db/model_file.v +++ b/lib/vfs/vfs_db/model_file.v @@ -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 ) } diff --git a/lib/vfs/vfs_db/model_file_test.v b/lib/vfs/vfs_db/model_file_test.v new file mode 100644 index 00000000..9b559378 --- /dev/null +++ b/lib/vfs/vfs_db/model_file_test.v @@ -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 +} diff --git a/lib/vfs/vfs_db/model_fsentry_test.v b/lib/vfs/vfs_db/model_fsentry_test.v new file mode 100644 index 00000000..7d55962e --- /dev/null +++ b/lib/vfs/vfs_db/model_fsentry_test.v @@ -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' + } + } +} diff --git a/lib/vfs/vfs_db/model_symlink_test.v b/lib/vfs/vfs_db/model_symlink_test.v new file mode 100644 index 00000000..7eeb3024 --- /dev/null +++ b/lib/vfs/vfs_db/model_symlink_test.v @@ -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 +} diff --git a/lib/vfs/vfs_db/print.v b/lib/vfs/vfs_db/print.v index 6f0c2049..d1ab2101 100644 --- a/lib/vfs/vfs_db/print.v +++ b/lib/vfs/vfs_db/print.v @@ -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' diff --git a/lib/vfs/vfs_db/print_test.v b/lib/vfs/vfs_db/print_test.v new file mode 100644 index 00000000..dbe82b34 --- /dev/null +++ b/lib/vfs/vfs_db/print_test.v @@ -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' +} diff --git a/lib/vfs/vfs_db/vfs.v b/lib/vfs/vfs_db/vfs.v index ac2e8ab6..dae1bd83 100644 --- a/lib/vfs/vfs_db/vfs.v +++ b/lib/vfs/vfs_db/vfs.v @@ -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 diff --git a/lib/vfs/vfs_db/vfs_directory.v b/lib/vfs/vfs_db/vfs_directory.v index f7b2873f..254d1a3a 100644 --- a/lib/vfs/vfs_db/vfs_directory.v +++ b/lib/vfs/vfs_db/vfs_directory.v @@ -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,8 +222,19 @@ 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 { @@ -293,15 +296,22 @@ 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 diff --git a/lib/vfs/vfs_db/vfs_directory_test.v b/lib/vfs/vfs_db/vfs_directory_test.v new file mode 100644 index 00000000..be191cda --- /dev/null +++ b/lib/vfs/vfs_db/vfs_directory_test.v @@ -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') + } +} diff --git a/lib/vfs/vfs_db/vfs_getters_test.v b/lib/vfs/vfs_db/vfs_getters_test.v new file mode 100644 index 00000000..df455064 --- /dev/null +++ b/lib/vfs/vfs_db/vfs_getters_test.v @@ -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') + } +} diff --git a/lib/vfs/vfs_db/vfs_implementation.v b/lib/vfs/vfs_db/vfs_implementation.v index 701b47c0..29df88c9 100644 --- a/lib/vfs/vfs_db/vfs_implementation.v +++ b/lib/vfs/vfs_db/vfs_implementation.v @@ -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') diff --git a/lib/vfs/vfs_db/vfs_implementation_test.v b/lib/vfs/vfs_db/vfs_implementation_test.v index 764bb72e..188a8351 100644 --- a/lib/vfs/vfs_db/vfs_implementation_test.v +++ b/lib/vfs/vfs_db/vfs_implementation_test.v @@ -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 db_metadata := ourdb.new( + path: os.join_path(test_data_dir, 'metadata') incremental_mode: false )! - mut vfs := new(mut db_data, data_dir: test_data_dir)! + 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 +} diff --git a/lib/vfs/vfs_db/vfs_test.v b/lib/vfs/vfs_db/vfs_test.v new file mode 100644 index 00000000..cbb7eaa0 --- /dev/null +++ b/lib/vfs/vfs_db/vfs_test.v @@ -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' + } +} diff --git a/lib/vfs/vfs_nested/nested_test.v b/lib/vfs/vfs_nested/nested_test.v index d9c72795..f1b68c3c 100644 --- a/lib/vfs/vfs_nested/nested_test.v +++ b/lib/vfs/vfs_nested/nested_test.v @@ -1,4 +1,4 @@ -module vfsnested +module vfs_nested import freeflowuniverse.herolib.vfs.vfs_local import os diff --git a/lib/vfs/vfs_nested/vfsnested.v b/lib/vfs/vfs_nested/vfsnested.v index 5c134d5b..30d773c4 100644 --- a/lib/vfs/vfs_nested/vfsnested.v +++ b/lib/vfs/vfs_nested/vfsnested.v @@ -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() +}