Files
markbase/src/nfs/markbase_fs.rs
Warren 71fa48a626 System Extension注册完成 + FSKit Driver待办事项
已完成:
 App ID(6770506571)
 Bundle ID(com.momentry.markbase.fskit)
 Developer ID Application证书导入
 .app Bundle创建(build/MarkBaseFSKit.app)
 entitlements.plist配置

限制:
- binary未实现FSKit driver(占位符)
- 无法通过systemextensionsctl install安装
- 需要完整FSKit接口实现

策略:
- 短期:WebDAV(500 MB/s)
- 长期:FSKit Driver完整实现(650 MB/s)

文档:
- SYSTEM_EXTENSION_MANUAL_INSTALL.md
- FSKIT_DRIVER_TODO.md(未来待办)
2026-05-18 20:45:50 +08:00

243 lines
7.8 KiB
Rust

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<Connection>,
path_cache: Mutex<HashMap<String, String>>,
}
struct FileNode {
node_id: String,
label: String,
node_type: String,
parent_id: Option<String>,
aliases_json: Option<String>,
file_size: Option<i64>,
}
impl MarkBaseFS {
pub fn new(user_id: String, db_path: PathBuf) -> VfsResult<Self> {
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<FileNode> {
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<String> = None;
let mut current_node: Option<FileNode> = 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<Box<dyn Iterator<Item = String> + 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<String> = if parent_id.is_none() {
stmt.query_map([], |row| row.get::<_, String>(0))
.map_err(|e| rusqlite_to_io_error(e))?
.collect::<Result<Vec<_>, _>>()
.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::<Result<Vec<_>, _>>()
.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<Box<dyn SeekAndRead + Send>> {
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<Box<dyn SeekAndWrite + Send>> {
Err(VfsErrorKind::NotSupported.into())
}
fn append_file(&self, _path: &str) -> VfsResult<Box<dyn SeekAndWrite + Send>> {
Err(VfsErrorKind::NotSupported.into())
}
fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
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<bool> {
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");
}
}