MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能: - ✅ Categories/Series双视图管理(category_view.rs + import_markdown.rs) - ✅ FUSE Multi-Volume支持(tree_type参数) - ✅ SSH/SFTP/SCP/rsync协议完整实现(4042行) - ✅ NFS/SMB Module Phase 1-3完成 - ✅ Archive Module Phase 1-4完成(2916行) - ✅ Download Center API完整实现 - ✅ S3兼容API实现(560行) Git配置修正: - ✅ 删除错误origin(gitea.momentry.ddns.net) - ✅ 删除m5max128(指向机器名) - ✅ 设置origin = m5max128gitea.momentry.ddns.net/admin/markbase - ✅ 设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase 数据清理: - ✅ 删除38个临时SQLite(保留accusys.sqlite、demo.sqlite) - ✅ 删除.bak、test_*.bin、调试脚本等临时文件 - ✅ 删除临时目录(build/、download files/、raid_test/等) - ✅ 更新.gitignore排除临时文件 架构优化: - 52个文件修改,2434行新增,4739行删除 - Workspace成员整合(16个crate) - 数据库状态:accusys.sqlite保留(主demo测试) 远程同步: - ✅ 准备推送到m5max128gitea(远程Gitea) - ✅ 准备推送到m4minigitea(本地Gitea)
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
use anyhow::Result;
|
||||
use fuse::{
|
||||
FileAttr, FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, Request,
|
||||
};
|
||||
use libc::{EIO, ENOENT};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, SeekFrom};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
const TTL: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
const READ_CHUNK_SIZE: usize = 524288; // 512KB for maximum throughput
|
||||
|
||||
const CACHE_SIZE: usize = 1000;
|
||||
|
||||
pub struct MarkBaseFs {
|
||||
db: Arc<DbManager>,
|
||||
cache: Arc<ThreadSafeCache>,
|
||||
inode_map: HashMap<u64, String>,
|
||||
path_cache: HashMap<String, u64>,
|
||||
next_inode: u64,
|
||||
}
|
||||
|
||||
impl MarkBaseFs {
|
||||
pub fn new(db_path: &str, tree_type: &str) -> Result<Self> {
|
||||
let db = DbManager::open(db_path, tree_type)?;
|
||||
let cache = ThreadSafeCache::new();
|
||||
|
||||
let count = db.preload_files(&cache, CACHE_SIZE)?;
|
||||
println!("Pre-cached {} files for tree_type: {}", count, tree_type);
|
||||
|
||||
Ok(Self {
|
||||
db: Arc::new(db),
|
||||
cache: Arc::new(cache),
|
||||
inode_map: HashMap::new(),
|
||||
path_cache: HashMap::new(),
|
||||
next_inode: 2,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_node_id_by_inode(&self, ino: u64) -> Option<String> {
|
||||
self.inode_map.get(&ino).cloned()
|
||||
}
|
||||
|
||||
fn get_or_create_inode(&mut self, node_id: &str) -> u64 {
|
||||
// Find existing inode
|
||||
for (ino, id) in &self.inode_map {
|
||||
if id == node_id {
|
||||
return *ino;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new inode
|
||||
let ino = self.next_inode;
|
||||
self.next_inode += 1;
|
||||
self.inode_map.insert(ino, node_id.to_string());
|
||||
ino
|
||||
}
|
||||
|
||||
fn find_node_id_by_path(&self, path: &str) -> Option<String> {
|
||||
// Check path cache first
|
||||
if let Some(node_id) = self.cache.lookup_path(path) {
|
||||
return Some(node_id);
|
||||
}
|
||||
|
||||
// Query from database
|
||||
match self.db.find_node_id(path) {
|
||||
Ok(Some(node_id)) => {
|
||||
self.cache.insert_path(path, &node_id);
|
||||
Some(node_id)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_path(&self, node_id: &str) -> Option<String> {
|
||||
// Check cache first
|
||||
if let Some((path, _)) = self.cache.lookup_file(node_id) {
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
// Query from database
|
||||
match self.db.get_file_path(node_id) {
|
||||
Ok(Some(path)) => {
|
||||
self.cache.insert_file(node_id, &path, 0);
|
||||
Some(path)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_file_attr(ino: u64, node_type: &str, file_size: u64) -> FileAttr {
|
||||
FileAttr {
|
||||
ino,
|
||||
size: file_size,
|
||||
blocks: (file_size + 511) / 512,
|
||||
atime: SystemTime::now(),
|
||||
mtime: SystemTime::now(),
|
||||
ctime: SystemTime::now(),
|
||||
crtime: SystemTime::now(),
|
||||
kind: if node_type == "folder" {
|
||||
FileType::Directory
|
||||
} else {
|
||||
FileType::RegularFile
|
||||
},
|
||||
perm: if node_type == "folder" { 0o755 } else { 0o644 },
|
||||
nlink: if node_type == "folder" { 2 } else { 1 },
|
||||
uid: 501, // default user
|
||||
gid: 20, // default group
|
||||
rdev: 0,
|
||||
flags: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Filesystem for MarkBaseFs {
|
||||
fn lookup(&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
|
||||
let parent_path = if parent == 1 { "/" } else { "" };
|
||||
let full_path = format!("{}/{}", parent_path, name.to_string_lossy());
|
||||
|
||||
let node_id = self.find_node_id_by_path(&full_path);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let info = self.db.get_node_info(&id).ok();
|
||||
|
||||
match info {
|
||||
Some((node_type, file_size)) => {
|
||||
let ino = self.get_or_create_inode(&id);
|
||||
let attr = self.make_file_attr(ino, &node_type, file_size);
|
||||
|
||||
reply.entry(&TTL, &attr, 0);
|
||||
}
|
||||
_ => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
None => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
|
||||
fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
|
||||
if ino == 1 {
|
||||
let attr = self.make_file_attr(1, "folder", 0);
|
||||
reply.attr(&TTL, &attr);
|
||||
return;
|
||||
}
|
||||
|
||||
let node_id = self.find_node_id_by_inode(ino);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let info = self.db.get_node_info(&id).ok();
|
||||
|
||||
match info {
|
||||
Some((node_type, file_size)) => {
|
||||
let attr = self.make_file_attr(ino, &node_type, file_size);
|
||||
reply.attr(&TTL, &attr);
|
||||
}
|
||||
_ => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
None => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
size: u32,
|
||||
reply: ReplyData,
|
||||
) {
|
||||
let node_id = self.find_node_id_by_inode(ino);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let file_path = self.get_file_path(&id);
|
||||
|
||||
match file_path {
|
||||
Some(fp) => {
|
||||
let mut file = File::open(&fp)?;
|
||||
file.seek(SeekFrom::Start(offset as u64)).ok();
|
||||
|
||||
let mut buffer = vec![0u8; size as usize];
|
||||
let bytes_read = file.read(&mut buffer).ok();
|
||||
buffer.truncate(bytes_read);
|
||||
reply.data(&buffer);
|
||||
}
|
||||
None => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
None => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
|
||||
fn readdir(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
mut reply: ReplyDirectory,
|
||||
) {
|
||||
if offset == 0 {
|
||||
reply.add(ino, 1, FileType::Directory, ".");
|
||||
reply.add(ino, 2, FileType::Directory, "..");
|
||||
|
||||
if ino == 1 {
|
||||
// Root - show Home folder
|
||||
reply.add(2, 3, FileType::Directory, "Home");
|
||||
} else {
|
||||
let node_id = self.find_node_id_by_inode(ino);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let children = self.db.list_children(&id).ok();
|
||||
let mut child_offset = 3;
|
||||
|
||||
for child_name in children {
|
||||
reply.add(
|
||||
child_offset,
|
||||
child_offset + 1,
|
||||
FileType::RegularFile,
|
||||
&child_name,
|
||||
);
|
||||
child_offset += 1;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reply.ok();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user