VFS/DataProvider/Config refactoring + SSH public key authentication
Phase 1-6 of refactoring plan: - VFS abstraction (VfsBackend trait + LocalFs + OpenFlags builder) - DataProvider trait (SqliteProvider + PgProvider, SFTPGo-compatible) - Config refactoring (AppConfig unified sections, env overrides) - SSH handlers (sftp/scp/rsync) migrated to VFS + DataProvider - SSH public key authentication (Ed25519 signature verification) - SSH stderr → CHANNEL_EXTENDED_DATA support - Web auth uses DataProvider instead of direct SQL - User home directory from provider (per-user isolation) - PostgreSQL auth provider for SFTPGo compatibility
This commit is contained in:
105
markbase-core/src/vfs/util.rs
Normal file
105
markbase-core/src/vfs/util.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user