feat(disc): remember a disc after it has been ejected
A disc is the one medium QBZ can play that leaves nothing behind: no path, no row, no file. Everything learned about it was thrown away when the tray opened and re-derived from the network next time. That is fine for names that came from MusicBrainz and not fine for names that came from a human. One DiscID can name several pressings — this album answers with four — so the moment there is a button to pick the right one, or a wizard that lets a title be fixed before it is written, the choice has to outlive the eject. Otherwise correcting a disc is a toy: fix it, take it out, put it back, it is wrong again. A CACHE, not a second source of truth, with one rule that makes it safe: a row the user edited is never overwritten by an automatic lookup. put_auto refuses to touch an edited row and only put_user can replace one, enforced in the store rather than at the call sites so a future third lookup path cannot forget. Identity is the TOC fingerprint: pure geometry, no network, available before anything has been looked up, and identical on both platforms. It names the GEOMETRY rather than the pressing, which is the point — "for this geometry the user chose pressing 2" is the fact worth keeping. Global rather than per-user, because which record is in the drive does not depend on who is logged in. Seven tests, including the one this exists for. disc_identity carries WHICH disc is open for the two features that name the medium rather than the session; nothing there persists. cc
This commit is contained in:
2 files changed
+495
No files matched your search
@@ -0,0 +1,80 @@
|
||||
//! WHICH disc the open ephemeral session came from.
|
||||
//!
|
||||
//! The session itself deliberately knows nothing about discs — `adopt_tracks`
|
||||
//! takes a label and a track list, and that is the whole point: a disc is not
|
||||
//! a second kind of session. But two features need to name the medium rather
|
||||
//! than the session:
|
||||
//!
|
||||
//! * the metadata button, which has to write its correction under a key that
|
||||
//! survives the eject (`qbz_disc::store`);
|
||||
//! * the rip wizard, which reads that same key for its defaults.
|
||||
//!
|
||||
//! So the identity lives HERE, beside the session rather than inside it: one
|
||||
//! process-global slot, set when a disc opens and cleared when the session
|
||||
//! closes. Nothing persists — the STORE is what persists; this only remembers
|
||||
//! which row is in the drive right now.
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// What kind of medium is open. The two are not interchangeable: a CD has a
|
||||
/// MusicBrainz DiscID and can be ripped, a SACD image has neither.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DiscKind {
|
||||
Cd,
|
||||
Sacd,
|
||||
}
|
||||
|
||||
impl DiscKind {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
DiscKind::Cd => "cd",
|
||||
DiscKind::Sacd => "sacd",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiscIdentity {
|
||||
/// TOC geometry hash — the key into `qbz_disc::store`. Available before
|
||||
/// anything has been looked up, and identical for two pressings that share
|
||||
/// a table of contents (which is exactly why remembering the user's choice
|
||||
/// under it is worth doing).
|
||||
pub fingerprint: String,
|
||||
/// MusicBrainz DiscID. `None` for a SACD image, which names itself.
|
||||
pub disc_id: Option<String>,
|
||||
pub kind: DiscKind,
|
||||
}
|
||||
|
||||
static CURRENT: Mutex<Option<DiscIdentity>> = Mutex::new(None);
|
||||
|
||||
pub fn set(identity: DiscIdentity) {
|
||||
if let Ok(mut slot) = CURRENT.lock() {
|
||||
*slot = Some(identity);
|
||||
}
|
||||
publish(true);
|
||||
}
|
||||
|
||||
pub fn current() -> Option<DiscIdentity> {
|
||||
CURRENT.lock().ok()?.clone()
|
||||
}
|
||||
|
||||
/// The session closed, or a folder replaced the disc. Called from the SAME
|
||||
/// place that tears the session down, so the two cannot disagree about whether
|
||||
/// a disc is open.
|
||||
pub fn clear() {
|
||||
if let Ok(mut slot) = CURRENT.lock() {
|
||||
*slot = None;
|
||||
}
|
||||
publish(false);
|
||||
}
|
||||
|
||||
/// Tell the UI whether what is open is a DISC.
|
||||
///
|
||||
/// The pane needs that question answered for BOTH media, and neither existing
|
||||
/// flag does it: `local_ephemeral_is_cd` is narrower on purpose (only a
|
||||
/// physical CD can be ripped) and there is nothing at all for a SACD image.
|
||||
/// Without it the "correct the details" button appeared on an opened FOLDER,
|
||||
/// where there is no disc to correct and nothing to write a correction under.
|
||||
fn publish(is_disc: bool) {
|
||||
crate::local_bridge::ui(move |mut b| b.as_mut().set_local_session_is_disc(is_disc));
|
||||
}
|
||||
Reference in new issue
Block a user