- Fix trailing whitespace in kex.rs and s3.rs - Add missing KexProposal import in kex_complete.rs - Auto-fix clippy warnings across all crates - All 153 tests pass
102 lines
3.1 KiB
Rust
102 lines
3.1 KiB
Rust
use super::{VfsError, VfsStat};
|
||
use chrono::Datelike;
|
||
use std::os::unix::fs::PermissionsExt;
|
||
use std::path::Path;
|
||
|
||
/// 从 std::io::ErrorKind 映射 VfsError
|
||
pub fn map_io_error(path: &Path, e: std::io::Error) -> VfsError {
|
||
match e.kind() {
|
||
std::io::ErrorKind::NotFound => VfsError::NotFound(path.display().to_string()),
|
||
std::io::ErrorKind::PermissionDenied => {
|
||
VfsError::PermissionDenied(path.display().to_string())
|
||
}
|
||
std::io::ErrorKind::AlreadyExists => VfsError::AlreadyExists(path.display().to_string()),
|
||
std::io::ErrorKind::DirectoryNotEmpty => VfsError::NotEmpty(path.display().to_string()),
|
||
std::io::ErrorKind::NotADirectory => VfsError::NotADirectory(path.display().to_string()),
|
||
std::io::ErrorKind::IsADirectory => VfsError::IsADirectory(path.display().to_string()),
|
||
std::io::ErrorKind::UnexpectedEof => VfsError::UnexpectedEof,
|
||
other => VfsError::Io(format!("{}: {}", other, path.display())),
|
||
}
|
||
}
|
||
|
||
/// 从 std::fs::Metadata 构建 VfsStat
|
||
pub fn stat_from_metadata(meta: &std::fs::Metadata, is_symlink: bool) -> VfsStat {
|
||
#[cfg(unix)]
|
||
use std::os::unix::fs::MetadataExt;
|
||
|
||
let mut stat = VfsStat::new();
|
||
stat.size = meta.len();
|
||
stat.is_dir = meta.is_dir();
|
||
stat.is_symlink = is_symlink;
|
||
|
||
#[cfg(unix)]
|
||
{
|
||
stat.mode = meta.permissions().mode();
|
||
stat.uid = meta.uid();
|
||
stat.gid = meta.gid();
|
||
}
|
||
|
||
#[cfg(not(unix))]
|
||
{
|
||
stat.mode = if meta.is_dir() { 0o40755 } else { 0o100644 };
|
||
}
|
||
|
||
if let Ok(t) = meta.accessed() {
|
||
stat.atime = t;
|
||
}
|
||
if let Ok(t) = meta.modified() {
|
||
stat.mtime = t;
|
||
}
|
||
|
||
stat
|
||
}
|
||
|
||
/// 构建目录条目的 long_name(类似 ls -l 格式)
|
||
pub fn build_long_name(stat: &VfsStat, name: &str) -> String {
|
||
let file_type = if stat.is_dir { 'd' } else { '-' };
|
||
let perms = format_permissions(stat.mode & 0o777);
|
||
let link_count = if stat.is_dir { 3 } else { 1 };
|
||
let size = stat.size;
|
||
let mtime = match stat.mtime.duration_since(std::time::UNIX_EPOCH) {
|
||
Ok(d) => {
|
||
let secs = d.as_secs();
|
||
format_timestamp(secs)
|
||
}
|
||
Err(_) => "Jan 1 1970".to_string(),
|
||
};
|
||
|
||
format!(
|
||
"{}{} {} {} {} {} {} {}",
|
||
file_type, perms, link_count, stat.uid, stat.gid, size, mtime, name
|
||
)
|
||
}
|
||
|
||
fn format_permissions(mode: u32) -> String {
|
||
let rwx = |n: u32| -> String {
|
||
let r = if n & 4 != 0 { 'r' } else { '-' };
|
||
let w = if n & 2 != 0 { 'w' } else { '-' };
|
||
let x = if n & 1 != 0 { 'x' } else { '-' };
|
||
format!("{}{}{}", r, w, x)
|
||
};
|
||
|
||
format!(
|
||
"{}{}{}",
|
||
rwx((mode >> 6) & 7),
|
||
rwx((mode >> 3) & 7),
|
||
rwx(mode & 7)
|
||
)
|
||
}
|
||
|
||
fn format_timestamp(secs: u64) -> String {
|
||
let datetime = match chrono::DateTime::from_timestamp(secs as i64, 0) {
|
||
Some(dt) => dt,
|
||
None => return "Jan 1 1970".to_string(),
|
||
};
|
||
let now = chrono::Utc::now();
|
||
if datetime.year() == now.year() {
|
||
datetime.format("%b %e %H:%M").to_string()
|
||
} else {
|
||
datetime.format("%b %e %Y").to_string()
|
||
}
|
||
}
|