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.
This commit is contained in:
Mahmoud Emad
2025-02-18 17:40:37 +02:00
parent 528d594056
commit 7b453962ca
9 changed files with 205 additions and 59 deletions

View File

@@ -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()

View File

@@ -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!')
}

View File

@@ -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(

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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!')
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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_}'
}