use std::collections::HashMap; use std::io; use std::path::PathBuf; use std::sync::Mutex; use rusqlite::Connection; use vfs::{FileSystem, VfsMetadata, VfsResult, VfsFileType, SeekAndRead, SeekAndWrite}; use vfs::error::VfsErrorKind; fn rusqlite_to_io_error(e: rusqlite::Error) -> io::Error { io::Error::new(io::ErrorKind::Other, e.to_string()) } #[derive(Debug)] pub struct MarkBaseFS { user_id: String, db_path: PathBuf, conn: Mutex, path_cache: Mutex>, } struct FileNode { node_id: String, label: String, node_type: String, parent_id: Option, aliases_json: Option, file_size: Option, } impl MarkBaseFS { pub fn new(user_id: String, db_path: PathBuf) -> VfsResult { let conn = Connection::open(&db_path) .map_err(|e| VfsErrorKind::IoError(rusqlite_to_io_error(e)))?; Ok(MarkBaseFS { user_id, db_path, conn: Mutex::new(conn), path_cache: Mutex::new(HashMap::new()), }) } fn resolve_path(&self, path: &str) -> VfsResult { if path == "" || path == "/" { return Ok(FileNode { node_id: "root".to_string(), label: "".to_string(), node_type: "folder".to_string(), parent_id: None, aliases_json: None, file_size: None, }); } let conn = self.conn.lock() .map_err(|_| VfsErrorKind::Other("Failed to lock connection".to_string()))?; let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); let mut current_parent: Option = None; let mut current_node: Option = None; for part in parts { let query = if current_parent.is_none() { "SELECT node_id, label, node_type, parent_id, aliases_json, file_size FROM file_nodes WHERE parent_id IS NULL AND label = ?1" } else { "SELECT node_id, label, node_type, parent_id, aliases_json, file_size FROM file_nodes WHERE parent_id = ?1 AND label = ?2" }; let mut stmt = conn.prepare(query) .map_err(|e| VfsErrorKind::IoError(rusqlite_to_io_error(e)))?; let node = if current_parent.is_none() { stmt.query_row([part], |row| { Ok(FileNode { node_id: row.get(0)?, label: row.get(1)?, node_type: row.get(2)?, parent_id: row.get(3)?, aliases_json: row.get(4)?, file_size: row.get(5)?, }) }).map_err(|e| rusqlite_to_io_error(e)) } else { let part_str = part.to_string(); stmt.query_row([current_parent.clone().unwrap(), part_str], |row| { Ok(FileNode { node_id: row.get(0)?, label: row.get(1)?, node_type: row.get(2)?, parent_id: row.get(3)?, aliases_json: row.get(4)?, file_size: row.get(5)?, }) }).map_err(|e| rusqlite_to_io_error(e)) }; match node { Ok(n) => { current_parent = Some(n.node_id.clone()); current_node = Some(n); } Err(_) => return Err(VfsErrorKind::FileNotFound.into()), } } current_node.ok_or(VfsErrorKind::FileNotFound.into()) } } impl FileSystem for MarkBaseFS { fn read_dir(&self, path: &str) -> VfsResult + Send>> { let conn = self.conn.lock() .map_err(|_| VfsErrorKind::Other("Failed to lock connection".to_string()))?; let parent_id = if path == "" || path == "/" { None } else { let node = self.resolve_path(path)?; Some(node.node_id) }; let query = if parent_id.is_none() { "SELECT label FROM file_nodes WHERE parent_id IS NULL" } else { "SELECT label FROM file_nodes WHERE parent_id = ?1" }; let mut stmt = conn.prepare(query) .map_err(|e| VfsErrorKind::IoError(rusqlite_to_io_error(e)))?; let children: Vec = if parent_id.is_none() { stmt.query_map([], |row| row.get::<_, String>(0)) .map_err(|e| rusqlite_to_io_error(e))? .collect::, _>>() .map_err(|e| rusqlite_to_io_error(e))? } else { stmt.query_map([parent_id.unwrap()], |row| row.get::<_, String>(0)) .map_err(|e| rusqlite_to_io_error(e))? .collect::, _>>() .map_err(|e| rusqlite_to_io_error(e))? }; Ok(Box::new(children.into_iter())) } fn create_dir(&self, _path: &str) -> VfsResult<()> { Err(VfsErrorKind::NotSupported.into()) } fn open_file(&self, path: &str) -> VfsResult> { let node = self.resolve_path(path)?; if node.node_type != "file" { return Err(VfsErrorKind::InvalidPath.into()); } let aliases_json = node.aliases_json.ok_or(VfsErrorKind::FileNotFound)?; let aliases: serde_json::Value = serde_json::from_str(&aliases_json) .map_err(|e| VfsErrorKind::IoError(io::Error::new(io::ErrorKind::Other, e.to_string())))?; let file_path = aliases["path"].as_str().ok_or(VfsErrorKind::FileNotFound)?; let file = std::fs::File::open(file_path)?; Ok(Box::new(file)) } fn create_file(&self, _path: &str) -> VfsResult> { Err(VfsErrorKind::NotSupported.into()) } fn append_file(&self, _path: &str) -> VfsResult> { Err(VfsErrorKind::NotSupported.into()) } fn metadata(&self, path: &str) -> VfsResult { let node = self.resolve_path(path)?; let file_type = if node.node_type == "folder" { VfsFileType::Directory } else { VfsFileType::File }; let len = node.file_size.unwrap_or(0) as u64; Ok(VfsMetadata { file_type, len, created: None, modified: None, accessed: None, }) } fn exists(&self, path: &str) -> VfsResult { match self.resolve_path(path) { Ok(_) => Ok(true), Err(_) => Ok(false), } } fn remove_file(&self, _path: &str) -> VfsResult<()> { Err(VfsErrorKind::NotSupported.into()) } fn remove_dir(&self, _path: &str) -> VfsResult<()> { Err(VfsErrorKind::NotSupported.into()) } } #[cfg(test)] mod tests { use super::*; use vfs::FileSystem; #[test] fn test_markbase_fs_creation() { let fs = MarkBaseFS::new( "warren".to_string(), PathBuf::from("data/users/warren.sqlite"), ); assert!(fs.is_ok()); } #[test] fn test_resolve_root() { let fs = MarkBaseFS::new( "warren".to_string(), PathBuf::from("data/users/warren.sqlite"), ).unwrap(); let node = fs.resolve_path(""); assert!(node.is_ok()); assert_eq!(node.unwrap().node_type, "folder"); } }