Test Gitea Runner functionality

This commit is contained in:
Warren
2026-05-30 14:08:55 +08:00
parent 596d8d5e27
commit b362e9b3f1
44 changed files with 1 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
use std::path::Path;
use std::path::PathBuf;
use std::env;
use std::process::Command;
use anyhow::{Result, Error};
#[derive(Debug, Clone, PartialEq)]
pub enum BackendType {
Nfs4,
Fskit,
}
impl BackendType {
pub fn name(&self) -> &'static str {
match self {
BackendType::Nfs4 => "nfs",
BackendType::Fskit => "fskit",
}
}
pub fn supports_macos_version(&self, version: &str) -> bool {
match self {
BackendType::Nfs4 => true,
BackendType::Fskit => version.starts_with("26"),
}
}
}
pub fn detect_macos_version() -> String {
env::var("MACOS_VERSION").unwrap_or_else(|_| {
let output = Command::new("sw_vers")
.arg("-productVersion")
.output()
.expect("Failed to get macOS version");
String::from_utf8_lossy(&output.stdout).trim().to_string()
})
}
pub fn select_backend() -> BackendType {
let version = detect_macos_version();
if version.starts_with("26") {
BackendType::Fskit
} else {
BackendType::Nfs4
}
}
pub fn select_backend_manual(backend_name: &str) -> Result<BackendType> {
match backend_name {
"nfs" | "nfs4" => Ok(BackendType::Nfs4),
"fskit" => Ok(BackendType::Fskit),
_ => Err(Error::msg(format!("Unknown backend: {}", backend_name))),
}
}
pub fn detect_fuse_t_binary() -> bool {
Path::new("/Library/Application Support/fuse-t/bin/go-nfsv4").exists()
}
pub fn get_fuse_t_path() -> Option<PathBuf> {
if detect_fuse_t_binary() {
Some(PathBuf::from("/Library/Application Support/fuse-t/bin/go-nfsv4"))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backend_type_name() {
assert_eq!(BackendType::Nfs4.name(), "nfs");
assert_eq!(BackendType::Fskit.name(), "fskit");
}
#[test]
fn test_backend_support() {
assert!(BackendType::Nfs4.supports_macos_version("25.0"));
assert!(BackendType::Nfs4.supports_macos_version("26.0"));
assert!(!BackendType::Fskit.supports_macos_version("25.0"));
assert!(BackendType::Fskit.supports_macos_version("26.0"));
}
#[test]
fn test_select_backend_macos_26() {
env::set_var("MACOS_VERSION", "26.4.1");
let backend = select_backend();
assert_eq!(backend, BackendType::Fskit);
env::remove_var("MACOS_VERSION");
}
#[test]
fn test_select_backend_macos_25() {
env::set_var("MACOS_VERSION", "25.0.0");
let backend = select_backend();
assert_eq!(backend, BackendType::Nfs4);
env::remove_var("MACOS_VERSION");
}
#[test]
fn test_manual_backend_selection() {
assert!(select_backend_manual("nfs").is_ok());
assert!(select_backend_manual("fskit").is_ok());
assert!(select_backend_manual("invalid").is_err());
}
#[test]
fn test_fuse_t_binary_detection() {
// Should detect FUSE-T binary after installation
let detected = detect_fuse_t_binary();
assert!(detected); // Expected to be true after installation
}
}

View File

@@ -0,0 +1,193 @@
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());
}
}

View File

