use crate::vfs::open_flags::OpenFlags; use crate::vfs::{VfsBackend, VfsStat}; use crate::ssh_server::upload_hook::UploadHook; use bytes::{Buf, Bytes}; use dav_server::davpath::DavPath; use dav_server::fs::{ DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsStream, OpenOptions, }; use dav_server::fakels::FakeLs; use futures_util::stream; use std::path::PathBuf; use std::sync::Arc; use std::time::SystemTime; pub struct VfsDavFs { vfs: Box, root: PathBuf, upload_hook: Option>, user_uuid: String, } impl Clone for VfsDavFs { fn clone(&self) -> Self { Self { vfs: self.vfs.clone_boxed(), root: self.root.clone(), upload_hook: self.upload_hook.clone(), user_uuid: self.user_uuid.clone(), } } } impl VfsDavFs { pub fn new( vfs: Box, root: PathBuf, upload_hook: Option>, user_uuid: String, ) -> Box { Box::new(Self { vfs, root, upload_hook, user_uuid, }) } fn resolve_path(&self, path: &DavPath) -> PathBuf { let relative = path.as_pathbuf(); self.root.join(relative) } } #[derive(Debug, Clone)] pub struct VfsDavMetaData { len: u64, is_dir: bool, modified: SystemTime, } impl VfsDavMetaData { pub fn new(len: u64, is_dir: bool) -> Self { Self { len, is_dir, modified: SystemTime::now(), } } pub fn from_stat(stat: &VfsStat) -> Self { Self { len: stat.size, is_dir: stat.is_dir, modified: stat.mtime, } } } impl DavMetaData for VfsDavMetaData { fn len(&self) -> u64 { self.len } fn modified(&self) -> Result { Ok(self.modified) } fn is_dir(&self) -> bool { self.is_dir } } #[derive(Debug, Clone)] pub struct VfsDavDirEntry { name: String, meta: VfsDavMetaData, } impl VfsDavDirEntry { pub fn new(name: String, meta: VfsDavMetaData) -> Self { Self { name, meta } } } impl DavDirEntry for VfsDavDirEntry { fn name(&self) -> Vec { self.name.as_bytes().to_vec() } fn metadata(&self) -> FsFuture<'_, Box> { Box::pin(std::future::ready(Ok(Box::new(self.meta.clone()) as Box))) } } pub struct VfsDavFile { data: Vec, position: u64, path: Option, upload_hook: Option>, user_uuid: String, is_write: bool, } impl std::fmt::Debug for VfsDavFile { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("VfsDavFile") .field("is_write", &self.is_write) .field("path", &self.path) .finish() } } impl VfsDavFile { pub fn new_read(data: Vec) -> Self { Self { data, position: 0, path: None, upload_hook: None, user_uuid: String::new(), is_write: false, } } pub fn new_write( path: PathBuf, upload_hook: Option>, user_uuid: String, ) -> Self { Self { data: Vec::new(), position: 0, path: Some(path), upload_hook, user_uuid, is_write: true, } } } impl DavFile for VfsDavFile { fn metadata(&'_ mut self) -> FsFuture<'_, Box> { let len = self.data.len() as u64; Box::pin(std::future::ready(Ok( Box::new(VfsDavMetaData::new(len, false)) as Box, ))) } fn write_buf(&'_ mut self, mut buf: Box) -> FsFuture<'_, ()> { while buf.has_remaining() { let chunk = buf.chunk(); self.data.extend_from_slice(chunk); buf.advance(chunk.len()); } Box::pin(std::future::ready(Ok(()))) } fn write_bytes(&'_ mut self, buf: Bytes) -> FsFuture<'_, ()> { self.data.extend_from_slice(&buf); Box::pin(std::future::ready(Ok(()))) } fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, Bytes> { let start = self.position as usize; let end = std::cmp::min(start + count, self.data.len()); if start >= self.data.len() { Box::pin(std::future::ready(Ok(Bytes::new()))) } else { let bytes = Bytes::copy_from_slice(&self.data[start..end]); self.position = end as u64; Box::pin(std::future::ready(Ok(bytes))) } } fn seek(&'_ mut self, pos: std::io::SeekFrom) -> FsFuture<'_, u64> { let new_pos = match pos { std::io::SeekFrom::Start(offset) => offset, std::io::SeekFrom::Current(offset) => { let current = self.position as i64; (current + offset) as u64 } std::io::SeekFrom::End(offset) => { let end = self.data.len() as i64; (end + offset) as u64 } }; self.position = new_pos; Box::pin(std::future::ready(Ok(new_pos))) } fn flush(&'_ mut self) -> FsFuture<'_, ()> { if self.is_write { if let Some(path) = &self.path { if let Err(_e) = std::fs::write(path, &self.data) { return Box::pin(std::future::ready(Err(FsError::NotImplemented))); } if let Some(hook) = &self.upload_hook { if let Err(e) = hook.trigger(path, &self.user_uuid) { log::warn!("Upload hook failed for {:?}: {}", path, e); } } } } Box::pin(std::future::ready(Ok(()))) } } impl DavFileSystem for VfsDavFs { fn open<'a>(&'a self, path: &'a DavPath, options: OpenOptions) -> FsFuture<'a, Box> { let full_path = self.resolve_path(path); if options.write { let file = VfsDavFile::new_write( full_path, self.upload_hook.clone(), self.user_uuid.clone(), ); Box::pin(std::future::ready(Ok(Box::new(file) as Box))) } else { let flags = OpenFlags::new().read(); match self.vfs.open_file(&full_path, &flags) { Ok(mut vfs_file) => { let mut data = Vec::new(); loop { let mut buf = vec![0u8; 8192]; match vfs_file.read(&mut buf) { Ok(0) => break, Ok(n) => data.extend_from_slice(&buf[..n]), Err(_) => return Box::pin(std::future::ready(Err(FsError::NotFound))), } } let file = VfsDavFile::new_read(data); Box::pin(std::future::ready(Ok(Box::new(file) as Box))) } Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))), } } } fn read_dir<'a>( &'a self, path: &'a DavPath, _meta: dav_server::fs::ReadDirMeta, ) -> FsFuture<'a, FsStream>> { let full_path = self.resolve_path(path); match self.vfs.read_dir(&full_path) { Ok(entries) => { let results: Vec> = entries .into_iter() .map(|e| { let meta = VfsDavMetaData::from_stat(&e.stat); Box::new(VfsDavDirEntry::new(e.name, meta)) as Box }) .collect(); let stream = stream::iter(results.into_iter().map(Ok)); Box::pin(std::future::ready(Ok(Box::pin(stream) as FsStream>))) } Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))), } } fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box> { let full_path = self.resolve_path(path); match self.vfs.stat(&full_path) { Ok(stat) => { let meta = VfsDavMetaData::from_stat(&stat); Box::pin(std::future::ready(Ok(Box::new(meta) as Box))) } Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))), } } } pub fn create_webdav_handler( vfs: Box, root: PathBuf, upload_hook: Option>, user_uuid: String, ) -> dav_server::DavHandler { let dav_fs = VfsDavFs::new(vfs, root, upload_hook, user_uuid); dav_server::DavHandler::builder() .filesystem(dav_fs) .locksystem(FakeLs::new()) .strip_prefix("/webdav") .build_handler() }