Files
MusicFS/src/music/db.rs
T
2026-06-30 12:17:48 +02:00

211 lines
6.6 KiB
Rust

use std::collections::{BTreeMap, HashMap};
use fuser::INodeNo;
use sea_orm::entity::prelude::*;
use crate::db::entities::Model;
use crate::item::Item;
use crate::music::metadata::MusicMetadata;
use crate::music::metadata::db::{
artists, music_metadata as music_metadata_entity, other_tags, pictures,
};
use tracing::{debug, error};
pub async fn save_music_metadata(
inode: i64,
music_metadata: &MusicMetadata,
client: &sea_orm::DatabaseConnection,
) -> Result<(), sea_orm::DbErr> {
use sea_orm::ActiveValue::Set;
music_metadata_entity::Entity::delete_by_id(inode)
.exec(client)
.await?;
music_metadata_entity::Entity::insert(music_metadata_entity::ActiveModel {
inode: Set(inode),
track_title: Set(music_metadata.track_title.clone()),
album: Set(music_metadata.album.clone()),
track_number: Set(music_metadata.track_number),
header: Set(music_metadata.header.clone()),
real_audio_start: Set(music_metadata.real_audio_start as i64),
})
.exec(client)
.await?;
if !music_metadata.artist.is_empty() {
artists::Entity::insert_many(music_metadata.artist.iter().map(|a| artists::ActiveModel {
inode: Set(inode),
artist: Set(a.clone()),
}))
.exec(client)
.await?;
}
if !music_metadata.other_tags.is_empty() {
other_tags::Entity::insert_many(music_metadata.other_tags.iter().enumerate().map(
|(pos, tag)| other_tags::ActiveModel {
inode: Set(inode),
position: Set(pos as i32),
tag: Set(tag.clone()),
},
))
.exec(client)
.await?;
}
if !music_metadata.picture_block_headers.is_empty() {
pictures::Entity::insert_many(
music_metadata
.picture_block_headers
.iter()
.zip(music_metadata.picture_data_ranges.iter())
.enumerate()
.map(|(pos, (hdr, (offset, len)))| pictures::ActiveModel {
inode: Set(inode),
position: Set(pos as i32),
block_header: Set(hdr.clone()),
data_offset: Set(*offset as i64),
data_length: Set(*len as i64),
}),
)
.exec(client)
.await?;
}
return Ok(());
}
pub async fn restore_music_metadata_from_db(
snapshot: &mut BTreeMap<INodeNo, Item>,
db_items: &HashMap<i64, Model>,
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;
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() {
return;
}
let mm_rows: HashMap<i64, music_metadata_entity::Model> =
match music_metadata_entity::Entity::find()
.filter(MmCol::Inode.is_in(unchanged_music_inodes.clone()))
.all(client)
.await
{
Ok(rows) => rows.into_iter().map(|m| (m.inode, m)).collect(),
Err(e) => {
error!(error = %e, "restore_music_metadata: mm rows query failed");
return;
}
};
let mut artists_by_inode: HashMap<i64, Vec<String>> = HashMap::new();
let artist_rows = match artists::Entity::find()
.filter(ArtCol::Inode.is_in(unchanged_music_inodes.clone()))
.all(client)
.await
{
Ok(r) => r,
Err(e) => {
error!(error = %e, "restore_music_metadata: artists query failed");
vec![]
}
};
for row in artist_rows {
artists_by_inode
.entry(row.inode)
.or_default()
.push(row.artist);
}
let mut other_tags_by_inode: HashMap<i64, Vec<(i32, String)>> = HashMap::new();
let other_tag_rows = match other_tags::Entity::find()
.filter(OtCol::Inode.is_in(unchanged_music_inodes.clone()))
.all(client)
.await
{
Ok(r) => r,
Err(e) => {
error!(error = %e, "restore_music_metadata: other_tags query failed");
vec![]
}
};
for row in other_tag_rows {
other_tags_by_inode
.entry(row.inode)
.or_default()
.push((row.position, row.tag));
}
let mut pictures_by_inode: HashMap<i64, Vec<pictures::Model>> = HashMap::new();
let picture_rows = match pictures::Entity::find()
.filter(PicCol::Inode.is_in(unchanged_music_inodes))
.all(client)
.await
{
Ok(r) => r,
Err(e) => {
error!(error = %e, "restore_music_metadata: pictures query failed");
vec![]
}
};
for row in picture_rows {
pictures_by_inode.entry(row.inode).or_default().push(row);
}
debug!(restored = mm_rows.len(), "restored music metadata from db");
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<Vec<u8>> =
sorted_pics.iter().map(|p| p.block_header.clone()).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_artist: None,
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);
}
}