@@ -0,0 +1,399 @@
use std::path::{Path, PathBuf};
use std::ffi::CStr;
use std::io;
use std::time::Duration;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use anyhow::Result;
use fuse_backend_rs::api::filesystem::{FileSystem, Entry, DirEntry, Context};
use fuse_backend_rs::abi::fuse_abi::{FsOptions, OpenOptions, statvfs64};
use libc::{stat as stat64, DT_DIR, DT_REG};
use crate::fuse::backend::BackendType;
pub struct MarkBaseFs {
user_id: String,
db_path: PathBuf,
backend: BackendType,
}
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 MarkBaseFs {
pub fn new(user_id: String, db_path: PathBuf, backend: BackendType) -> Self {
MarkBaseFs {
user_id,
db_path,
backend,
}
}
pub fn get_user_id(&self) -> &str {
&self.user_id
}
pub fn get_backend(&self) -> &BackendType {
&self.backend
}
pub fn get_db_path(&self) -> &Path {
&self.db_path
}
pub fn mount(&self, mount_path: &Path) -> Result<()> {
println!("=== Mounting MarkBase FUSE ===");
println!("User: {}", self.user_id);
println!("Database: {}", self.db_path.display());
println!("Backend: {}", self.backend.name());
println!("Mount path: {}", mount_path.display());
Ok(())
}
pub fn uuid_to_ino(uuid: &str) -> u64 {
let bytes = uuid.as_bytes();
if bytes.len() >= 8 {
u64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7],
])
} else {
0
}
}
pub fn ino_to_uuid(ino: u64) -> String {
let bytes = ino.to_be_bytes();
format!("{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7])
}
fn query_node(&self, uuid: &str) -> io::Result<QueryNodeResult> {
use rusqlite::Connection;
let conn = Connection::open(&self.db_path)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
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)?,
})
}
).map_err(|e| io::Error::new(io::ErrorKind::NotFound, e.to_string()))
}
fn query_children(&self, parent_uuid: &str) -> io::Result<Vec<QueryNodeResult>> {
use rusqlite::Connection;
let conn = Connection::open(&self.db_path)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
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"
).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let rows = 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)?,
})
}).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let mut children = Vec::new();
for row in rows {
children.push(row.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?);
}
Ok(children)
}
fn get_file_path(&self, uuid: &str) -> io::Result<PathBuf> {
use rusqlite::Connection;
let conn = Connection::open(&self.db_path)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
conn.query_row(
"SELECT location FROM file_locations WHERE file_uuid = ?",
[uuid],
|row| row.get::<_, String>(0)
).map(PathBuf::from).map_err(|e| io::Error::new(io::ErrorKind::NotFound, e.to_string()))
}
}
impl FileSystem for MarkBaseFs {
type Inode = u64;
type Handle = u64;
fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
println!("MarkBaseFs::init() called - filesystem ready");
println!("Database: {}", self.db_path.display());
println!("User: {}", self.user_id);
println!("Backend: {}", self.backend.name());
Ok(FsOptions::empty())
}
fn lookup(&self, _ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<Entry> {
let parent_uuid = Self::ino_to_uuid(parent);
let name_str = name.to_string_lossy();
let children = self.query_children(&parent_uuid)?;
for child in children {
if child.label == name_str {
let child_ino = Self::uuid_to_ino(&child.node_id);
let is_dir = child.node_type == "folder";
let mut stat: stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = child_ino;
stat.st_mode = if is_dir { 0o755 | libc::S_IFDIR } else { 0o644 | libc::S_IFREG };
stat.st_nlink = if is_dir { 2 } else { 1 };
stat.st_size = child.file_size.unwrap_or(0) as i64;
stat.st_mtime = child.updated_at.unwrap_or(0);
stat.st_ctime = child.created_at.unwrap_or(0);
return Ok(Entry {
inode: child_ino,
generation: 0,
attr: stat,
attr_flags: 0,
attr_timeout: Duration::from_secs(60),
entry_timeout: Duration::from_secs(60),
});
}
}
Err(io::Error::from_raw_os_error(libc::ENOENT))
}
fn getattr(
&self,
_ctx: &Context,
inode: Self::Inode,
_handle: Option<Self::Handle>,
) -> io::Result<(stat64, Duration)> {
let uuid = Self::ino_to_uuid(inode);
let node = self.query_node(&uuid)?;
let is_dir = node.node_type == "folder";
let mut stat: stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = inode;
stat.st_mode = if is_dir { 0o755 | libc::S_IFDIR } else { 0o644 | libc::S_IFREG };
stat.st_nlink = if is_dir { 2 } else { 1 };
stat.st_size = node.file_size.unwrap_or(0) as i64;
stat.st_mtime = node.updated_at.unwrap_or(0);
stat.st_ctime = node.created_at.unwrap_or(0);
Ok((stat, Duration::from_secs(60)))
}
fn opendir(
&self,
_ctx: &Context,
inode: Self::Inode,
_flags: u32,
) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
Ok((Some(inode), OpenOptions::empty()))
}
fn readdir(
&self,
_ctx: &Context,
inode: Self::Inode,
_handle: Self::Handle,
_size: u32,
offset: u64,
add_entry: &mut dyn FnMut(DirEntry) -> io::Result<usize>,
) -> io::Result<()> {
let uuid = Self::ino_to_uuid(inode);
let children = self.query_children(&uuid)?;
for (idx, child) in children.iter().enumerate().skip(offset as usize) {
let child_ino = Self::uuid_to_ino(&child.node_id);
let type_ = if child.node_type == "folder" { DT_DIR } else { DT_REG };
let name_bytes = child.label.as_bytes();
let entry = DirEntry {
ino: child_ino,
offset: (idx + 1) as u64,
type_: type_ as u32,
name: name_bytes,
};
match add_entry(entry) {
Ok(0) => break,
Ok(_) => continue,
Err(e) => return Err(e),
}
}
Ok(())
}
fn releasedir(
&self,
_ctx: &Context,
_inode: Self::Inode,
_flags: u32,
_handle: Self::Handle,
) -> io::Result<()> {
Ok(())
}
fn open(
&self,
_ctx: &Context,
inode: Self::Inode,
_flags: u32,
_fuse_flags: u32,
) -> io::Result<(Option<Self::Handle>, OpenOptions, Option<u32>)> {
Ok((Some(inode), OpenOptions::empty(), None))
}
fn read(
&self,
_ctx: &Context,
inode: Self::Inode,
_handle: Self::Handle,
w: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyWriter,
size: u32,
offset: u64,
_lock_owner: Option<u64>,
_flags: u32,
) -> io::Result<usize> {
let uuid = Self::ino_to_uuid(inode);
let path = self.get_file_path(&uuid)?;
if !path.exists() {
return Err(io::Error::from_raw_os_error(libc::ENOENT));
}
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)?;
w.write_all(&buffer[..bytes_read])?;
Ok(bytes_read)
}
fn write(
&self,
_ctx: &Context,
inode: Self::Inode,
_handle: Self::Handle,
r: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyReader,
size: u32,
offset: u64,
_lock_owner: Option<u64>,
_delayed_write: bool,
_flags: u32,
_fuse_flags: u32,
) -> io::Result<usize> {
let uuid = Self::ino_to_uuid(inode);
let path = self.get_file_path(&uuid)?;
let mut file = File::create(&path)?;
file.seek(SeekFrom::Start(offset))?;
let mut buffer = vec![0u8; size as usize];
let bytes_read = r.read(&mut buffer)?;
file.write_all(&buffer[..bytes_read])?;
Ok(bytes_read)
}
fn release(
&self,
_ctx: &Context,
_inode: Self::Inode,
_flags: u32,
_handle: Self::Handle,
_flush: bool,
_flock_release: bool,
_lock_owner: Option<u64>,
) -> io::Result<()> {
Ok(())
}
fn statfs(&self, _ctx: &Context, _inode: Self::Inode) -> io::Result<statvfs64> {
let mut stat: statvfs64 = unsafe { std::mem::zeroed() };
stat.f_bsize = 4096;
stat.f_frsize = 4096;
stat.f_blocks = 1000000;
stat.f_bfree = 500000;
stat.f_bavail = 500000;
stat.f_files = 12659;
stat.f_ffree = 50000;
stat.f_favail = 50000;
Ok(stat)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_markbase_fs_creation() {
let db_path = PathBuf::from("/tmp/test.sqlite");
let fs = MarkBaseFs::new("test_user".to_string(), db_path, BackendType::Fskit);
assert_eq!(fs.get_user_id(), "test_user");
assert_eq!(fs.get_backend(), &BackendType::Fskit);
}
#[test]
fn test_uuid_to_ino_conversion() {
let uuid = "8b1ede3cd6970f02fa85b8e34b682caf";
let ino = MarkBaseFs::uuid_to_ino(uuid);
let ino2 = MarkBaseFs::uuid_to_ino(uuid);
assert_eq!(ino, ino2);
assert!(ino > 0);
}
#[test]
fn test_mount_placeholder() {
let db_path = PathBuf::from("/tmp/test.sqlite");
let fs = MarkBaseFs::new("test_user".to_string(), db_path, BackendType::Nfs4);
let mount_path = Path::new("/tmp/mount_test");
let result = fs.mount(mount_path);
assert!(result.is_ok());
}
}

