From 68aba5236288aa830ae2af99cf4e245cea2c94ec Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 26 Jun 2026 20:25:33 +0200 Subject: [PATCH] Handle files as tree on fuse side --- src/local.rs | 162 ++++++++++++++++++++++++++++++------------ src/music_metadata.rs | 10 +-- 2 files changed, 123 insertions(+), 49 deletions(-) diff --git a/src/local.rs b/src/local.rs index 4297783..eaf4208 100644 --- a/src/local.rs +++ b/src/local.rs @@ -18,8 +18,8 @@ use notify::{Event, EventKind, RecursiveMode, Watcher}; use sea_orm::entity::prelude::*; use twox_hash::XxHash64; -use crate::file_watcher::FileWatcher; use crate::music_metadata::MusicMetadata; +use crate::{file_watcher::FileWatcher, music_metadata}; #[derive(PartialEq, Eq, Copy, Clone, Debug)] enum FileType { @@ -30,6 +30,7 @@ enum FileType { #[derive(Debug)] struct LocalItem { inode: INodeNo, + parent_inode: INodeNo, name: String, original_path: PathBuf, local_path: PathBuf, @@ -179,6 +180,7 @@ impl LocalOrigin { let local_root = LocalItem { inode: INodeNo::ROOT, + parent_inode: INodeNo::ROOT, name: "/".to_string(), original_path: source.to_path_buf(), local_path: destination.to_path_buf(), @@ -212,10 +214,6 @@ impl LocalOrigin { } else { FileType::File }; - - let mut local_path = PathBuf::new(); - local_path.push(name.clone()); - let music_metadata = if item_path.extension().and_then(std::ffi::OsStr::to_str) == Some("flac") { MusicMetadata::parse_music_metadata(&item_path) @@ -223,9 +221,19 @@ impl LocalOrigin { None }; + let mut local_path = PathBuf::new(); + if music_metadata.is_some() { + let music_metadata = music_metadata.clone().unwrap(); + local_path.push(music_metadata.artist.join("-")); + local_path.push(music_metadata.album); + } + local_path.push(name.clone()); + let inode = INodeNo(metadata.ino()); + let parent_inode = LocalOrigin::ensure_virtual_dirs(&local_path, source, map); let local_item = LocalItem { inode, + parent_inode, name, original_path: item_path.clone(), local_path, @@ -281,6 +289,61 @@ impl LocalOrigin { FileType::File => fuser::FileType::RegularFile, }; } + + fn virtual_inode(path: &str) -> INodeNo { + let mut hasher = XxHash64::with_seed(5678); + hasher.write(path.as_bytes()); + INodeNo(hasher.finish() | (1u64 << 63)) + } + + fn ensure_virtual_dirs( + local_path: &Path, + source: &Path, + map: &mut BTreeMap, + ) -> INodeNo { + let components: Vec<_> = local_path.components().collect(); + + if components.len() <= 1 { + return INodeNo::ROOT; + } + + let mut current_parent = INodeNo::ROOT; + let mut current_path = String::new(); + + for component in &components[..components.len() - 1] { + let comp_str = component.as_os_str().to_str().unwrap_or_default(); + if !current_path.is_empty() { + current_path.push('/'); + } + current_path.push_str(comp_str); + + let virt_ino = LocalOrigin::virtual_inode(¤t_path); + + if !map.contains_key(&virt_ino) { + let metadata = fs::metadata(source).unwrap(); + let virt_item = LocalItem { + inode: virt_ino, + parent_inode: current_parent, + name: comp_str.to_string(), + original_path: source.to_path_buf(), + local_path: PathBuf::from(¤t_path), + file_type: FileType::Directory, + metadata, + music_metadata: None, + hash: 0, + }; + let virt_item = LocalItem { + hash: virt_item.compute_hash(), + ..virt_item + }; + map.insert(virt_ino, virt_item); + } + + current_parent = virt_ino; + } + + return current_parent; + } } impl Filesystem for LocalOrigin { @@ -313,46 +376,40 @@ impl Filesystem for LocalOrigin { ) { println!("readdir(ino={}, fh={}, offset={})", ino, fh, offset); - if ino == INodeNo::ROOT { - if offset == 0 { - let _ = reply.add( - INodeNo::ROOT, - 0, - fuser::FileType::Directory, - &Path::new("."), - ); - let _ = reply.add( - INodeNo::ROOT, - 1, - fuser::FileType::Directory, - &Path::new(".."), - ); + let files = self.files.lock().unwrap(); + + let parent_inode = match files.get(&ino) { + Some(dir) => dir.parent_inode, + None => { + reply.error(Errno::ENOENT); + return; } + }; - // TODO fix unwrap - for (i, (key, value)) in self - .files - .lock() - .unwrap() - .iter() - .filter(|(key, _)| **key != INodeNo::ROOT) - .skip(offset as usize) - .enumerate() - { - let offset: u64 = 2 + i as u64; - let file_type: fuser::FileType = LocalOrigin::file_type(value.file_type); - let path = &value.name; - - let buffer_full: bool = reply.add(*key, offset, file_type, path); - - if buffer_full { - break; - } - } - reply.ok(); - } else { - reply.error(Errno::ENOSYS); + if offset == 0 { + let _ = reply.add(ino, 0, fuser::FileType::Directory, &Path::new(".")); + let _ = reply.add( + parent_inode, + 1, + fuser::FileType::Directory, + &Path::new(".."), + ); } + + for (i, (key, value)) in files + .iter() + .filter(|(_, v)| v.parent_inode == ino && v.inode != ino) + .skip(offset as usize) + .enumerate() + { + let entry_offset: u64 = 2 + i as u64; + let file_type: fuser::FileType = LocalOrigin::file_type(value.file_type); + let buffer_full: bool = reply.add(*key, entry_offset, file_type, &value.name); + if buffer_full { + break; + } + } + reply.ok(); } fn lookup( @@ -369,7 +426,7 @@ impl Filesystem for LocalOrigin { .lock() .unwrap() .iter() - .find(|item| item.1.name == name.to_str().unwrap()) + .find(|item| item.1.parent_inode == parent && item.1.name == name.to_str().unwrap()) { Some(item) => { let ttl = Duration::new(1, 0); @@ -471,11 +528,28 @@ impl From<&LocalItem> for ActiveModel { impl From<(LocalItemEntity, fs::Metadata)> for LocalItem { fn from((entity, metadata): (LocalItemEntity, fs::Metadata)) -> Self { + let local_path = PathBuf::from(&entity.local_path); + let parent_inode = { + let components: Vec<_> = local_path.components().collect(); + if components.len() <= 1 { + INodeNo::ROOT + } else { + let mut parent_path = String::new(); + for (i, comp) in components[..components.len() - 1].iter().enumerate() { + if i > 0 { + parent_path.push('/'); + } + parent_path.push_str(comp.as_os_str().to_str().unwrap_or_default()); + } + LocalOrigin::virtual_inode(&parent_path) + } + }; let local_item = LocalItem { inode: INodeNo(entity.inode as u64), + parent_inode, name: entity.name, original_path: PathBuf::from(entity.original_path), - local_path: PathBuf::from(entity.local_path), + local_path, file_type: match entity.file_type.as_str() { "directory" => FileType::Directory, _ => FileType::File, diff --git a/src/music_metadata.rs b/src/music_metadata.rs index cd12a84..74735bf 100644 --- a/src/music_metadata.rs +++ b/src/music_metadata.rs @@ -7,12 +7,12 @@ use symphonia::core::{ probe::Hint, }; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct MusicMetadata { - artist: Vec, - album: String, - track_number: i32, - track_title: String, + pub artist: Vec, + pub album: String, + pub track_number: i32, + pub track_title: String, } impl MusicMetadata {