WebDAV improvements: flush fix, RwLock recovery, expired lock cleanup, atomic set_times
P0 fixes: - flush(): add flushed flag, proper error logging, Drop warning for data loss - props_data RwLock: replace unwrap() with try_read/try_write recovery - PersistedLs: add is_expired() + cleanup_expired_locks() helper P1 improvements: - Props persistence via VFS (load_props/save_props/patch_props) - COPY/MOVE sync dead props (copy on COPY, move key on rename) - Atomic set_atime/set_mtime via filetime crate (no race condition) New files: - webdav_locks.rs: PersistedLs with lock persistence + expiry cleanup Tests: 288 passed, 0 failed
This commit is contained in:
@@ -153,6 +153,10 @@ impl VfsBackend for LocalFs {
|
||||
fs::remove_dir(path).map_err(|e| util::map_io_error(path, e))
|
||||
}
|
||||
|
||||
fn remove_dir_all(&self, path: &Path) -> Result<(), VfsError> {
|
||||
fs::remove_dir_all(path).map_err(|e| util::map_io_error(path, e))
|
||||
}
|
||||
|
||||
fn remove_file(&self, path: &Path) -> Result<(), VfsError> {
|
||||
fs::remove_file(path).map_err(|e| util::map_io_error(path, e))
|
||||
}
|
||||
@@ -185,6 +189,39 @@ impl VfsBackend for LocalFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_times(&self, path: &Path, atime: SystemTime, mtime: SystemTime) -> Result<(), VfsError> {
|
||||
let at = atime.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_err(|_| VfsError::Io("atime before UNIX_EPOCH".to_string()))?;
|
||||
let mt = mtime.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_err(|_| VfsError::Io("mtime before UNIX_EPOCH".to_string()))?;
|
||||
filetime::set_file_times(
|
||||
path,
|
||||
filetime::FileTime::from_unix_time(at.as_secs() as i64, at.subsec_nanos() as u32),
|
||||
filetime::FileTime::from_unix_time(mt.as_secs() as i64, mt.subsec_nanos() as u32),
|
||||
)
|
||||
.map_err(|e| util::map_io_error(path, e))
|
||||
}
|
||||
|
||||
fn set_atime(&self, path: &Path, atime: SystemTime) -> Result<(), VfsError> {
|
||||
let at = atime.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_err(|_| VfsError::Io("atime before UNIX_EPOCH".to_string()))?;
|
||||
filetime::set_file_atime(
|
||||
path,
|
||||
filetime::FileTime::from_unix_time(at.as_secs() as i64, at.subsec_nanos() as u32),
|
||||
)
|
||||
.map_err(|e| util::map_io_error(path, e))
|
||||
}
|
||||
|
||||
fn set_mtime(&self, path: &Path, mtime: SystemTime) -> Result<(), VfsError> {
|
||||
let mt = mtime.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_err(|_| VfsError::Io("mtime before UNIX_EPOCH".to_string()))?;
|
||||
filetime::set_file_mtime(
|
||||
path,
|
||||
filetime::FileTime::from_unix_time(mt.as_secs() as i64, mt.subsec_nanos() as u32),
|
||||
)
|
||||
.map_err(|e| util::map_io_error(path, e))
|
||||
}
|
||||
|
||||
fn read_link(&self, path: &Path) -> Result<PathBuf, VfsError> {
|
||||
let target = fs::read_link(path).map_err(|e| util::map_io_error(path, e))?;
|
||||
Ok(target)
|
||||
@@ -232,6 +269,15 @@ impl VfsBackend for LocalFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy(&self, from: &Path, to: &Path) -> Result<(), VfsError> {
|
||||
// Check if source is a directory
|
||||
if from.is_dir() {
|
||||
return copy_dir_recursive_impl(from, to);
|
||||
}
|
||||
fs::copy(from, to).map_err(|e| util::map_io_error(from, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ===== Snapshot support =====
|
||||
|
||||
fn create_snapshot(&self, path: &Path, name: &str) -> Result<(), VfsError> {
|
||||
@@ -240,7 +286,7 @@ impl VfsBackend for LocalFs {
|
||||
|
||||
let snapshot_path = snapshot_dir.join(name);
|
||||
if path.is_dir() {
|
||||
self.copy_dir_recursive(path, &snapshot_path)?;
|
||||
copy_dir_recursive_impl(path, &snapshot_path)?;
|
||||
} else {
|
||||
fs::copy(path, &snapshot_path).map_err(|e| util::map_io_error(path, e))?;
|
||||
}
|
||||
@@ -311,7 +357,7 @@ impl VfsBackend for LocalFs {
|
||||
}
|
||||
|
||||
if snapshot_path.is_dir() {
|
||||
self.copy_dir_recursive(&snapshot_path, path)?;
|
||||
copy_dir_recursive_impl(&snapshot_path, path)?;
|
||||
} else {
|
||||
fs::copy(&snapshot_path, path).map_err(|e| util::map_io_error(&snapshot_path, e))?;
|
||||
}
|
||||
@@ -540,24 +586,6 @@ impl VfsBackend for LocalFs {
|
||||
}
|
||||
|
||||
impl LocalFs {
|
||||
fn copy_dir_recursive(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
|
||||
fs::create_dir_all(dst).map_err(|e| util::map_io_error(dst, e))?;
|
||||
|
||||
for entry in fs::read_dir(src).map_err(|e| util::map_io_error(src, e))? {
|
||||
let entry = entry.map_err(|e| VfsError::Io(e.to_string()))?;
|
||||
let src_path = entry.path();
|
||||
let dst_path = dst.join(entry.file_name());
|
||||
|
||||
if src_path.is_dir() {
|
||||
self.copy_dir_recursive(&src_path, &dst_path)?;
|
||||
} else {
|
||||
fs::copy(&src_path, &dst_path).map_err(|e| util::map_io_error(&src_path, e))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_size(&self, path: &Path) -> Result<u64, VfsError> {
|
||||
if path.is_dir() {
|
||||
let mut total = 0;
|
||||
@@ -772,6 +800,22 @@ impl VfsAclMeta {
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursive directory copy helper (used by VfsBackend::copy)
|
||||
fn copy_dir_recursive_impl(src: &Path, dst: &Path) -> Result<(), VfsError> {
|
||||
fs::create_dir_all(dst).map_err(|e| util::map_io_error(dst, e))?;
|
||||
for entry in fs::read_dir(src).map_err(|e| util::map_io_error(src, e))? {
|
||||
let entry = entry.map_err(|e| util::map_io_error(src, e))?;
|
||||
let src_entry = entry.path();
|
||||
let dst_entry = dst.join(entry.file_name());
|
||||
if src_entry.is_dir() {
|
||||
copy_dir_recursive_impl(&src_entry, &dst_entry)?;
|
||||
} else {
|
||||
fs::copy(&src_entry, &dst_entry).map_err(|e| util::map_io_error(&src_entry, e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -148,6 +148,21 @@ pub trait VfsBackend: Send + Sync {
|
||||
/// 删除空目录
|
||||
fn remove_dir(&self, path: &Path) -> Result<(), VfsError>;
|
||||
|
||||
/// 递归删除目录及其所有内容
|
||||
fn remove_dir_all(&self, path: &Path) -> Result<(), VfsError> {
|
||||
// Default: read entries and remove one by one
|
||||
let entries = self.read_dir(path)?;
|
||||
for entry in entries {
|
||||
let child = path.join(&entry.name);
|
||||
if entry.stat.is_dir {
|
||||
self.remove_dir_all(&child)?;
|
||||
} else {
|
||||
self.remove_file(&child)?;
|
||||
}
|
||||
}
|
||||
self.remove_dir(path)
|
||||
}
|
||||
|
||||
/// 删除文件
|
||||
fn remove_file(&self, path: &Path) -> Result<(), VfsError>;
|
||||
|
||||
@@ -157,6 +172,28 @@ pub trait VfsBackend: Send + Sync {
|
||||
/// 设置文件属性
|
||||
fn set_stat(&self, path: &Path, stat: &VfsStat) -> Result<(), VfsError>;
|
||||
|
||||
/// 原子性设置 atime 和 mtime(默认实现调用 stat + set_stat,有 race condition)
|
||||
fn set_times(&self, path: &Path, atime: SystemTime, mtime: SystemTime) -> Result<(), VfsError> {
|
||||
let mut stat = self.stat(path)?;
|
||||
stat.atime = atime;
|
||||
stat.mtime = mtime;
|
||||
self.set_stat(path, &stat)
|
||||
}
|
||||
|
||||
/// 原子性设置 atime(默认实现调用 stat + set_stat,有 race condition)
|
||||
fn set_atime(&self, path: &Path, atime: SystemTime) -> Result<(), VfsError> {
|
||||
let mut stat = self.stat(path)?;
|
||||
stat.atime = atime;
|
||||
self.set_stat(path, &stat)
|
||||
}
|
||||
|
||||
/// 原子性设置 mtime(默认实现调用 stat + set_stat,有 race condition)
|
||||
fn set_mtime(&self, path: &Path, mtime: SystemTime) -> Result<(), VfsError> {
|
||||
let mut stat = self.stat(path)?;
|
||||
stat.mtime = mtime;
|
||||
self.set_stat(path, &stat)
|
||||
}
|
||||
|
||||
/// 读取符号链接目标
|
||||
fn read_link(&self, path: &Path) -> Result<PathBuf, VfsError>;
|
||||
|
||||
@@ -172,6 +209,24 @@ pub trait VfsBackend: Send + Sync {
|
||||
/// 创建硬链接
|
||||
fn hard_link(&self, original: &Path, link: &Path) -> Result<(), VfsError>;
|
||||
|
||||
/// 复制文件(高效实现,fallback 到 read+write)
|
||||
fn copy(&self, from: &Path, to: &Path) -> Result<(), VfsError> {
|
||||
let flags = open_flags::OpenFlags::new().read();
|
||||
let mut src = self.open_file(from, &flags)?;
|
||||
let write_flags = open_flags::OpenFlags::new().write().create().truncate().mode(0o644);
|
||||
let mut dst = self.open_file(to, &write_flags)?;
|
||||
let mut buf = vec![0u8; 65536];
|
||||
loop {
|
||||
match src.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => dst.write_all(&buf[..n])?,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
dst.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ===== Snapshot support (ZFS-style) =====
|
||||
|
||||
/// 创建快照
|
||||
|
||||
Reference in New Issue
Block a user