From d14710c073c4daac3620bb458eb182b4fa965e15 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 27 Jun 2026 14:47:37 +0200 Subject: [PATCH] Refactor --- src/local.rs | 687 +++++++++++++++++++++++------------------- src/music_metadata.rs | 46 +-- 2 files changed, 401 insertions(+), 332 deletions(-) diff --git a/src/local.rs b/src/local.rs index 975f507..8440f49 100644 --- a/src/local.rs +++ b/src/local.rs @@ -44,6 +44,33 @@ struct LocalItem { } impl LocalItem { + #[allow(clippy::too_many_arguments)] + fn new( + inode: INodeNo, + parent_inode: INodeNo, + name: String, + original_path: PathBuf, + local_path: PathBuf, + file_type: FileType, + metadata: fs::Metadata, + music_metadata: Option, + ) -> LocalItem { + let mut item = LocalItem { + inode, + parent_inode, + name, + original_path, + local_path, + file_type, + metadata, + music_metadata, + hash: 0, + }; + item.hash = item.compute_hash(); + + return item; + } + fn compute_hash(&self) -> u64 { let seed = 1234; let mut hasher = XxHash64::with_seed(seed); @@ -82,11 +109,12 @@ pub struct LocalOrigin { impl std::fmt::Debug for LocalOrigin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("LocalOrigin") + return f + .debug_struct("LocalOrigin") .field("source", &self.source) .field("destination", &self.destination) .field("files", &self.files) - .finish() + .finish(); } } @@ -109,12 +137,31 @@ impl LocalOrigin { .map(|e| (e.inode, e)) .collect(); + LocalOrigin::sync_items_to_db(&snapshot, &db_items, &client).await; + LocalOrigin::restore_music_metadata_from_db(&mut snapshot, &db_items, &client).await; + LocalOrigin::restore_virtual_paths(&mut snapshot, &db_items, Path::new(&source)); + + let local_origin = LocalOrigin { + source: source.into(), + destination: destination.into(), + files: Arc::new(Mutex::new(snapshot)), + client, + }; + //dbg!(&local_origin); + return Ok(local_origin); + } + + async fn sync_items_to_db( + snapshot: &BTreeMap, + db_items: &std::collections::HashMap, + client: &sea_orm::DatabaseConnection, + ) { let mut to_insert: Vec = vec![]; let mut to_update: Vec = vec![]; let mut to_delete: Vec = vec![]; let mut to_save_music: Vec = vec![]; - for (ino, item) in &snapshot { + for (ino, item) in snapshot { let ino_i64 = ino.0 as i64; match db_items.get(&ino_i64) { None => { @@ -140,13 +187,13 @@ impl LocalOrigin { } if !to_insert.is_empty() { - Entity::insert_many(to_insert).exec(&client).await.unwrap(); + Entity::insert_many(to_insert).exec(client).await.unwrap(); } for model in to_update { - model.update(&client).await.unwrap(); + model.update(client).await.unwrap(); } for ino in to_delete { - Entity::delete_by_id(ino).exec(&client).await.unwrap(); + Entity::delete_by_id(ino).exec(client).await.unwrap(); } for ino_i64 in &to_save_music { @@ -155,14 +202,23 @@ impl LocalOrigin { .get(&ino) .and_then(|item| item.music_metadata.as_ref()) { - LocalOrigin::save_music_metadata(*ino_i64, music_metadata, &client) + LocalOrigin::save_music_metadata(*ino_i64, music_metadata, client) .await .unwrap(); } } + } + + async fn restore_music_metadata_from_db( + snapshot: &mut BTreeMap, + db_items: &std::collections::HashMap, + client: &sea_orm::DatabaseConnection, + ) { + use artists::Column as ArtCol; + use music_metadata_entity::Column as MmCol; + use other_tags::Column as OtCol; + use pictures::Column as PicCol; - // For unchanged FLAC items, restore music_metadata from DB - // (overrides the fresh FLAC parse so that writes made in a previous session survive restarts) let unchanged_music_inodes: Vec = db_items .values() .filter_map(|db_item| { @@ -176,112 +232,116 @@ impl LocalOrigin { }) .collect(); - if !unchanged_music_inodes.is_empty() { - use artists::Column as ArtCol; - use music_metadata_entity::Column as MmCol; - use other_tags::Column as OtCol; - use pictures::Column as PicCol; - - let mm_rows: std::collections::HashMap = - music_metadata_entity::Entity::find() - .filter(MmCol::Inode.is_in(unchanged_music_inodes.clone())) - .all(&client) - .await - .unwrap() - .into_iter() - .map(|m| (m.inode, m)) - .collect(); - - let mut artists_by_inode: std::collections::HashMap> = - std::collections::HashMap::new(); - for row in artists::Entity::find() - .filter(ArtCol::Inode.is_in(unchanged_music_inodes.clone())) - .all(&client) - .await - .unwrap() - { - artists_by_inode - .entry(row.inode) - .or_default() - .push(row.artist); - } - - let mut other_tags_by_inode: std::collections::HashMap> = - std::collections::HashMap::new(); - for row in other_tags::Entity::find() - .filter(OtCol::Inode.is_in(unchanged_music_inodes.clone())) - .all(&client) - .await - .unwrap() - { - other_tags_by_inode - .entry(row.inode) - .or_default() - .push((row.position, row.tag)); - } - - let mut pictures_by_inode: std::collections::HashMap> = - std::collections::HashMap::new(); - for row in pictures::Entity::find() - .filter(PicCol::Inode.is_in(unchanged_music_inodes)) - .all(&client) - .await - .unwrap() - { - pictures_by_inode.entry(row.inode).or_default().push(row); - } - - for (inode, mm_row) in mm_rows { - let ino = INodeNo(inode as u64); - let Some(item) = snapshot.get_mut(&ino) else { - continue; - }; - - let mut sorted_tags = other_tags_by_inode.remove(&inode).unwrap_or_default(); - sorted_tags.sort_by_key(|(pos, _)| *pos); - - let mut sorted_pics = pictures_by_inode.remove(&inode).unwrap_or_default(); - sorted_pics.sort_by_key(|p| p.position); - - let picture_block_headers: Vec<[u8; 4]> = sorted_pics - .iter() - .map(|p| { - let mut hdr = [0u8; 4]; - let len = p.block_header.len().min(4); - hdr[..len].copy_from_slice(&p.block_header[..len]); - hdr - }) - .collect(); - - let picture_data_ranges: Vec<(u64, u64)> = sorted_pics - .iter() - .map(|p| (p.data_offset as u64, p.data_length as u64)) - .collect(); - - let mut music_metadata = MusicMetadata { - artist: artists_by_inode.remove(&inode).unwrap_or_default(), - album: mm_row.album, - track_number: mm_row.track_number, - track_title: mm_row.track_title, - other_tags: sorted_tags.into_iter().map(|(_, tag)| tag).collect(), - header: mm_row.header, - picture_block_headers, - picture_data_ranges, - real_audio_start: mm_row.real_audio_start as u64, - vorbis_comment_offset: 0, - vorbis_comment_length: 0, - }; - music_metadata.find_vorbis_offsets(); - item.music_metadata = Some(music_metadata); - } + if unchanged_music_inodes.is_empty() { + return; } - // For unchanged items (hash matches), restore virtual paths from DB - // so that renames performed in a previous session are preserved. + let mm_rows: std::collections::HashMap = + music_metadata_entity::Entity::find() + .filter(MmCol::Inode.is_in(unchanged_music_inodes.clone())) + .all(client) + .await + .unwrap() + .into_iter() + .map(|m| (m.inode, m)) + .collect(); + + let mut artists_by_inode: std::collections::HashMap> = + std::collections::HashMap::new(); + for row in artists::Entity::find() + .filter(ArtCol::Inode.is_in(unchanged_music_inodes.clone())) + .all(client) + .await + .unwrap() + { + artists_by_inode + .entry(row.inode) + .or_default() + .push(row.artist); + } + + let mut other_tags_by_inode: std::collections::HashMap> = + std::collections::HashMap::new(); + for row in other_tags::Entity::find() + .filter(OtCol::Inode.is_in(unchanged_music_inodes.clone())) + .all(client) + .await + .unwrap() + { + other_tags_by_inode + .entry(row.inode) + .or_default() + .push((row.position, row.tag)); + } + + let mut pictures_by_inode: std::collections::HashMap> = + std::collections::HashMap::new(); + for row in pictures::Entity::find() + .filter(PicCol::Inode.is_in(unchanged_music_inodes)) + .all(client) + .await + .unwrap() + { + pictures_by_inode.entry(row.inode).or_default().push(row); + } + + for (inode, mm_row) in mm_rows { + let ino = INodeNo(inode as u64); + let Some(item) = snapshot.get_mut(&ino) else { + continue; + }; + + let mut sorted_tags = other_tags_by_inode.remove(&inode).unwrap_or_default(); + sorted_tags.sort_by_key(|(pos, _)| *pos); + + let mut sorted_pics = pictures_by_inode.remove(&inode).unwrap_or_default(); + sorted_pics.sort_by_key(|p| p.position); + + let picture_block_headers: Vec<[u8; 4]> = sorted_pics + .iter() + .map(|p| { + let mut hdr = [0u8; 4]; + let len = p.block_header.len().min(4); + hdr[..len].copy_from_slice(&p.block_header[..len]); + hdr + }) + .collect(); + + let picture_data_ranges: Vec<(u64, u64)> = sorted_pics + .iter() + .map(|p| (p.data_offset as u64, p.data_length as u64)) + .collect(); + + let mut music_metadata = MusicMetadata { + artist: artists_by_inode.remove(&inode).unwrap_or_default(), + album: mm_row.album, + track_number: mm_row.track_number, + track_title: mm_row.track_title, + other_tags: sorted_tags.into_iter().map(|(_, tag)| tag).collect(), + header: mm_row.header, + picture_block_headers, + picture_data_ranges, + real_audio_start: mm_row.real_audio_start as u64, + vorbis_comment_offset: 0, + vorbis_comment_length: 0, + }; + music_metadata.find_vorbis_offsets(); + item.music_metadata = Some(music_metadata); + } + } + + fn restore_virtual_paths( + snapshot: &mut BTreeMap, + db_items: &std::collections::HashMap, + source: &Path, + ) { let restorations: Vec<(INodeNo, String, PathBuf)> = db_items .values() .filter_map(|db_item| { let ino = INodeNo(db_item.inode as u64); + if ino == INodeNo::ROOT { + return None; + } snapshot .get(&ino) .filter(|item| item.hash as i64 == db_item.hash) @@ -296,7 +356,7 @@ impl LocalOrigin { .collect(); for (_, _, local_path) in &restorations { - LocalOrigin::ensure_virtual_dirs(local_path, Path::new(&source), &mut snapshot); + LocalOrigin::ensure_virtual_dirs(local_path, source, snapshot); } for (ino, name, local_path) in restorations { if let Some(item) = snapshot.get_mut(&ino) { @@ -305,15 +365,6 @@ impl LocalOrigin { item.local_path = local_path; } } - - let local_origin = LocalOrigin { - source: source.into(), - destination: destination.into(), - files: Arc::new(Mutex::new(snapshot)), - client, - }; - //dbg!(&local_origin); - Ok(local_origin) } async fn save_music_metadata( @@ -380,7 +431,7 @@ impl LocalOrigin { .await?; } - Ok(()) + return Ok(()); } fn fill_fileset( @@ -413,25 +464,20 @@ impl LocalOrigin { ) -> Result, io::Error> { let mut map = BTreeMap::new(); - 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(), - file_type: FileType::Directory, - metadata: fs::metadata(source)?, - music_metadata: None, - hash: 0, - }; - let local_root = LocalItem { - hash: local_root.compute_hash(), - ..local_root - }; + let local_root = LocalItem::new( + INodeNo::ROOT, + INodeNo::ROOT, + "/".to_string(), + source.to_path_buf(), + destination.to_path_buf(), + FileType::Directory, + fs::metadata(source)?, + None, + ); map.insert(INodeNo::ROOT, local_root); LocalOrigin::read_into_map(source, destination, &mut map)?; - Ok(map) + return Ok(map); } fn read_into_map( @@ -466,21 +512,16 @@ impl LocalOrigin { let inode = INodeNo(metadata.ino()); let parent_inode = LocalOrigin::ensure_virtual_dirs(&local_path, source, map); - let local_item = LocalItem { + let local_item = LocalItem::new( inode, parent_inode, name, - original_path: item_path.clone(), + item_path.clone(), local_path, file_type, metadata, music_metadata, - hash: 0, - }; - let local_item = LocalItem { - hash: local_item.compute_hash(), - ..local_item - }; + ); map.insert(inode, local_item); @@ -488,7 +529,7 @@ impl LocalOrigin { LocalOrigin::read_into_map(&item_path, destination, map)?; } } - Ok(()) + return Ok(()); } fn local_file_to_file_attr(local_file: &LocalItem) -> FileAttr { @@ -508,11 +549,11 @@ impl LocalOrigin { ino: local_file.inode, size, blocks: metadata.blocks(), - atime: atime, - mtime: mtime, - ctime: ctime, - crtime: crtime, - kind: kind, + atime, + mtime, + ctime, + crtime, + kind, perm: metadata.permissions().mode() as u16, nlink: metadata.nlink() as u32, uid: metadata.uid(), @@ -524,10 +565,19 @@ impl LocalOrigin { } fn file_type(file_type: FileType) -> fuser::FileType { - return match file_type { + match file_type { FileType::Directory => fuser::FileType::Directory, FileType::File => fuser::FileType::RegularFile, - }; + } + } + + fn run_db_blocking(future: F) -> F::Output { + return tokio::runtime::Builder::new_current_thread() + .enable_io() + .enable_time() + .build() + .unwrap() + .block_on(future); } fn read_bytes_at(path: &Path, offset: u64, len: usize) -> io::Result> { @@ -536,11 +586,82 @@ impl LocalOrigin { let mut buf = vec![0u8; len]; let n = f.read(&mut buf)?; buf.truncate(n); - Ok(buf) + + return Ok(buf); + } + + fn assemble_flac_read( + original_path: &Path, + header: &[u8], + pic_hdrs: &[[u8; 4]], + pic_ranges: &[(u64, u64)], + real_audio_start: u64, + offset: u64, + size: u32, + ) -> io::Result> { + let end = offset + size as u64; + let header_end = header.len() as u64; + + // Pure header read — most common for tag readers. + if end <= header_end { + return Ok(header[offset as usize..end as usize].to_vec()); + } + + let mut buf = Vec::with_capacity(size as usize); + if offset < header_end { + buf.extend_from_slice(&header[offset as usize..]); + } + + let mut virt_pos = header_end; + for (pic_hdr, (data_real_offset, data_len)) in pic_hdrs.iter().zip(pic_ranges.iter()) { + let pic_hdr_end = virt_pos + 4; + let pic_end = pic_hdr_end + data_len; + + if end <= virt_pos { + break; + } + if offset >= pic_end { + virt_pos = pic_end; + continue; + } + + let hdr_from = (offset.max(virt_pos) - virt_pos) as usize; + let hdr_to = ((end.min(pic_hdr_end)) - virt_pos) as usize; + if hdr_from < hdr_to { + buf.extend_from_slice(&pic_hdr[hdr_from..hdr_to.min(4)]); + } + + let data_start = offset.max(pic_hdr_end); + let data_end = end.min(pic_end); + if data_start < data_end { + let real_off = data_real_offset + (data_start - pic_hdr_end); + let bytes = LocalOrigin::read_bytes_at( + original_path, + real_off, + (data_end - data_start) as usize, + )?; + buf.extend_from_slice(&bytes); + } + + virt_pos = pic_end; + } + + let audio_start = offset.max(virt_pos); + if audio_start < end { + let real_off = real_audio_start + (audio_start - virt_pos); + let bytes = + LocalOrigin::read_bytes_at(original_path, real_off, (end - audio_start) as usize)?; + buf.extend_from_slice(&bytes); + } + + return Ok(buf); } fn parent_inode_from_path(local_path: &Path) -> INodeNo { - let components: Vec<_> = local_path.components().collect(); + let components: Vec<_> = local_path + .components() + .filter(|c| matches!(c, std::path::Component::Normal(_))) + .collect(); if components.len() <= 1 { return INodeNo::ROOT; } @@ -551,13 +672,15 @@ impl LocalOrigin { } parent_path.push_str(comp.as_os_str().to_str().unwrap_or_default()); } - LocalOrigin::virtual_inode(&parent_path) + + return LocalOrigin::virtual_inode(&parent_path); } fn virtual_inode(path: &str) -> INodeNo { let mut hasher = XxHash64::with_seed(5678); hasher.write(path.as_bytes()); - INodeNo(hasher.finish() | (1u64 << 63)) + + return INodeNo(hasher.finish() | (1u64 << 63)); } fn ensure_virtual_dirs( @@ -565,7 +688,10 @@ impl LocalOrigin { source: &Path, map: &mut BTreeMap, ) -> INodeNo { - let components: Vec<_> = local_path.components().collect(); + let components: Vec<_> = local_path + .components() + .filter(|c| matches!(c, std::path::Component::Normal(_))) + .collect(); if components.len() <= 1 { return INodeNo::ROOT; @@ -584,22 +710,16 @@ impl LocalOrigin { 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 - }; + let virt_item = LocalItem::new( + virt_ino, + current_parent, + comp_str.to_string(), + source.to_path_buf(), + PathBuf::from(¤t_path), + FileType::Directory, + fs::metadata(source).unwrap(), + None, + ); map.insert(virt_ino, virt_item); } @@ -715,16 +835,11 @@ impl Filesystem for LocalOrigin { if let Some(music_metadata) = updated_music_metadata { let client = self.client.clone(); let ino_i64 = ino.0 as i64; - tokio::runtime::Builder::new_current_thread() - .enable_io() - .enable_time() - .build() - .unwrap() - .block_on(async move { - LocalOrigin::save_music_metadata(ino_i64, &music_metadata, &client) - .await - .unwrap(); - }); + LocalOrigin::run_db_blocking(async move { + LocalOrigin::save_music_metadata(ino_i64, &music_metadata, &client) + .await + .unwrap(); + }); } reply.written(written); @@ -769,26 +884,38 @@ impl Filesystem for LocalOrigin { } }; - if offset == 0 { - let _ = reply.add(ino, 0, fuser::FileType::Directory, &Path::new(".")); - let _ = reply.add( - parent_inode, - 1, - fuser::FileType::Directory, - &Path::new(".."), - ); + // Offsets are 1-based cursors: "." = 1, ".." = 2, real entries = 3+. + // The kernel passes back the offset of the last entry it received; + // we return entries with offset > that value. + if offset < 1 { + if reply.add(ino, 1, fuser::FileType::Directory, ".") { + reply.ok(); + return; + } } + if offset < 2 { + if reply.add(parent_inode, 2, fuser::FileType::Directory, "..") { + reply.ok(); + return; + } + } + + let skip_count = if offset <= 2 { + 0 + } else { + (offset - 2) as usize + }; + for (i, (key, value)) in files .iter() .filter(|(_, v)| v.parent_inode == ino && v.inode != ino) - .skip(offset as usize) + .skip(skip_count) .enumerate() { - let entry_offset: u64 = 2 + i as u64; + let entry_offset = (skip_count + i + 3) 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 { + if reply.add(*key, entry_offset, file_type, &value.name) { break; } } @@ -859,23 +986,18 @@ impl Filesystem for LocalOrigin { drop(files); - tokio::runtime::Builder::new_current_thread() - .enable_time() - .enable_io() - .build() - .unwrap() - .block_on(async move { - use sea_orm::ActiveValue::Set; - ActiveModel { - inode: Set(inode_i64), - name: Set(new_name_owned), - local_path: Set(new_local_path_str), - ..Default::default() - } - .update(&client) - .await - .unwrap(); - }); + LocalOrigin::run_db_blocking(async move { + use sea_orm::ActiveValue::Set; + ActiveModel { + inode: Set(inode_i64), + name: Set(new_name_owned), + local_path: Set(new_local_path_str), + ..Default::default() + } + .update(&client) + .await + .unwrap(); + }); reply.ok(); } @@ -901,98 +1023,41 @@ impl Filesystem for LocalOrigin { }; let original_path = item.original_path.clone(); - let mm = item + let flac = item .music_metadata .as_ref() - .filter(|mm| mm.real_audio_start > 0); - let is_flac = mm.is_some(); - let header = mm.map(|mm| mm.header.clone()).unwrap_or_default(); - let pic_hdrs = mm - .map(|mm| mm.picture_block_headers.clone()) - .unwrap_or_default(); - let pic_ranges = mm - .map(|mm| mm.picture_data_ranges.clone()) - .unwrap_or_default(); - let real_audio_start = mm.map(|mm| mm.real_audio_start).unwrap_or(0); + .filter(|mm| mm.real_audio_start > 0) + .map(|mm| { + ( + mm.header.clone(), + mm.picture_block_headers.clone(), + mm.picture_data_ranges.clone(), + mm.real_audio_start, + ) + }); drop(files); - if !is_flac { + // Non-FLAC files are streamed straight from the real file. + let Some((header, pic_hdrs, pic_ranges, real_audio_start)) = flac else { match LocalOrigin::read_bytes_at(&original_path, offset, size as usize) { Ok(bytes) => reply.data(&bytes), Err(_) => reply.error(Errno::EIO), } return; + }; + + match LocalOrigin::assemble_flac_read( + &original_path, + &header, + &pic_hdrs, + &pic_ranges, + real_audio_start, + offset, + size, + ) { + Ok(bytes) => reply.data(&bytes), + Err(_) => reply.error(Errno::EIO), } - - let end = offset + size as u64; - let header_end = header.len() as u64; - - // Pure header read — most common for tag readers - if end <= header_end { - reply.data(&header[offset as usize..end as usize]); - return; - } - - // Multi-region read: walk [header][pic_hdr+pic_data]...[audio] and collect bytes - let mut buf = Vec::with_capacity(size as usize); - - if offset < header_end { - buf.extend_from_slice(&header[offset as usize..]); - } - - let mut virt_pos = header_end; - for (pic_hdr, (data_real_offset, data_len)) in pic_hdrs.iter().zip(pic_ranges.iter()) { - let pic_hdr_end = virt_pos + 4; - let pic_end = pic_hdr_end + data_len; - - if end <= virt_pos { - break; - } - if offset >= pic_end { - virt_pos = pic_end; - continue; - } - - let hdr_from = (offset.max(virt_pos) - virt_pos) as usize; - let hdr_to = ((end.min(pic_hdr_end)) - virt_pos) as usize; - if hdr_from < hdr_to { - buf.extend_from_slice(&pic_hdr[hdr_from..hdr_to.min(4)]); - } - - let data_start = offset.max(pic_hdr_end); - let data_end = end.min(pic_end); - if data_start < data_end { - let real_off = data_real_offset + (data_start - pic_hdr_end); - match LocalOrigin::read_bytes_at( - &original_path, - real_off, - (data_end - data_start) as usize, - ) { - Ok(bytes) => buf.extend_from_slice(&bytes), - Err(_) => { - reply.error(Errno::EIO); - return; - } - } - } - - virt_pos = pic_end; - } - - let audio_start = offset.max(virt_pos); - if audio_start < end { - let real_off = real_audio_start + (audio_start - virt_pos); - match LocalOrigin::read_bytes_at(&original_path, real_off, (end - audio_start) as usize) - { - Ok(bytes) => buf.extend_from_slice(&bytes), - Err(_) => { - reply.error(Errno::EIO); - return; - } - } - } - - reply.data(&buf); } fn lookup( @@ -1004,7 +1069,7 @@ impl Filesystem for LocalOrigin { ) { println!("lookup(parent={}, name={})", parent, name.display()); - return match self + match self .files .lock() .unwrap() @@ -1018,7 +1083,7 @@ impl Filesystem for LocalOrigin { reply.entry(&ttl, &attr, Generation(0)); } None => reply.error(Errno::ENOENT), - }; + } } } @@ -1113,23 +1178,19 @@ 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 = LocalOrigin::parent_inode_from_path(&local_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, - file_type: match entity.file_type.as_str() { - "directory" => FileType::Directory, - _ => FileType::File, - }, - metadata, - music_metadata: None, - hash: 0, + let file_type = match entity.file_type.as_str() { + "directory" => FileType::Directory, + _ => FileType::File, }; - LocalItem { - hash: local_item.compute_hash(), - ..local_item - } + LocalItem::new( + INodeNo(entity.inode as u64), + parent_inode, + entity.name, + PathBuf::from(entity.original_path), + local_path, + file_type, + metadata, + None, + ) } } diff --git a/src/music_metadata.rs b/src/music_metadata.rs index e82aab1..1329ff1 100644 --- a/src/music_metadata.rs +++ b/src/music_metadata.rs @@ -11,6 +11,20 @@ use symphonia::core::{ probe::Hint, }; +const BLOCK_PADDING: u8 = 1; +const BLOCK_VORBIS_COMMENT: u8 = 4; +const BLOCK_PICTURE: u8 = 6; +const BLOCK_LAST_FLAG: u8 = 0x80; +const BLOCK_TYPE_MASK: u8 = 0x7f; + +/// Parse a 4-byte FLAC metadata block header into (is_last, block_type, data_length). +fn read_block_header(hdr: &[u8; 4]) -> (bool, u8, u64) { + let is_last = (hdr[0] & BLOCK_LAST_FLAG) != 0; + let block_type = hdr[0] & BLOCK_TYPE_MASK; + let length = u32::from_be_bytes([0, hdr[1], hdr[2], hdr[3]]) as u64; + (is_last, block_type, length) +} + #[derive(Debug, Default, Clone)] pub struct MusicMetadata { pub artist: Vec, @@ -94,10 +108,8 @@ impl MusicMetadata { if cursor.read_exact(&mut hdr).is_err() { break; } - let is_last = (hdr[0] & 0x80) != 0; - let block_type = hdr[0] & 0x7f; - let length = u32::from_be_bytes([0, hdr[1], hdr[2], hdr[3]]) as u64; - if block_type == 4 { + let (is_last, block_type, length) = read_block_header(&hdr); + if block_type == BLOCK_VORBIS_COMMENT { self.vorbis_comment_offset = cursor.position(); self.vorbis_comment_length = length; return; @@ -146,12 +158,10 @@ fn parse_flac(path: &Path) -> Option { loop { let mut hdr = [0u8; 4]; f.read_exact(&mut hdr).ok()?; - let is_last = (hdr[0] & 0x80) != 0; - let block_type = hdr[0] & 0x7f; - let length = u32::from_be_bytes([0, hdr[1], hdr[2], hdr[3]]) as u64; + let (is_last, block_type, length) = read_block_header(&hdr); pos += 4; - if block_type == 6 { + if block_type == BLOCK_PICTURE { // PICTURE: keep block header (we'll fix is_last later), record data range picture_block_headers.push(hdr); picture_data_ranges.push((pos, length)); @@ -159,8 +169,8 @@ fn parse_flac(path: &Path) -> Option { } else { let mut data = vec![0u8; length as usize]; f.read_exact(&mut data).ok()?; - // Skip VORBIS_COMMENT (type 4) — we rebuild it - if block_type != 4 { + // Skip VORBIS_COMMENT — we rebuild it + if block_type != BLOCK_VORBIS_COMMENT { other_blocks.push((block_type, data)); } } @@ -173,7 +183,7 @@ fn parse_flac(path: &Path) -> Option { // Fix is_last on the last picture block header: it must be 1 when audio follows if let Some(last_hdr) = picture_block_headers.last_mut() { - last_hdr[0] = 0x80 | (last_hdr[0] & 0x7f); + last_hdr[0] = BLOCK_LAST_FLAG | (last_hdr[0] & BLOCK_TYPE_MASK); } Some(FlacParsed { @@ -191,8 +201,8 @@ fn build_flac_header(blocks: Vec<(u8, Vec)>, metadata: &MusicMetadata) -> (V let vorbis_len = vorbis.len() as u64; let mut patched: Vec<(u8, Vec)> = blocks; - patched.push((4, vorbis)); - patched.push((1, vec![0u8; PADDING_SIZE])); // PADDING — allows metaflac in-place writes + patched.push((BLOCK_VORBIS_COMMENT, vorbis)); + patched.push((BLOCK_PADDING, vec![0u8; PADDING_SIZE])); // allows metaflac in-place writes let vorbis_idx = patched.len() - 2; let has_pictures = !metadata.picture_data_ranges.is_empty(); @@ -203,7 +213,7 @@ fn build_flac_header(blocks: Vec<(u8, Vec)>, metadata: &MusicMetadata) -> (V let mut vorbis_offset = 0u64; for (i, (block_type, data)) in patched.iter().enumerate() { let is_last_block = i == last && !has_pictures; - let flag: u8 = if is_last_block { 0x80 } else { 0x00 }; + let flag: u8 = if is_last_block { BLOCK_LAST_FLAG } else { 0x00 }; let length = data.len() as u32; if i == vorbis_idx { vorbis_offset = out.len() as u64 + 4; // data starts after 4-byte block header @@ -273,14 +283,12 @@ fn extract_non_vorbis_blocks(header: &[u8]) -> Vec<(u8, Vec)> { if cursor.read_exact(&mut hdr).is_err() { break; } - let is_last = (hdr[0] & 0x80) != 0; - let block_type = hdr[0] & 0x7f; - let length = u32::from_be_bytes([0, hdr[1], hdr[2], hdr[3]]) as usize; - let mut data = vec![0u8; length]; + let (is_last, block_type, length) = read_block_header(&hdr); + let mut data = vec![0u8; length as usize]; if cursor.read_exact(&mut data).is_err() { break; } - if block_type != 4 && block_type != 1 { + if block_type != BLOCK_VORBIS_COMMENT && block_type != BLOCK_PADDING { blocks.push((block_type, data)); } if is_last {