- Remove unused imports in server.rs (Body, HeaderValue, RwLock) - Remove unused imports in forward_acl.rs (tests still need Ipv4Addr) - Remove unused imports in host_key.rs (Read, Write) - Remove unused imports in kex_exchange.rs (HostKeyType) - Remove unused imports in known_hosts.rs (tests need Ipv4Addr) - Remove unused imports in multiplex.rs (Arc) - Auto-fix other unused imports via clippy --fix Tests: 303 passed, 0 failed (4 new tests added)
255 lines
8.5 KiB
Rust
255 lines
8.5 KiB
Rust
use std::path::{Path, PathBuf};
|
||
use std::time::SystemTime;
|
||
use std::pin::Pin;
|
||
use std::future::Future;
|
||
use std::io::{self, SeekFrom};
|
||
|
||
use tokio::fs;
|
||
use tokio::io::{AsyncReadExt, AsyncWriteExt, AsyncSeekExt};
|
||
|
||
use super::{VfsError, VfsStat, VfsDirEntry, open_flags::OpenFlags};
|
||
|
||
/// Async VFS 文件實現(使用 tokio::fs)
|
||
pub struct AsyncLocalFile {
|
||
file: fs::File,
|
||
path: PathBuf,
|
||
is_write: bool,
|
||
}
|
||
|
||
impl AsyncLocalFile {
|
||
pub fn new(file: fs::File, path: PathBuf, is_write: bool) -> Self {
|
||
Self { file, path, is_write }
|
||
}
|
||
}
|
||
|
||
impl super::AsyncVfsFile for AsyncLocalFile {
|
||
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Pin<Box<dyn Future<Output = Result<usize, VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
self.file.read(buf).await
|
||
.map_err(|e| VfsError::Io(e.to_string()))
|
||
})
|
||
}
|
||
|
||
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Pin<Box<dyn Future<Output = Result<usize, VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
self.file.write(buf).await
|
||
.map_err(|e| VfsError::Io(e.to_string()))
|
||
})
|
||
}
|
||
|
||
fn seek<'a>(&'a mut self, pos: SeekFrom) -> Pin<Box<dyn Future<Output = Result<u64, VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
self.file.seek(pos).await
|
||
.map_err(|e| VfsError::Io(e.to_string()))
|
||
})
|
||
}
|
||
|
||
fn flush<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
self.file.flush().await
|
||
.map_err(|e| VfsError::Io(e.to_string()))
|
||
})
|
||
}
|
||
}
|
||
|
||
/// Async VFS 后端實現(使用 tokio::fs)
|
||
pub struct AsyncLocalFs {
|
||
root: PathBuf,
|
||
}
|
||
|
||
impl Default for AsyncLocalFs {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl AsyncLocalFs {
|
||
pub fn new() -> Self {
|
||
Self { root: PathBuf::new() }
|
||
}
|
||
|
||
pub fn with_root(root: PathBuf) -> Self {
|
||
Self { root }
|
||
}
|
||
|
||
fn map_io_error(path: &Path, e: io::Error) -> VfsError {
|
||
match e.kind() {
|
||
io::ErrorKind::NotFound => VfsError::NotFound(path.to_string_lossy().to_string()),
|
||
io::ErrorKind::PermissionDenied => VfsError::PermissionDenied(path.to_string_lossy().to_string()),
|
||
io::ErrorKind::AlreadyExists => VfsError::AlreadyExists(path.to_string_lossy().to_string()),
|
||
_ => VfsError::Io(e.to_string()),
|
||
}
|
||
}
|
||
|
||
fn stat_from_metadata(meta: &std::fs::Metadata) -> VfsStat {
|
||
VfsStat {
|
||
size: meta.len(),
|
||
mode: 0o644,
|
||
uid: 0,
|
||
gid: 0,
|
||
atime: meta.accessed().unwrap_or(SystemTime::UNIX_EPOCH),
|
||
mtime: meta.modified().unwrap_or(SystemTime::UNIX_EPOCH),
|
||
is_dir: meta.is_dir(),
|
||
is_symlink: meta.file_type().is_symlink(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Clone for AsyncLocalFs {
|
||
fn clone(&self) -> Self {
|
||
Self { root: self.root.clone() }
|
||
}
|
||
}
|
||
|
||
impl super::AsyncVfsBackend for AsyncLocalFs {
|
||
fn clone_boxed(&self) -> Box<dyn super::AsyncVfsBackend> {
|
||
Box::new(self.clone())
|
||
}
|
||
|
||
fn read_dir<'a>(&'a self, path: &'a Path) -> Pin<Box<dyn Future<Output = Result<Vec<VfsDirEntry>, VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
let mut entries = Vec::new();
|
||
let mut dir = fs::read_dir(path).await
|
||
.map_err(|e| Self::map_io_error(path, e))?;
|
||
|
||
while let Some(entry) = dir.next_entry().await.map_err(|e| Self::map_io_error(path, e))? {
|
||
let name = entry.file_name().to_string_lossy().to_string();
|
||
let long_name = name.clone();
|
||
let meta = entry.metadata().await.map_err(|e| Self::map_io_error(path, e))?;
|
||
let stat = Self::stat_from_metadata(&meta);
|
||
entries.push(VfsDirEntry { name, long_name, stat });
|
||
}
|
||
|
||
Ok(entries)
|
||
})
|
||
}
|
||
|
||
fn open_file<'a>(&'a self, path: &'a Path, flags: &'a OpenFlags) -> Pin<Box<dyn Future<Output = Result<Box<dyn super::AsyncVfsFile>, VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
let mut options = fs::OpenOptions::new();
|
||
|
||
if flags.read {
|
||
options.read(true);
|
||
}
|
||
if flags.write {
|
||
options.write(true);
|
||
}
|
||
if flags.create {
|
||
options.create(true);
|
||
}
|
||
if flags.truncate {
|
||
options.truncate(true);
|
||
}
|
||
if flags.append {
|
||
options.append(true);
|
||
}
|
||
|
||
let file = options.open(path).await
|
||
.map_err(|e| Self::map_io_error(path, e))?;
|
||
|
||
Ok(Box::new(AsyncLocalFile::new(file, path.to_path_buf(), flags.write)) as Box<dyn super::AsyncVfsFile>)
|
||
})
|
||
}
|
||
|
||
fn stat<'a>(&'a self, path: &'a Path) -> Pin<Box<dyn Future<Output = Result<VfsStat, VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
let meta = fs::metadata(path).await
|
||
.map_err(|e| Self::map_io_error(path, e))?;
|
||
Ok(Self::stat_from_metadata(&meta))
|
||
})
|
||
}
|
||
|
||
fn create_dir<'a>(&'a self, path: &'a Path, _mode: u32) -> Pin<Box<dyn Future<Output = Result<(), VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
fs::create_dir(path).await
|
||
.map_err(|e| Self::map_io_error(path, e))?;
|
||
Ok(())
|
||
})
|
||
}
|
||
|
||
fn remove_dir<'a>(&'a self, path: &'a Path) -> Pin<Box<dyn Future<Output = Result<(), VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
fs::remove_dir(path).await
|
||
.map_err(|e| Self::map_io_error(path, e))?;
|
||
Ok(())
|
||
})
|
||
}
|
||
|
||
fn remove_file<'a>(&'a self, path: &'a Path) -> Pin<Box<dyn Future<Output = Result<(), VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
fs::remove_file(path).await
|
||
.map_err(|e| Self::map_io_error(path, e))?;
|
||
Ok(())
|
||
})
|
||
}
|
||
|
||
fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> Pin<Box<dyn Future<Output = Result<(), VfsError>> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
fs::rename(from, to).await
|
||
.map_err(|e| Self::map_io_error(from, e))?;
|
||
Ok(())
|
||
})
|
||
}
|
||
|
||
fn exists<'a>(&'a self, path: &'a Path) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
|
||
Box::pin(async move {
|
||
fs::metadata(path).await.is_ok()
|
||
})
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use tempfile::TempDir;
|
||
use crate::vfs::AsyncVfsBackend;
|
||
use crate::vfs::AsyncVfsFile;
|
||
|
||
#[tokio::test]
|
||
async fn test_async_read_dir() {
|
||
let tmp = TempDir::new().unwrap();
|
||
fs::create_dir(tmp.path().join("subdir")).await.unwrap();
|
||
fs::write(tmp.path().join("test.txt"), "content").await.unwrap();
|
||
|
||
let vfs = AsyncLocalFs::with_root(tmp.path().to_path_buf());
|
||
let entries = AsyncVfsBackend::read_dir(&vfs, tmp.path()).await.unwrap();
|
||
|
||
assert_eq!(entries.len(), 2);
|
||
assert!(entries.iter().any(|e| e.name == "subdir" && e.stat.is_dir));
|
||
assert!(entries.iter().any(|e| e.name == "test.txt" && !e.stat.is_dir));
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_async_open_read() {
|
||
let tmp = TempDir::new().unwrap();
|
||
fs::write(tmp.path().join("test.txt"), "hello world").await.unwrap();
|
||
|
||
let vfs = AsyncLocalFs::with_root(tmp.path().to_path_buf());
|
||
let flags = OpenFlags::new().read();
|
||
let mut file = AsyncVfsBackend::open_file(&vfs, &tmp.path().join("test.txt"), &flags).await.unwrap();
|
||
|
||
let mut buf = [0u8; 11];
|
||
let n = AsyncVfsFile::read(&mut *file, &mut buf).await.unwrap();
|
||
assert_eq!(n, 11);
|
||
assert_eq!(&buf, b"hello world");
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_async_create_dir() {
|
||
let tmp = TempDir::new().unwrap();
|
||
let vfs = AsyncLocalFs::with_root(tmp.path().to_path_buf());
|
||
|
||
AsyncVfsBackend::create_dir(&vfs, &tmp.path().join("newdir"), 0o755).await.unwrap();
|
||
assert!(AsyncVfsBackend::exists(&vfs, &tmp.path().join("newdir")).await);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_async_remove_file() {
|
||
let tmp = TempDir::new().unwrap();
|
||
fs::write(tmp.path().join("test.txt"), "content").await.unwrap();
|
||
|
||
let vfs = AsyncLocalFs::with_root(tmp.path().to_path_buf());
|
||
AsyncVfsBackend::remove_file(&vfs, &tmp.path().join("test.txt")).await.unwrap();
|
||
assert!(!AsyncVfsBackend::exists(&vfs, &tmp.path().join("test.txt")).await);
|
||
}
|
||
} |