Files
markbase/markbase-fuse/src/fuse/handlers.rs
2026-05-30 14:08:55 +08:00

193 lines
5.9 KiB
Rust

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<i64>,
parent_id: Option<String>,
created_at: Option<i64>,
updated_at: Option<i64>,
}
impl<'a> FuseOperations<'a> {
pub fn new(fs: &'a MarkBaseFs) -> Self {
FuseOperations { fs }
}
pub fn getattr(&self, ino: u64) -> Result<FileAttr> {
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<Vec<(u64, String, FileKind)>> {
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<Vec<u8>> {
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<QueryNodeResult> {
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<i64>>(3)?,
parent_id: row.get::<_, Option<String>>(4)?,
created_at: row.get::<_, Option<i64>>(5)?,
updated_at: row.get::<_, Option<i64>>(6)?,
})
}
)?;
Ok(node)
}
fn query_children(&self, parent_uuid: &str) -> Result<Vec<QueryNodeResult>> {
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<i64>>(3)?,
parent_id: row.get::<_, Option<String>>(4)?,
created_at: row.get::<_, Option<i64>>(5)?,
updated_at: row.get::<_, Option<i64>>(6)?,
})
})?.collect::<Result<Vec<_>, _>>()?;
Ok(children)
}
fn get_file_path(&self, uuid: &str) -> Result<PathBuf> {
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());
}
}