- Add webdav.rs module: VfsDavFs, VfsDavFile, VfsDavMetaData - Implement DavFileSystem + Clone for GuardedFileSystem blanket impl - Add clone_boxed to VfsBackend trait (required for Sync) - Update CLI webdav.rs to use VFS instead of SQLite - Add bytes dependency - All 155 tests pass
234 lines
7.1 KiB
Rust
234 lines
7.1 KiB
Rust
use super::open_flags::OpenFlags;
|
||
use super::util;
|
||
use super::{VfsBackend, VfsDirEntry, VfsError, VfsFile, VfsStat};
|
||
use std::fs::{self, File, OpenOptions};
|
||
use std::io::{Read, Seek, SeekFrom, Write};
|
||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||
use std::path::{Path, PathBuf};
|
||
|
||
/// 本地文件系统实现(直接包装 std::fs,不做路径解析)
|
||
/// 路径解析由上层(SftpHandler)负责
|
||
pub struct LocalFs;
|
||
|
||
impl Default for LocalFs {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl LocalFs {
|
||
pub fn new() -> Self {
|
||
Self
|
||
}
|
||
}
|
||
|
||
struct LocalFile {
|
||
file: File,
|
||
}
|
||
|
||
impl VfsFile for LocalFile {
|
||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, VfsError> {
|
||
self.file.read(buf).map_err(|e| VfsError::Io(e.to_string()))
|
||
}
|
||
|
||
fn write(&mut self, buf: &[u8]) -> Result<usize, VfsError> {
|
||
self.file
|
||
.write(buf)
|
||
.map_err(|e| VfsError::Io(e.to_string()))
|
||
}
|
||
|
||
fn seek(&mut self, pos: SeekFrom) -> Result<u64, VfsError> {
|
||
self.file.seek(pos).map_err(|e| VfsError::Io(e.to_string()))
|
||
}
|
||
|
||
fn flush(&mut self) -> Result<(), VfsError> {
|
||
self.file.flush().map_err(|e| VfsError::Io(e.to_string()))
|
||
}
|
||
|
||
fn stat(&mut self) -> Result<VfsStat, VfsError> {
|
||
let meta = self
|
||
.file
|
||
.metadata()
|
||
.map_err(|e| VfsError::Io(e.to_string()))?;
|
||
Ok(util::stat_from_metadata(&meta, false))
|
||
}
|
||
|
||
fn set_len(&mut self, size: u64) -> Result<(), VfsError> {
|
||
self.file
|
||
.set_len(size)
|
||
.map_err(|e| VfsError::Io(e.to_string()))
|
||
}
|
||
}
|
||
|
||
impl VfsBackend for LocalFs {
|
||
fn clone_boxed(&self) -> Box<dyn VfsBackend> {
|
||
Box::new(Self {})
|
||
}
|
||
|
||
fn read_dir(&self, path: &Path) -> Result<Vec<VfsDirEntry>, VfsError> {
|
||
let dir = fs::read_dir(path).map_err(|e| util::map_io_error(path, e))?;
|
||
|
||
let mut entries = Vec::new();
|
||
for entry in dir {
|
||
let entry = entry.map_err(|e| util::map_io_error(path, e))?;
|
||
let name = entry.file_name().to_string_lossy().to_string();
|
||
let file_type = entry.file_type().map_err(|e| util::map_io_error(path, e))?;
|
||
let meta = entry.metadata().map_err(|e| util::map_io_error(path, e))?;
|
||
let stat = util::stat_from_metadata(&meta, file_type.is_symlink());
|
||
let long_name = util::build_long_name(&stat, &name);
|
||
|
||
entries.push(VfsDirEntry {
|
||
name,
|
||
long_name,
|
||
stat,
|
||
});
|
||
}
|
||
|
||
entries.sort_by(|a, b| a.name.cmp(&b.name));
|
||
Ok(entries)
|
||
}
|
||
|
||
fn open_file(&self, path: &Path, flags: &OpenFlags) -> Result<Box<dyn VfsFile>, VfsError> {
|
||
let mut opts = OpenOptions::new();
|
||
opts.read(flags.read);
|
||
opts.write(flags.write);
|
||
opts.append(flags.append);
|
||
opts.create(flags.create);
|
||
opts.truncate(flags.truncate);
|
||
opts.create_new(flags.exclusive);
|
||
|
||
let file = opts.open(path).map_err(|e| util::map_io_error(path, e))?;
|
||
|
||
#[cfg(unix)]
|
||
if flags.create && !flags.exclusive {
|
||
if let Ok(meta) = file.metadata() {
|
||
if flags.mode != 0 && meta.permissions().mode() != flags.mode {
|
||
fs::set_permissions(path, std::fs::Permissions::from_mode(flags.mode)).ok();
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(Box::new(LocalFile { file }))
|
||
}
|
||
|
||
fn stat(&self, path: &Path) -> Result<VfsStat, VfsError> {
|
||
let meta = fs::metadata(path).map_err(|e| util::map_io_error(path, e))?;
|
||
Ok(util::stat_from_metadata(&meta, false))
|
||
}
|
||
|
||
fn lstat(&self, path: &Path) -> Result<VfsStat, VfsError> {
|
||
let meta = fs::symlink_metadata(path).map_err(|e| util::map_io_error(path, e))?;
|
||
let is_symlink = path.is_symlink() || meta.file_type().is_symlink();
|
||
Ok(util::stat_from_metadata(&meta, is_symlink))
|
||
}
|
||
|
||
fn create_dir(&self, path: &Path, mode: u32) -> Result<(), VfsError> {
|
||
fs::create_dir(path).map_err(|e| util::map_io_error(path, e))?;
|
||
|
||
#[cfg(unix)]
|
||
{
|
||
fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
|
||
.map_err(|e| util::map_io_error(path, e))?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn create_dir_all(&self, path: &Path, mode: u32) -> Result<(), VfsError> {
|
||
fs::create_dir_all(path).map_err(|e| util::map_io_error(path, e))?;
|
||
|
||
#[cfg(unix)]
|
||
{
|
||
if mode != 0 {
|
||
fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
|
||
.map_err(|e| util::map_io_error(path, e))?;
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn remove_dir(&self, path: &Path) -> Result<(), VfsError> {
|
||
fs::remove_dir(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))
|
||
}
|
||
|
||
fn rename(&self, from: &Path, to: &Path) -> Result<(), VfsError> {
|
||
fs::rename(from, to).map_err(|e| util::map_io_error(from, e))
|
||
}
|
||
|
||
fn set_stat(&self, path: &Path, stat: &VfsStat) -> Result<(), VfsError> {
|
||
#[cfg(unix)]
|
||
{
|
||
if stat.mode != 0 {
|
||
fs::set_permissions(path, std::fs::Permissions::from_mode(stat.mode))
|
||
.map_err(|e| util::map_io_error(path, e))?;
|
||
}
|
||
}
|
||
|
||
if let (Some(atime), Some(mtime)) = (
|
||
stat.atime.duration_since(std::time::UNIX_EPOCH).ok(),
|
||
stat.mtime.duration_since(std::time::UNIX_EPOCH).ok(),
|
||
) {
|
||
filetime::set_file_times(
|
||
path,
|
||
filetime::FileTime::from_unix_time(atime.as_secs() as i64, 0),
|
||
filetime::FileTime::from_unix_time(mtime.as_secs() as i64, 0),
|
||
)
|
||
.map_err(|e| util::map_io_error(path, e))?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
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)
|
||
}
|
||
|
||
fn create_symlink(&self, target: &Path, link: &Path) -> Result<(), VfsError> {
|
||
#[cfg(unix)]
|
||
{
|
||
std::os::unix::fs::symlink(target, link).map_err(|e| util::map_io_error(link, e))?;
|
||
}
|
||
|
||
#[cfg(not(unix))]
|
||
{
|
||
std::os::windows::fs::symlink_file(target, link)
|
||
.map_err(|e| util::map_io_error(link, e))?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn real_path(&self, path: &Path) -> Result<PathBuf, VfsError> {
|
||
let canonical = path
|
||
.canonicalize()
|
||
.map_err(|e| util::map_io_error(path, e))?;
|
||
Ok(canonical)
|
||
}
|
||
|
||
fn exists(&self, path: &Path) -> bool {
|
||
path.exists()
|
||
}
|
||
|
||
fn hard_link(&self, original: &Path, link: &Path) -> Result<(), VfsError> {
|
||
#[cfg(unix)]
|
||
{
|
||
fs::hard_link(original, link).map_err(|e| util::map_io_error(original, e))?;
|
||
}
|
||
|
||
#[cfg(not(unix))]
|
||
{
|
||
return Err(VfsError::Unsupported(
|
||
"hard_link not supported on non-Unix systems".to_string(),
|
||
));
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|