Handle files as tree on fuse side

This commit is contained in:
Alexander
2026-06-26 20:25:33 +02:00
parent a5023a6441
commit 68aba52362
2 changed files with 123 additions and 49 deletions
+118 -44
View File
@@ -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, LocalItem>,
) -> 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(&current_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(&current_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,
+5 -5
View File
@@ -7,12 +7,12 @@ use symphonia::core::{
probe::Hint,
};
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct MusicMetadata {
artist: Vec<String>,
album: String,
track_number: i32,
track_title: String,
pub artist: Vec<String>,
pub album: String,
pub track_number: i32,
pub track_title: String,
}
impl MusicMetadata {