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, cache: Arc, inode_map: HashMap, path_cache: HashMap, next_inode: u64, } impl MarkBaseFs { pub fn new(db_path: &str, tree_type: &str) -> Result { 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 { 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 { // 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 { // 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(); } }