use std::path::PathBuf; use std::fs::File; use std::io::{Read, Seek, SeekFrom}; use anyhow::{Result, Error}; pub struct FuseOperations<'a> { fs: &'a MarkBaseFs, } struct QueryNodeResult { node_id: String, label: String, node_type: String, file_size: Option, parent_id: Option, created_at: Option, updated_at: Option, } impl<'a> FuseOperations<'a> { pub fn new(fs: &'a MarkBaseFs) -> Self { FuseOperations { fs } } pub fn getattr(&self, ino: u64) -> Result { let uuid = MarkBaseFs::ino_to_uuid(ino); let node = self.query_node(&uuid)?; let kind = match node.node_type.as_str() { "folder" => FileKind::Directory, "file" => FileKind::RegularFile, _ => FileKind::RegularFile, }; let size = if kind == FileKind::RegularFile { node.file_size.unwrap_or(0) as u64 } else { 0 }; Ok(FileAttr { ino, size, mode: if kind == FileKind::Directory { 0o755 } else { 0o644 }, nlink: if kind == FileKind::Directory { 2 } else { 1 }, uid: 0, gid: 0, atime: node.updated_at.unwrap_or(0) as u64, mtime: node.updated_at.unwrap_or(0) as u64, ctime: node.created_at.unwrap_or(0) as u64, kind, }) } pub fn readdir(&self, ino: u64) -> Result> { let uuid = MarkBaseFs::ino_to_uuid(ino); let children = self.query_children(&uuid)?; let entries: Vec<(u64, String, FileKind)> = children .into_iter() .map(|node| { let child_ino = MarkBaseFs::uuid_to_ino(&node.node_id); let kind = match node.node_type.as_str() { "folder" => FileKind::Directory, "file" => FileKind::RegularFile, _ => FileKind::RegularFile, }; (child_ino, node.label, kind) }) .collect(); Ok(entries) } pub fn read(&self, ino: u64, offset: u64, size: u32) -> Result> { let uuid = MarkBaseFs::ino_to_uuid(ino); let path = self.get_file_path(&uuid)?; if !path.exists() { return Err(Error::msg("File not found")); } let mut file = File::open(&path)?; file.seek(SeekFrom::Start(offset))?; let mut buffer = vec![0u8; size as usize]; let bytes_read = file.read(&mut buffer)?; buffer.truncate(bytes_read); Ok(buffer) } fn query_node(&self, uuid: &str) -> Result { use rusqlite::Connection; let db_path = self.fs.get_db_path(); let conn = Connection::open(db_path)?; let node = conn.query_row( "SELECT node_id, label, node_type, file_size, parent_id, created_at, updated_at FROM file_nodes WHERE node_id = ?", [uuid], |row| { Ok(QueryNodeResult { node_id: row.get::<_, String>(0)?, label: row.get::<_, String>(1)?, node_type: row.get::<_, String>(2)?, file_size: row.get::<_, Option>(3)?, parent_id: row.get::<_, Option>(4)?, created_at: row.get::<_, Option>(5)?, updated_at: row.get::<_, Option>(6)?, }) } )?; Ok(node) } fn query_children(&self, parent_uuid: &str) -> Result> { use rusqlite::Connection; let db_path = self.fs.get_db_path(); let conn = Connection::open(db_path)?; let mut stmt = conn.prepare( "SELECT node_id, label, node_type, file_size, parent_id, created_at, updated_at FROM file_nodes WHERE parent_id = ? ORDER BY sort_order, label" )?; let children = stmt.query_map([parent_uuid], |row| { Ok(QueryNodeResult { node_id: row.get::<_, String>(0)?, label: row.get::<_, String>(1)?, node_type: row.get::<_, String>(2)?, file_size: row.get::<_, Option>(3)?, parent_id: row.get::<_, Option>(4)?, created_at: row.get::<_, Option>(5)?, updated_at: row.get::<_, Option>(6)?, }) })?.collect::, _>>()?; Ok(children) } fn get_file_path(&self, uuid: &str) -> Result { use rusqlite::Connection; let db_path = self.fs.get_db_path(); let conn = Connection::open(db_path)?; let path_str = conn.query_row( "SELECT location FROM file_locations WHERE file_uuid = ?", [uuid], |row| row.get::<_, String>(0) )?; Ok(PathBuf::from(path_str)) } } #[cfg(test)] mod tests { use super::*; use crate::fuse::backend::BackendType; #[test] fn test_fuse_operations_creation() { let db_path = PathBuf::from("data/users/warren.sqlite"); let fs = MarkBaseFs::new("warren".to_string(), db_path, BackendType::Fskit); let ops = FuseOperations::new(&fs); assert!(true); } #[test] fn test_uuid_roundtrip() { let uuid = "8b1ede3cd6970f02fa85b8e34b682caf"; let ino = MarkBaseFs::uuid_to_ino(uuid); // Just verify the conversion produces a valid inode number assert!(ino > 0); // And that we can convert back let recovered = MarkBaseFs::ino_to_uuid(ino); assert!(!recovered.is_empty()); } }