View File

@@ -0,0 +1,9 @@
pub mod poc_hello;
pub mod backend;
pub mod markbase_fs;
pub mod mount_manager;
pub use backend::{BackendType, select_backend, select_backend_manual, detect_macos_version};
pub use poc_hello::{HelloFs, mount_hello_fs};
pub use markbase_fs::MarkBaseFs;
pub use mount_manager::{MountHandle, mount_user_fs};

View File

@@ -0,0 +1,160 @@
use std::path::PathBuf;
use std::sync::Arc;
use std::thread;
use anyhow::{Result, Error};
use log::info;
use fuse_backend_rs::api::server::Server;
use fuse_backend_rs::transport::FuseSession;
use crate::fuse::markbase_fs::MarkBaseFs;
pub struct MountHandle {
session: FuseSession,
mount_path: PathBuf,
user_id: String,
}
impl MountHandle {
pub fn new(
user_id: String,
mount_path: PathBuf,
_db_path: PathBuf,
readonly: bool,
) -> Result<Self> {
let fsname = "MarkBase";
let subtype = &user_id;
let session = FuseSession::new(&mount_path, fsname, subtype, readonly)
.map_err(|e| Error::msg(format!("Failed to create FUSE session: {:?}", e)))?;
Ok(MountHandle {
session,
mount_path,
user_id,
})
}
pub fn mount(&mut self, db_path: PathBuf) -> Result<()> {
info!("Mounting MarkBase FUSE for user: {}", self.user_id);
info!("Mount path: {}", self.mount_path.display());
info!("Database: {}", db_path.display());
self.session.mount()
.map_err(|e| Error::msg(format!("Failed to mount: {:?}", e)))?;
info!("FUSE session mounted successfully");
Ok(())
}
pub fn unmount(&mut self) -> Result<()> {
info!("Unmounting MarkBase FUSE for user: {}", self.user_id);
self.session.umount()
.map_err(|e| Error::msg(format!("Failed to unmount: {:?}", e)))?;
self.session.wake()
.map_err(|e| Error::msg(format!("Failed to wake session: {:?}", e)))?;
info!("FUSE session unmounted successfully");
Ok(())
}
}
pub fn mount_user_fs(
user_id: String,
mount_path: PathBuf,
db_path: PathBuf,
readonly: bool,
) -> Result<()> {
println!("[DEBUG] Creating mount handle...");
let mut handle = MountHandle::new(user_id.clone(), mount_path.clone(), db_path.clone(), readonly)?;
println!("[DEBUG] Calling session.mount()...");
handle.mount(db_path.clone())?;
println!("[DEBUG] Creating filesystem instance...");
let backend = crate::fuse::backend::select_backend();
let fs = Arc::new(MarkBaseFs::new(user_id.clone(), db_path, backend));
let server = Arc::new(Server::new(fs));
println!("[DEBUG] Creating FUSE channel...");
let channel = handle.session.new_channel()
.map_err(|e| Error::msg(format!("Failed to create channel: {:?}", e)))?;
println!("[DEBUG] Starting FUSE request handler thread...");
let user_id_clone = user_id.clone();
let handler_thread = thread::spawn(move || {
println!("[DEBUG] Handler thread started for user: {}", user_id_clone);
let mut channel = channel;
loop {
match channel.get_request() {
Ok(Some((reader, writer))) => {
println!("[DEBUG] Received FUSE request");
let writer = writer.into();
if let Err(e) = server.handle_message(reader, writer, None, None) {
println!("[WARN] Error handling FUSE request: {:?}", e);
}
}
Ok(None) => {
println!("[DEBUG] FUSE channel received signal to exit");
break;
}
Err(e) => {
println!("[WARN] Error getting FUSE request: {:?}", e);
break;
}
}
}
println!("[DEBUG] Handler thread exited for user: {}", user_id_clone);
});
println!("[DEBUG] Calling session.wait_mount()...");
match handle.session.wait_mount() {
Ok(_) => {
println!("[INFO] wait_mount() returned OK - mount completed successfully");
}
Err(e) => {
println!("[ERROR] wait_mount() failed: {:?}", e);
return Err(Error::msg(format!("Failed to wait mount: {:?}", e)));
}
}
println!("[INFO] Mount completed for user: {}", user_id);
println!("[DEBUG] Handler thread status: {:?}", handler_thread.is_finished());
println!("[DEBUG] Joining handler thread...");
handler_thread.join()
.map_err(|e| Error::msg(format!("Handler thread error: {:?}", e)))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_mount_handle_creation() {
let mount_path = PathBuf::from("/tmp/test_mount");
let db_path = PathBuf::from("/tmp/test.sqlite");
let result = MountHandle::new(
"test_user".to_string(),
mount_path,
db_path,
false,
);
assert!(result.is_ok());
}
}

View File

@@ -0,0 +1,36 @@
use std::path::Path;
use anyhow::Result;
pub struct HelloFs;
impl HelloFs {
pub fn new() -> Self {
HelloFs
}
}
pub fn mount_hello_fs(path: &Path) -> Result<()> {
println!("FUSE Hello POC - Mount at: {}", path.display());
println!("NOTE: This is a placeholder implementation.");
println!("Actual FUSE mount requires fuse library (not yet added).");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hello_fs_creation() {
let fs = HelloFs::new();
assert!(true);
}
#[test]
fn test_mount_placeholder() {
let path = Path::new("/tmp/test_fuse");
let result = mount_hello_fs(path);
assert!(result.is_ok());
}
}