use std::path::PathBuf;
use std::sync::Arc;
use mineral_channel_core::MusicChannel;
use mineral_model::{BitRate, Song, SongId};
use parking_lot::Mutex;
use rustc_hash::FxHashSet;
use super::assemble;
use super::worker::run;
use crate::media_cache::cache_key;
pub(super) struct TagJob {
pub(super) song: Song,
pub(super) path: PathBuf,
pub(super) quality: BitRate,
}
impl TagJob {
pub(super) fn song_id(&self) -> &SongId {
&self.song.id
}
}
pub(super) fn job_key(
song_id: &mineral_model::SongId,
quality: BitRate,
path: &std::path::Path,
) -> String {
format!("{}:{}", cache_key(song_id, quality), path.display())
}
pub(super) struct QueueInner {
tx: tokio::sync::mpsc::UnboundedSender<TagJob>,
pub(super) inflight: Arc<Mutex<FxHashSet<String>>>,
}
#[derive(Clone)]
pub(crate) struct TaggingQueue {
pub(super) inner: Option<Arc<QueueInner>>,
}
impl TaggingQueue {
pub(crate) fn spawn(
enabled: bool,
channels: &[Arc<dyn MusicChannel>],
http: Option<&reqwest::Client>,
workers: usize,
) -> Self {
if !enabled {
return Self { inner: None };
}
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let rx = Arc::new(tokio::sync::Mutex::new(rx));
let inflight = Arc::new(Mutex::new(FxHashSet::default()));
let album_cache = assemble::AlbumCache::default();
for _ in 0..workers.max(1) {
tokio::spawn(run(
Arc::clone(&rx),
channels.to_vec(),
http.cloned(),
Arc::clone(&inflight),
album_cache.clone(),
));
}
Self {
inner: Some(Arc::new(QueueInner { tx, inflight })),
}
}
pub(crate) fn enqueue(&self, song: Song, path: PathBuf, quality: BitRate) -> bool {
let key = job_key(&song.id, quality, &path);
self.send(
TagJob {
song,
path,
quality,
},
&key,
)
}
fn send(&self, job: TagJob, key: &str) -> bool {
let Some(inner) = &self.inner else {
return false;
};
if !inner.inflight.lock().insert(key.to_owned()) {
return false;
}
if inner.tx.send(job).is_err() {
inner.inflight.lock().remove(key);
return false;
}
true
}
}