193 lines
5.9 KiB
Rust
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());
|
|
}
|
|
} |