From 7b453962ca80bd69e00f362c7be7e92b024808a4 Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Tue, 18 Feb 2025 17:40:37 +0200 Subject: [PATCH] feat: Enhance WebDAV server and add VFS encoder/decoder tests - Add user authentication to the WebDAV server using a user database. - Implement encoding and decoding functionality for directories, files, and symlinks in the OurDBFS VFS. - Add comprehensive unit tests for the encoder and decoder functions. - Improve the OurDBFS factory method to handle directory creation more robustly using pathlib. - Add `delete` and `link_delete` methods to the `NestedVFS` and `OurDBVFS` implementations (though currently unimplemented). - Improve WebDAV file handling to correctly determine and set the content type. The previous implementation was incomplete and returned a dummy response. - Update VFS test to actually test functionality. - Remove unnecessary `root_dir` parameter from the WebDAV app. --- examples/vfs/example.vsh | 8 +- lib/vfs/ourdb_fs/encoder_test.v | 126 +++++++++++++++++++++++++++++++ lib/vfs/ourdb_fs/factory.v | 12 ++- lib/vfs/vfsnested/vfsnested.v | 12 ++- lib/vfs/vfsourdb/vfsourdb.v | 10 +++ lib/vfs/vfsourdb/vfsourdb_test.v | 52 ++++++------- lib/vfs/webdav/app.v | 16 ++-- lib/vfs/webdav/methods_vfs.v | 26 +++---- lib/vfs/webdav/prop.v | 2 +- 9 files changed, 205 insertions(+), 59 deletions(-) create mode 100644 lib/vfs/ourdb_fs/encoder_test.v diff --git a/examples/vfs/example.vsh b/examples/vfs/example.vsh index da81bc95..091bf0fe 100755 --- a/examples/vfs/example.vsh +++ b/examples/vfs/example.vsh @@ -18,6 +18,10 @@ high_level_vfs.add_vfs('/config', vfs2) or { panic(err) } high_level_vfs.add_vfs('/data/backup', vfs3) or { panic(err) } // Nested under /data // Create WebDAV Server that uses high level VFS -webdav_server := webdav.new_app( - vfs: high_level_vfs +mut webdav_server := webdav.new_app( + vfs: high_level_vfs + user_db: { + 'omda': '123' + } )! +webdav_server.run() diff --git a/lib/vfs/ourdb_fs/encoder_test.v b/lib/vfs/ourdb_fs/encoder_test.v new file mode 100644 index 00000000..a89b1127 --- /dev/null +++ b/lib/vfs/ourdb_fs/encoder_test.v @@ -0,0 +1,126 @@ +module ourdb_fs + +import os +import time + +fn test_directory_encoder_decoder() ! { + println('Testing encoding/decoding directories...') + + current_time := time.now().unix() + dir := Directory{ + metadata: Metadata{ + id: u32(current_time) + name: 'root' + file_type: .directory + created_at: current_time + modified_at: current_time + accessed_at: current_time + mode: 0o755 + owner: 'user' + group: 'user' + } + children: [u32(1), u32(2)] + parent_id: 0 + myvfs: unsafe { nil } + } + + encoded := dir.encode() + + mut decoded := decode_directory(encoded) or { + return error('Failed to decode directory: ${err}') + } + + assert decoded.metadata.id == dir.metadata.id + assert decoded.metadata.name == dir.metadata.name + assert decoded.metadata.file_type == dir.metadata.file_type + assert decoded.metadata.created_at == dir.metadata.created_at + assert decoded.metadata.modified_at == dir.metadata.modified_at + assert decoded.metadata.accessed_at == dir.metadata.accessed_at + assert decoded.metadata.mode == dir.metadata.mode + assert decoded.metadata.owner == dir.metadata.owner + assert decoded.metadata.group == dir.metadata.group + assert decoded.children == dir.children + assert decoded.parent_id == dir.parent_id + + println('Test completed successfully!') +} + +fn test_file_encoder_decoder() ! { + println('Testing encoding/decoding files...') + + current_time := time.now().unix() + file := File{ + metadata: Metadata{ + id: u32(current_time) + name: 'test.txt' + file_type: .file + created_at: current_time + modified_at: current_time + accessed_at: current_time + mode: 0o644 + owner: 'user' + group: 'user' + } + data: 'Hello, world!' + parent_id: 0 + myvfs: unsafe { nil } + } + + encoded := file.encode() + + mut decoded := decode_file(encoded) or { return error('Failed to decode file: ${err}') } + + assert decoded.metadata.id == file.metadata.id + assert decoded.metadata.name == file.metadata.name + assert decoded.metadata.file_type == file.metadata.file_type + assert decoded.metadata.created_at == file.metadata.created_at + assert decoded.metadata.modified_at == file.metadata.modified_at + assert decoded.metadata.accessed_at == file.metadata.accessed_at + assert decoded.metadata.mode == file.metadata.mode + assert decoded.metadata.owner == file.metadata.owner + assert decoded.metadata.group == file.metadata.group + assert decoded.data == file.data + assert decoded.parent_id == file.parent_id + + println('Test completed successfully!') +} + +fn test_symlink_encoder_decoder() ! { + println('Testing encoding/decoding symlinks...') + + current_time := time.now().unix() + symlink := Symlink{ + metadata: Metadata{ + id: u32(current_time) + name: 'test.txt' + file_type: .symlink + created_at: current_time + modified_at: current_time + accessed_at: current_time + mode: 0o644 + owner: 'user' + group: 'user' + } + target: 'test.txt' + parent_id: 0 + myvfs: unsafe { nil } + } + + encoded := symlink.encode() + + mut decoded := decode_symlink(encoded) or { return error('Failed to decode symlink: ${err}') } + + assert decoded.metadata.id == symlink.metadata.id + assert decoded.metadata.name == symlink.metadata.name + assert decoded.metadata.file_type == symlink.metadata.file_type + assert decoded.metadata.created_at == symlink.metadata.created_at + assert decoded.metadata.modified_at == symlink.metadata.modified_at + assert decoded.metadata.accessed_at == symlink.metadata.accessed_at + assert decoded.metadata.mode == symlink.metadata.mode + assert decoded.metadata.owner == symlink.metadata.owner + assert decoded.metadata.group == symlink.metadata.group + assert decoded.target == symlink.target + assert decoded.parent_id == symlink.parent_id + + println('Test completed successfully!') +} diff --git a/lib/vfs/ourdb_fs/factory.v b/lib/vfs/ourdb_fs/factory.v index 3b5cc4f2..14bbff76 100644 --- a/lib/vfs/ourdb_fs/factory.v +++ b/lib/vfs/ourdb_fs/factory.v @@ -1,7 +1,7 @@ module ourdb_fs -import os import freeflowuniverse.herolib.data.ourdb +import freeflowuniverse.herolib.core.pathlib // Factory method for creating a new OurDBFS instance @[params] @@ -14,13 +14,11 @@ pub: // Factory method for creating a new OurDBFS instance pub fn new(params VFSParams) !&OurDBFS { - if !os.exists(params.data_dir) { - os.mkdir(params.data_dir) or { return error('Failed to create data directory: ${err}') } + pathlib.get_dir(path: params.data_dir, create: true) or { + return error('Failed to create data directory: ${err}') } - if !os.exists(params.metadata_dir) { - os.mkdir(params.metadata_dir) or { - return error('Failed to create metadata directory: ${err}') - } + pathlib.get_dir(path: params.metadata_dir, create: true) or { + return error('Failed to create metadata directory: ${err}') } mut db_meta := ourdb.new( diff --git a/lib/vfs/vfsnested/vfsnested.v b/lib/vfs/vfsnested/vfsnested.v index f93efcf7..ccba36b4 100644 --- a/lib/vfs/vfsnested/vfsnested.v +++ b/lib/vfs/vfsnested/vfsnested.v @@ -56,6 +56,16 @@ pub fn (mut self NestedVFS) root_get() !vfscore.FSEntry { } } +pub fn (mut self NestedVFS) delete(path string) ! { + // mut impl, rel_path := self.find_vfs(path)! + // return impl.file_read(rel_path) +} + +pub fn (mut self NestedVFS) link_delete(path string) ! { + // mut impl, rel_path := self.find_vfs(path)! + // return impl.file_read(rel_path) +} + pub fn (mut self NestedVFS) file_create(path string) !vfscore.FSEntry { mut impl, rel_path := self.find_vfs(path)! return impl.file_create(rel_path) @@ -112,7 +122,7 @@ pub fn (mut self NestedVFS) dir_delete(path string) ! { } pub fn (mut self NestedVFS) exists(path string) bool { - mut impl, rel_path := self.find_vfs(path) or {return false} + mut impl, rel_path := self.find_vfs(path) or { return false } return impl.exists(rel_path) } diff --git a/lib/vfs/vfsourdb/vfsourdb.v b/lib/vfs/vfsourdb/vfsourdb.v index 9b697912..d72d478c 100644 --- a/lib/vfs/vfsourdb/vfsourdb.v +++ b/lib/vfs/vfsourdb/vfsourdb.v @@ -62,6 +62,16 @@ pub fn (mut self OurDBVFS) file_write(path string, data []u8) ! { } } +pub fn (mut self OurDBVFS) delete(path string) ! { + // mut impl, rel_path := self.find_vfs(path)! + // return impl.file_read(rel_path) +} + +pub fn (mut self OurDBVFS) link_delete(path string) ! { + // mut impl, rel_path := self.find_vfs(path)! + // return impl.file_read(rel_path) +} + pub fn (mut self OurDBVFS) file_delete(path string) ! { parent_path := os.dir(path) file_name := os.base(path) diff --git a/lib/vfs/vfsourdb/vfsourdb_test.v b/lib/vfs/vfsourdb/vfsourdb_test.v index 72b0ce67..c96e90ec 100644 --- a/lib/vfs/vfsourdb/vfsourdb_test.v +++ b/lib/vfs/vfsourdb/vfsourdb_test.v @@ -31,39 +31,39 @@ fn test_vfsourdb() ! { assert test_dir.get_metadata().file_type == .directory // Test file creation and writing - // mut test_file := vfs.file_create('/test_dir/test.txt')! - // assert test_file.get_metadata().name == 'test.txt' - // assert test_file.get_metadata().file_type == .file + mut test_file := vfs.file_create('/test_dir/test.txt')! + assert test_file.get_metadata().name == 'test.txt' + assert test_file.get_metadata().file_type == .file - // test_content := 'Hello, World!'.bytes() - // vfs.file_write('/test_dir/test.txt', test_content)! + test_content := 'Hello, World!'.bytes() + vfs.file_write('/test_dir/test.txt', test_content)! - // // Test file reading - // read_content := vfs.file_read('/test_dir/test.txt')! - // assert read_content == test_content + // Test file reading + read_content := vfs.file_read('/test_dir/test.txt')! + assert read_content == test_content - // // Test directory listing - // entries := vfs.dir_list('/test_dir')! - // assert entries.len == 1 - // assert entries[0].get_metadata().name == 'test.txt' + // Test directory listing + entries := vfs.dir_list('/test_dir')! + assert entries.len == 1 + assert entries[0].get_metadata().name == 'test.txt' - // // Test exists - // assert vfs.exists('/test_dir') == true - // assert vfs.exists('/test_dir/test.txt') == true - // assert vfs.exists('/nonexistent') == false + // Test exists + assert vfs.exists('/test_dir') == true + assert vfs.exists('/test_dir/test.txt') == true + assert vfs.exists('/nonexistent') == false - // // Test symlink creation and reading - // vfs.link_create('/test_dir/test.txt', '/test_dir/test_link')! - // link_target := vfs.link_read('/test_dir/test_link')! - // assert link_target == '/test_dir/test.txt' + // Test symlink creation and reading + vfs.link_create('/test_dir/test.txt', '/test_dir/test_link')! + link_target := vfs.link_read('/test_dir/test_link')! + assert link_target == '/test_dir/test.txt' - // // Test file deletion - // vfs.file_delete('/test_dir/test.txt')! - // assert vfs.exists('/test_dir/test.txt') == false + // Test file deletion + vfs.file_delete('/test_dir/test.txt')! + assert vfs.exists('/test_dir/test.txt') == false - // // Test directory deletion - // vfs.dir_delete('/test_dir')! - // assert vfs.exists('/test_dir') == false + // Test directory deletion + vfs.dir_delete('/test_dir')! + assert vfs.exists('/test_dir') == false println('Test completed successfully!') } diff --git a/lib/vfs/webdav/app.v b/lib/vfs/webdav/app.v index 6acc8baf..7a59e2a8 100644 --- a/lib/vfs/webdav/app.v +++ b/lib/vfs/webdav/app.v @@ -8,8 +8,8 @@ import freeflowuniverse.herolib.vfs.vfscore @[heap] struct App { vweb.Context - user_db map[string]string @[required] - root_dir pathlib.Path @[vweb_global] + user_db map[string]string @[required] + // root_dir pathlib.Path @[vweb_global] pub mut: // lock_manager LockManager vfs vfscore.VFSImplementation @@ -21,16 +21,16 @@ pub mut: pub struct AppArgs { pub mut: server_port int = 8080 - root_dir string @[required] - user_db map[string]string @[required] - vfs vfscore.VFSImplementation + // root_dir string @[required] + user_db map[string]string @[required] + vfs vfscore.VFSImplementation } pub fn new_app(args AppArgs) !&App { - root_dir := pathlib.get_dir(path: args.root_dir, create: true)! + // root_dir := pathlib.get_dir(path: args.root_dir, create: true)! mut app := &App{ - user_db: args.user_db.clone() - root_dir: root_dir + user_db: args.user_db.clone() + // root_dir: root_dir server_port: args.server_port vfs: args.vfs } diff --git a/lib/vfs/webdav/methods_vfs.v b/lib/vfs/webdav/methods_vfs.v index 9e742392..b4c59463 100644 --- a/lib/vfs/webdav/methods_vfs.v +++ b/lib/vfs/webdav/methods_vfs.v @@ -1,11 +1,11 @@ module webdav -import vweb -import os import freeflowuniverse.herolib.core.pathlib -import encoding.xml import freeflowuniverse.herolib.ui.console +import encoding.xml import net.urllib +import os +import vweb @['/:path...'; get] fn (mut app App) get_file(path string) vweb.Result { @@ -18,18 +18,16 @@ fn (mut app App) get_file(path string) vweb.Result { return app.server_error() } - println('fs_entry: ${fs_entry}') + file_data := app.vfs.file_read(fs_entry.get_path()) or { return app.server_error() } - // file_data := app.vfs.file_read(fs_entry.path) + ext := fs_entry.get_metadata().name.all_after_last('.') + content_type := if v := vweb.mime_types[ext] { + v + } else { + 'text/plain' + } - // ext := fs_entry.get_metadata().name.all_after_last('.') - // content_type := if v := vweb.mime_types[ext] { - // v - // } else { - // 'text/plain' - // } - - // app.set_status(200, 'Ok') - // app.send_response_to_client(content_type, file_data) + app.set_status(200, 'Ok') + app.send_response_to_client(content_type, file_data.str()) return vweb.not_found() // this is for returning a dummy result } diff --git a/lib/vfs/webdav/prop.v b/lib/vfs/webdav/prop.v index f127da7f..6a8a0c91 100644 --- a/lib/vfs/webdav/prop.v +++ b/lib/vfs/webdav/prop.v @@ -7,7 +7,7 @@ import time import vweb fn (mut app App) generate_response_element(path string, depth int) xml.XMLNode { - mut path_ := path.all_after(app.root_dir.path) + mut path_ := path if !path_.starts_with('/') { path_ = '/${path_}' }