Updates for the "full flow"
This commit is contained in:
@@ -9,6 +9,6 @@ pub mod matching;
|
||||
|
||||
pub use error::{WatchError, WatchResult};
|
||||
pub use library::{
|
||||
LibrarySummary, WatchListEntry, add_album, add_artist, add_track, library_summary, list_items,
|
||||
remove_item,
|
||||
AddSummary, LibrarySummary, WatchListEntry, add_album, add_artist, add_track,
|
||||
library_summary, list_items, remove_item,
|
||||
};
|
||||
|
||||
377
src/library.rs
377
src/library.rs
@@ -21,12 +21,28 @@ pub struct WatchListEntry {
|
||||
pub added_at: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
/// Summary of how many tracks were added when watching an artist or album.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AddSummary {
|
||||
pub tracks_added: u64,
|
||||
pub tracks_already_owned: u64,
|
||||
pub errors: u64,
|
||||
}
|
||||
|
||||
impl fmt::Display for AddSummary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"added: {}, already owned: {}, errors: {}",
|
||||
self.tracks_added, self.tracks_already_owned, self.errors,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Summary statistics for the library.
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct LibrarySummary {
|
||||
pub total_artists: u64,
|
||||
pub total_albums: u64,
|
||||
pub total_tracks: u64,
|
||||
pub total_items: u64,
|
||||
pub wanted: u64,
|
||||
pub available: u64,
|
||||
pub downloaded: u64,
|
||||
@@ -35,8 +51,7 @@ pub struct LibrarySummary {
|
||||
|
||||
impl fmt::Display for LibrarySummary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Monitored: {} artists, {} albums, {} tracks",
|
||||
self.total_artists, self.total_albums, self.total_tracks)?;
|
||||
writeln!(f, "Total tracked: {} items", self.total_items)?;
|
||||
writeln!(f, " Wanted: {}", self.wanted)?;
|
||||
writeln!(f, " Available: {}", self.available)?;
|
||||
writeln!(f, " Downloaded: {}", self.downloaded)?;
|
||||
@@ -44,122 +59,156 @@ impl fmt::Display for LibrarySummary {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an artist to the watchlist. Auto-detects if already owned.
|
||||
/// Add an artist to the watchlist by expanding into individual track wanted items.
|
||||
///
|
||||
/// If `musicbrainz_id` is provided, it will be stored with the artist record.
|
||||
/// If `name` is empty but `musicbrainz_id` is provided and a `provider` is given,
|
||||
/// the name will be looked up from MusicBrainz.
|
||||
/// Fetches the artist's discography from the provider, then for each release
|
||||
/// fetches the tracklist and adds each track as a separate Wanted item.
|
||||
pub async fn add_artist(
|
||||
conn: &DatabaseConnection,
|
||||
name: Option<&str>,
|
||||
musicbrainz_id: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
) -> WatchResult<WatchListEntry> {
|
||||
provider: &impl MetadataProvider,
|
||||
) -> WatchResult<AddSummary> {
|
||||
let (resolved_name, resolved_mbid) =
|
||||
resolve_artist_info(name, musicbrainz_id, provider).await?;
|
||||
|
||||
let artist = queries::artists::upsert(conn, &resolved_name, resolved_mbid.as_deref()).await?;
|
||||
|
||||
let is_owned = matching::artist_is_owned(conn, &resolved_name).await?;
|
||||
let item = queries::wanted::add(conn, ItemType::Artist, Some(artist.id), None, None).await?;
|
||||
|
||||
let status = if is_owned {
|
||||
queries::wanted::update_status(conn, item.id, WantedStatus::Owned).await?;
|
||||
WantedStatus::Owned
|
||||
} else {
|
||||
WantedStatus::Wanted
|
||||
// Get artist MBID — either provided or from the search
|
||||
let artist_mbid = resolved_mbid.or_else(|| artist.musicbrainz_id.clone());
|
||||
let artist_mbid = match artist_mbid {
|
||||
Some(mbid) => mbid,
|
||||
None => {
|
||||
// Search for the artist to get MBID
|
||||
let results = provider
|
||||
.search_artist(&resolved_name, 1)
|
||||
.await
|
||||
.map_err(|e| WatchError::Other(format!("artist search failed: {e}")))?;
|
||||
results
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|a| a.mbid)
|
||||
.ok_or_else(|| WatchError::Other(format!("artist '{}' not found on MusicBrainz", resolved_name)))?
|
||||
}
|
||||
};
|
||||
|
||||
if is_owned {
|
||||
tracing::info!(name = %resolved_name, "artist already in library, marked as owned");
|
||||
} else {
|
||||
tracing::info!(name = %resolved_name, "artist added to watchlist");
|
||||
tracing::info!(name = %resolved_name, mbid = %artist_mbid, "fetching discography");
|
||||
|
||||
// Fetch all releases
|
||||
let releases = provider
|
||||
.get_artist_releases(&artist_mbid, 100)
|
||||
.await
|
||||
.map_err(|e| WatchError::Other(format!("failed to fetch discography: {e}")))?;
|
||||
|
||||
tracing::info!(count = releases.len(), "found releases");
|
||||
|
||||
let mut summary = AddSummary::default();
|
||||
|
||||
for release in &releases {
|
||||
tracing::info!(title = %release.title, mbid = %release.mbid, "fetching tracks");
|
||||
|
||||
let tracks = match provider.get_release_tracks(&release.mbid).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
tracing::warn!(release = %release.title, error = %e, "failed to fetch tracks");
|
||||
summary.errors += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
for track in &tracks {
|
||||
match add_track_inner(conn, &resolved_name, &track.title, Some(&track.recording_mbid)).await {
|
||||
Ok(true) => summary.tracks_added += 1,
|
||||
Ok(false) => summary.tracks_already_owned += 1,
|
||||
Err(e) => {
|
||||
tracing::warn!(track = %track.title, error = %e, "failed to add track");
|
||||
summary.errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(WatchListEntry {
|
||||
id: item.id,
|
||||
item_type: ItemType::Artist,
|
||||
name: artist.name,
|
||||
artist_name: None,
|
||||
status,
|
||||
added_at: item.added_at,
|
||||
})
|
||||
tracing::info!(%summary, "artist watch complete");
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
/// Add an album to the watchlist. Auto-detects if already owned.
|
||||
/// Add an album to the watchlist by expanding into individual track wanted items.
|
||||
///
|
||||
/// If `musicbrainz_id` is provided and a `provider` is given, album and artist
|
||||
/// details will be resolved from MusicBrainz.
|
||||
/// Fetches the album's tracklist and adds each track as a separate Wanted item.
|
||||
pub async fn add_album(
|
||||
conn: &DatabaseConnection,
|
||||
artist_name: Option<&str>,
|
||||
album_name: Option<&str>,
|
||||
musicbrainz_id: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
) -> WatchResult<WatchListEntry> {
|
||||
provider: &impl MetadataProvider,
|
||||
) -> WatchResult<AddSummary> {
|
||||
let (resolved_album, resolved_artist, resolved_mbid) =
|
||||
resolve_album_info(artist_name, album_name, musicbrainz_id, provider).await?;
|
||||
|
||||
let artist = queries::artists::upsert(conn, &resolved_artist, None).await?;
|
||||
let album = queries::albums::upsert(
|
||||
conn,
|
||||
&resolved_album,
|
||||
&resolved_artist,
|
||||
resolved_mbid.as_deref(),
|
||||
Some(artist.id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let is_owned = matching::album_is_owned(conn, &resolved_artist, &resolved_album).await?;
|
||||
let item = queries::wanted::add(
|
||||
conn,
|
||||
ItemType::Album,
|
||||
Some(artist.id),
|
||||
Some(album.id),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let status = if is_owned {
|
||||
queries::wanted::update_status(conn, item.id, WantedStatus::Owned).await?;
|
||||
WantedStatus::Owned
|
||||
} else {
|
||||
WantedStatus::Wanted
|
||||
// Get release MBID
|
||||
let release_mbid = match resolved_mbid {
|
||||
Some(mbid) => mbid,
|
||||
None => {
|
||||
let results = provider
|
||||
.search_release(&resolved_artist, &resolved_album)
|
||||
.await
|
||||
.map_err(|e| WatchError::Other(format!("album search failed: {e}")))?;
|
||||
results
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|r| r.mbid)
|
||||
.ok_or_else(|| WatchError::Other(format!("album '{}' not found on MusicBrainz", resolved_album)))?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(WatchListEntry {
|
||||
id: item.id,
|
||||
item_type: ItemType::Album,
|
||||
name: album.name,
|
||||
artist_name: Some(artist.name),
|
||||
status,
|
||||
added_at: item.added_at,
|
||||
})
|
||||
tracing::info!(album = %resolved_album, artist = %resolved_artist, mbid = %release_mbid, "fetching tracks");
|
||||
|
||||
let tracks = provider
|
||||
.get_release_tracks(&release_mbid)
|
||||
.await
|
||||
.map_err(|e| WatchError::Other(format!("failed to fetch tracks: {e}")))?;
|
||||
|
||||
let mut summary = AddSummary::default();
|
||||
|
||||
for track in &tracks {
|
||||
match add_track_inner(conn, &resolved_artist, &track.title, Some(&track.recording_mbid)).await {
|
||||
Ok(true) => summary.tracks_added += 1,
|
||||
Ok(false) => summary.tracks_already_owned += 1,
|
||||
Err(e) => {
|
||||
tracing::warn!(track = %track.title, error = %e, "failed to add track");
|
||||
summary.errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(%summary, "album watch complete");
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
/// Add a track to the watchlist. Auto-detects if already owned.
|
||||
///
|
||||
/// If `musicbrainz_id` is provided and a `provider` is given, track and artist
|
||||
/// details will be resolved from MusicBrainz via `get_recording`.
|
||||
/// Add a single track to the watchlist. Auto-detects if already owned.
|
||||
pub async fn add_track(
|
||||
conn: &DatabaseConnection,
|
||||
artist_name: Option<&str>,
|
||||
title: Option<&str>,
|
||||
musicbrainz_id: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
provider: &impl MetadataProvider,
|
||||
) -> WatchResult<WatchListEntry> {
|
||||
let (resolved_title, resolved_artist, resolved_mbid) =
|
||||
resolve_track_info(artist_name, title, musicbrainz_id, provider).await?;
|
||||
|
||||
let artist = queries::artists::upsert(conn, &resolved_artist, None).await?;
|
||||
|
||||
let is_owned = matching::track_is_owned(conn, &resolved_artist, &resolved_title).await?;
|
||||
let item = queries::wanted::add(conn, ItemType::Track, Some(artist.id), None, None).await?;
|
||||
|
||||
// Store the MBID on the artist if we got one from the recording lookup
|
||||
if let Some(ref mbid) = resolved_mbid {
|
||||
tracing::debug!(mbid = %mbid, "recording MBID stored");
|
||||
}
|
||||
let item = queries::wanted::add(
|
||||
conn,
|
||||
ItemType::Track,
|
||||
&resolved_title,
|
||||
resolved_mbid.as_deref(),
|
||||
Some(artist.id),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let status = if is_owned {
|
||||
queries::wanted::update_status(conn, item.id, WantedStatus::Owned).await?;
|
||||
@@ -178,41 +227,72 @@ pub async fn add_track(
|
||||
})
|
||||
}
|
||||
|
||||
/// Internal: add a single track wanted item. Returns Ok(true) if added as Wanted,
|
||||
/// Ok(false) if already owned.
|
||||
async fn add_track_inner(
|
||||
conn: &DatabaseConnection,
|
||||
artist_name: &str,
|
||||
title: &str,
|
||||
recording_mbid: Option<&str>,
|
||||
) -> WatchResult<bool> {
|
||||
let artist = queries::artists::upsert(conn, artist_name, None).await?;
|
||||
let is_owned = matching::track_is_owned(conn, artist_name, title).await?;
|
||||
|
||||
let item = queries::wanted::add(
|
||||
conn,
|
||||
ItemType::Track,
|
||||
title,
|
||||
recording_mbid,
|
||||
Some(artist.id),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if is_owned {
|
||||
queries::wanted::update_status(conn, item.id, WantedStatus::Owned).await?;
|
||||
tracing::debug!(title = title, artist = artist_name, "already owned");
|
||||
Ok(false)
|
||||
} else {
|
||||
tracing::debug!(title = title, artist = artist_name, "added to watchlist");
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve artist name from MBID if needed.
|
||||
/// Returns (name, mbid).
|
||||
async fn resolve_artist_info(
|
||||
name: Option<&str>,
|
||||
mbid: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
provider: &impl MetadataProvider,
|
||||
) -> WatchResult<(String, Option<String>)> {
|
||||
// If we have a name, use it directly
|
||||
if let Some(n) = name.filter(|s| !s.is_empty()) {
|
||||
return Ok((n.to_string(), mbid.map(String::from)));
|
||||
}
|
||||
|
||||
// No name — need MBID + provider to resolve
|
||||
let mbid = mbid.ok_or_else(|| {
|
||||
WatchError::Other("either a name or --mbid is required".into())
|
||||
})?;
|
||||
let _provider = provider.ok_or_else(|| {
|
||||
WatchError::Other("MusicBrainz provider needed to resolve MBID".into())
|
||||
})?;
|
||||
|
||||
// TODO: Add get_artist(mbid) to MetadataProvider trait for proper resolution.
|
||||
// For now, store the MBID and use a placeholder name.
|
||||
tracing::info!(mbid = mbid, "MBID provided without name — storing MBID as reference");
|
||||
Ok((format!("Artist [{}]", &mbid[..8.min(mbid.len())]), Some(mbid.to_string())))
|
||||
// Search for artist by MBID to get the name
|
||||
let results = provider
|
||||
.search_artist(mbid, 1)
|
||||
.await
|
||||
.map_err(|e| WatchError::Other(format!("artist lookup failed: {e}")))?;
|
||||
|
||||
if let Some(artist) = results.into_iter().next() {
|
||||
Ok((artist.name, Some(mbid.to_string())))
|
||||
} else {
|
||||
Ok((format!("Artist [{}]", &mbid[..8.min(mbid.len())]), Some(mbid.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve album info from MBID if needed.
|
||||
/// Returns (album_name, artist_name, album_mbid).
|
||||
async fn resolve_album_info(
|
||||
artist_name: Option<&str>,
|
||||
album_name: Option<&str>,
|
||||
mbid: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
provider: &impl MetadataProvider,
|
||||
) -> WatchResult<(String, String, Option<String>)> {
|
||||
// If we have both names, use them directly
|
||||
if let (Some(album), Some(artist)) = (
|
||||
album_name.filter(|s| !s.is_empty()),
|
||||
artist_name.filter(|s| !s.is_empty()),
|
||||
@@ -220,16 +300,10 @@ async fn resolve_album_info(
|
||||
return Ok((album.to_string(), artist.to_string(), mbid.map(String::from)));
|
||||
}
|
||||
|
||||
// Need to resolve from MBID via provider
|
||||
let mbid = mbid.ok_or_else(|| {
|
||||
WatchError::Other("either artist+album names or --mbid is required".into())
|
||||
})?;
|
||||
let provider = provider.ok_or_else(|| {
|
||||
WatchError::Other("MusicBrainz provider needed to resolve MBID".into())
|
||||
})?;
|
||||
|
||||
// Use search_release with the MBID — our trait doesn't have get_release yet,
|
||||
// but we can search for it
|
||||
let results = provider
|
||||
.search_release("", mbid)
|
||||
.await
|
||||
@@ -248,14 +322,12 @@ async fn resolve_album_info(
|
||||
}
|
||||
|
||||
/// Resolve track info from MBID if needed.
|
||||
/// Returns (title, artist_name, recording_mbid).
|
||||
async fn resolve_track_info(
|
||||
artist_name: Option<&str>,
|
||||
title: Option<&str>,
|
||||
mbid: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
provider: &impl MetadataProvider,
|
||||
) -> WatchResult<(String, String, Option<String>)> {
|
||||
// If we have both names, use them directly
|
||||
if let (Some(t), Some(a)) = (
|
||||
title.filter(|s| !s.is_empty()),
|
||||
artist_name.filter(|s| !s.is_empty()),
|
||||
@@ -263,32 +335,23 @@ async fn resolve_track_info(
|
||||
return Ok((t.to_string(), a.to_string(), mbid.map(String::from)));
|
||||
}
|
||||
|
||||
// Resolve from MBID
|
||||
let mbid = mbid.ok_or_else(|| {
|
||||
WatchError::Other("either artist+title or --mbid is required".into())
|
||||
})?;
|
||||
let provider = provider.ok_or_else(|| {
|
||||
WatchError::Other("MusicBrainz provider needed to resolve MBID".into())
|
||||
})?;
|
||||
|
||||
let details = provider
|
||||
.get_recording(mbid)
|
||||
.await
|
||||
.map_err(|e| WatchError::Other(format!("MusicBrainz lookup failed: {e}")))?;
|
||||
|
||||
let resolved_title = title
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(String::from)
|
||||
.unwrap_or(details.title);
|
||||
let resolved_artist = artist_name
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(String::from)
|
||||
.unwrap_or(details.artist);
|
||||
|
||||
Ok((resolved_title, resolved_artist, Some(mbid.to_string())))
|
||||
Ok((
|
||||
title.filter(|s| !s.is_empty()).map(String::from).unwrap_or(details.title),
|
||||
artist_name.filter(|s| !s.is_empty()).map(String::from).unwrap_or(details.artist),
|
||||
Some(mbid.to_string()),
|
||||
))
|
||||
}
|
||||
|
||||
/// List watchlist items with optional filters, resolved to display-friendly entries.
|
||||
/// List watchlist items with optional filters.
|
||||
pub async fn list_items(
|
||||
conn: &DatabaseConnection,
|
||||
status_filter: Option<WantedStatus>,
|
||||
@@ -298,18 +361,22 @@ pub async fn list_items(
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for item in items {
|
||||
let (name, artist_name) = resolve_names(conn, &item).await;
|
||||
// Use the name field directly now
|
||||
let artist_name = if let Some(id) = item.artist_id {
|
||||
queries::artists::get_by_id(conn, id)
|
||||
.await
|
||||
.map(|a| a.name)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Apply artist filter if provided
|
||||
if let Some(filter) = artist_filter {
|
||||
let filter_norm = matching::normalize(filter);
|
||||
let matches = artist_name
|
||||
.as_deref()
|
||||
.or(if item.item_type == ItemType::Artist { Some(name.as_str()) } else { None })
|
||||
.map(|a| {
|
||||
let a_norm = matching::normalize(a);
|
||||
strsim::jaro_winkler(&filter_norm, &a_norm) > 0.85
|
||||
})
|
||||
.map(|a| strsim::jaro_winkler(&filter_norm, &matching::normalize(a)) > 0.85)
|
||||
.unwrap_or(false);
|
||||
if !matches {
|
||||
continue;
|
||||
@@ -319,7 +386,7 @@ pub async fn list_items(
|
||||
entries.push(WatchListEntry {
|
||||
id: item.id,
|
||||
item_type: item.item_type,
|
||||
name,
|
||||
name: item.name.clone(),
|
||||
artist_name,
|
||||
status: item.status,
|
||||
added_at: item.added_at,
|
||||
@@ -342,11 +409,7 @@ pub async fn library_summary(conn: &DatabaseConnection) -> WatchResult<LibrarySu
|
||||
|
||||
let mut summary = LibrarySummary::default();
|
||||
for item in &all {
|
||||
match item.item_type {
|
||||
ItemType::Artist => summary.total_artists += 1,
|
||||
ItemType::Album => summary.total_albums += 1,
|
||||
ItemType::Track => summary.total_tracks += 1,
|
||||
}
|
||||
summary.total_items += 1;
|
||||
match item.status {
|
||||
WantedStatus::Wanted => summary.wanted += 1,
|
||||
WantedStatus::Available => summary.available += 1,
|
||||
@@ -357,57 +420,3 @@ pub async fn library_summary(conn: &DatabaseConnection) -> WatchResult<LibrarySu
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
/// Resolve the display name and artist name for a wanted item.
|
||||
async fn resolve_names(
|
||||
conn: &DatabaseConnection,
|
||||
item: &shanty_db::entities::wanted_item::Model,
|
||||
) -> (String, Option<String>) {
|
||||
match item.item_type {
|
||||
ItemType::Artist => {
|
||||
let name = if let Some(id) = item.artist_id {
|
||||
queries::artists::get_by_id(conn, id)
|
||||
.await
|
||||
.map(|a| a.name)
|
||||
.unwrap_or_else(|_| format!("Artist #{id}"))
|
||||
} else {
|
||||
"Unknown Artist".to_string()
|
||||
};
|
||||
(name, None)
|
||||
}
|
||||
ItemType::Album => {
|
||||
let (album_name, artist_name) = if let Some(id) = item.album_id {
|
||||
let album = queries::albums::get_by_id(conn, id).await;
|
||||
match album {
|
||||
Ok(a) => (a.name.clone(), Some(a.album_artist.clone())),
|
||||
Err(_) => (format!("Album #{id}"), None),
|
||||
}
|
||||
} else {
|
||||
("Unknown Album".to_string(), None)
|
||||
};
|
||||
let artist_name = artist_name.or_else(|| {
|
||||
item.artist_id.map(|id| format!("Artist #{id}"))
|
||||
});
|
||||
(album_name, artist_name)
|
||||
}
|
||||
ItemType::Track => {
|
||||
let artist_name = if let Some(id) = item.artist_id {
|
||||
queries::artists::get_by_id(conn, id)
|
||||
.await
|
||||
.map(|a| a.name)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let name = if let Some(id) = item.track_id {
|
||||
queries::tracks::get_by_id(conn, id)
|
||||
.await
|
||||
.map(|t| t.title.unwrap_or_else(|| format!("Track #{id}")))
|
||||
.unwrap_or_else(|_| format!("Track #{id}"))
|
||||
} else {
|
||||
"Track (title not stored)".to_string()
|
||||
};
|
||||
(name, artist_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
src/main.rs
27
src/main.rs
@@ -119,8 +119,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
let database_url = cli.database.unwrap_or_else(default_database_url);
|
||||
let db = Database::new(&database_url).await?;
|
||||
|
||||
// Create MB client lazily — only needed for MBID resolution
|
||||
let mb_client = MusicBrainzClient::new().ok();
|
||||
// MB client needed for artist/album expansion into tracks
|
||||
let mb_client = MusicBrainzClient::new()?;
|
||||
|
||||
match cli.command {
|
||||
Commands::Add { what } => match what {
|
||||
@@ -128,37 +128,28 @@ async fn main() -> anyhow::Result<()> {
|
||||
if name.is_none() && mbid.is_none() {
|
||||
anyhow::bail!("provide either a name or --mbid");
|
||||
}
|
||||
let entry = add_artist(
|
||||
let summary = add_artist(
|
||||
db.conn(),
|
||||
name.as_deref(),
|
||||
mbid.as_deref(),
|
||||
mb_client.as_ref(),
|
||||
&mb_client,
|
||||
)
|
||||
.await?;
|
||||
println!(
|
||||
"Added artist: {} (id={}, status={:?})",
|
||||
entry.name, entry.id, entry.status
|
||||
);
|
||||
println!("Artist watch: {summary}");
|
||||
}
|
||||
AddCommand::Album { artist, album, mbid } => {
|
||||
if artist.is_none() && album.is_none() && mbid.is_none() {
|
||||
anyhow::bail!("provide artist+album names or --mbid");
|
||||
}
|
||||
let entry = add_album(
|
||||
let summary = add_album(
|
||||
db.conn(),
|
||||
artist.as_deref(),
|
||||
album.as_deref(),
|
||||
mbid.as_deref(),
|
||||
mb_client.as_ref(),
|
||||
&mb_client,
|
||||
)
|
||||
.await?;
|
||||
println!(
|
||||
"Added album: {} by {} (id={}, status={:?})",
|
||||
entry.name,
|
||||
entry.artist_name.as_deref().unwrap_or("?"),
|
||||
entry.id,
|
||||
entry.status
|
||||
);
|
||||
println!("Album watch: {summary}");
|
||||
}
|
||||
AddCommand::Track { artist, title, mbid } => {
|
||||
if artist.is_none() && title.is_none() && mbid.is_none() {
|
||||
@@ -169,7 +160,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
artist.as_deref(),
|
||||
title.as_deref(),
|
||||
mbid.as_deref(),
|
||||
mb_client.as_ref(),
|
||||
&mb_client,
|
||||
)
|
||||
.await?;
|
||||
println!(
|
||||
|
||||
Reference in New Issue
Block a user