Implement write method to support metadata update

This commit is contained in:
Alexander
2026-06-27 00:16:04 +02:00
parent 9737e26bc9
commit d49018bfc7
2 changed files with 365 additions and 5 deletions
+235
View File
@@ -161,6 +161,121 @@ impl LocalOrigin {
}
}
// 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<i64> = db_items
.values()
.filter_map(|db_item| {
let ino = INodeNo(db_item.inode as u64);
snapshot
.get(&ino)
.filter(|item| {
item.hash as i64 == db_item.hash && item.music_metadata.is_some()
})
.map(|_| db_item.inode)
})
.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<i64, music_metadata_entity::Model> =
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<i64, Vec<String>> =
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<i64, Vec<(i32, String)>> =
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<i64, Vec<pictures::Model>> =
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);
}
}
// For unchanged items (hash matches), restore virtual paths from DB
// so that renames performed in a previous session are preserved.
let restorations: Vec<(INodeNo, String, PathBuf)> = db_items
@@ -496,6 +611,125 @@ impl LocalOrigin {
}
impl Filesystem for LocalOrigin {
fn open(
&self,
_req: &Request,
ino: INodeNo,
_flags: fuser::OpenFlags,
reply: fuser::ReplyOpen,
) {
if self.files.lock().unwrap().contains_key(&ino) {
reply.opened(fuser::FileHandle(ino.0), fuser::FopenFlags::empty());
} else {
reply.error(Errno::ENOENT);
}
}
fn setattr(
&self,
_req: &Request,
ino: INodeNo,
_mode: Option<u32>,
_uid: Option<u32>,
_gid: Option<u32>,
_size: Option<u64>,
_atime: Option<fuser::TimeOrNow>,
_mtime: Option<fuser::TimeOrNow>,
_ctime: Option<std::time::SystemTime>,
_fh: Option<fuser::FileHandle>,
_crtime: Option<std::time::SystemTime>,
_chgtime: Option<std::time::SystemTime>,
_bkuptime: Option<std::time::SystemTime>,
_flags: Option<fuser::BsdFileFlags>,
reply: fuser::ReplyAttr,
) {
match self.files.lock().unwrap().get(&ino) {
Some(file) => reply.attr(
&Duration::new(1, 0),
&LocalOrigin::local_file_to_file_attr(file),
),
None => reply.error(Errno::ENOENT),
}
}
fn write(
&self,
_req: &Request,
ino: INodeNo,
_fh: fuser::FileHandle,
offset: u64,
data: &[u8],
_write_flags: fuser::WriteFlags,
_flags: fuser::OpenFlags,
_lock_owner: Option<fuser::LockOwner>,
reply: fuser::ReplyWrite,
) {
let written = data.len() as u32;
let write_start = offset;
let write_end = write_start + data.len() as u64;
let updated_music_metadata = {
let mut files = self.files.lock().unwrap();
let item = match files.get_mut(&ino) {
Some(item) => item,
None => {
reply.written(written);
return;
}
};
let music_metadata = match &mut item.music_metadata {
Some(mm) if mm.vorbis_comment_length > 0 => mm,
_ => {
reply.written(written);
return;
}
};
let vc_data_offset = music_metadata.vorbis_comment_offset;
let vc_hdr_offset = vc_data_offset - 4;
// Must cover the 4-byte block header to read the (possibly new) data length
if write_start <= vc_hdr_offset && write_end >= vc_data_offset {
let hdr_from = (vc_hdr_offset - write_start) as usize;
let new_length = u32::from_be_bytes([
0,
data[hdr_from + 1],
data[hdr_from + 2],
data[hdr_from + 3],
]) as u64;
let vc_data_end = vc_data_offset + new_length;
if write_end >= vc_data_end {
let from = (vc_data_offset - write_start) as usize;
let to = (vc_data_end - write_start) as usize;
music_metadata.update_from_vorbis_comment_data(&data[from..to]);
Some(music_metadata.clone())
} else {
None
}
} else {
None
}
};
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();
});
}
reply.written(written);
}
fn getattr(
&self,
_req: &fuser::Request,
@@ -627,6 +861,7 @@ impl Filesystem for LocalOrigin {
tokio::runtime::Builder::new_current_thread()
.enable_time()
.enable_io()
.build()
.unwrap()
.block_on(async move {