Updted to also allow mbid
This commit is contained in:
@@ -8,6 +8,7 @@ repository = "ssh://connor@git.rcjohnstone.com:2222/Shanty/watch.git"
|
||||
|
||||
[dependencies]
|
||||
shanty-db = { path = "../shanty-db" }
|
||||
shanty-tag = { path = "../shanty-tag" }
|
||||
sea-orm = { version = "1", features = ["sqlx-sqlite", "runtime-tokio-native-tls"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
206
src/library.rs
206
src/library.rs
@@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use shanty_db::entities::wanted_item::{ItemType, WantedStatus};
|
||||
use shanty_db::queries;
|
||||
use shanty_tag::provider::MetadataProvider;
|
||||
|
||||
use crate::error::WatchResult;
|
||||
use crate::error::{WatchError, WatchResult};
|
||||
use crate::matching;
|
||||
|
||||
/// A display-friendly watchlist entry with resolved names.
|
||||
@@ -44,24 +45,24 @@ impl fmt::Display for LibrarySummary {
|
||||
}
|
||||
|
||||
/// Add an artist to the watchlist. Auto-detects if already owned.
|
||||
///
|
||||
/// 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.
|
||||
pub async fn add_artist(
|
||||
conn: &DatabaseConnection,
|
||||
name: &str,
|
||||
name: Option<&str>,
|
||||
musicbrainz_id: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
) -> WatchResult<WatchListEntry> {
|
||||
let artist = queries::artists::upsert(conn, name, musicbrainz_id).await?;
|
||||
let (resolved_name, resolved_mbid) =
|
||||
resolve_artist_info(name, musicbrainz_id, provider).await?;
|
||||
|
||||
let is_owned = matching::artist_is_owned(conn, name).await?;
|
||||
let item = queries::wanted::add(
|
||||
conn,
|
||||
ItemType::Artist,
|
||||
Some(artist.id),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.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?;
|
||||
|
||||
// If already owned, update status
|
||||
let status = if is_owned {
|
||||
queries::wanted::update_status(conn, item.id, WantedStatus::Owned).await?;
|
||||
WantedStatus::Owned
|
||||
@@ -70,9 +71,9 @@ pub async fn add_artist(
|
||||
};
|
||||
|
||||
if is_owned {
|
||||
tracing::info!(name = name, "artist already in library, marked as owned");
|
||||
tracing::info!(name = %resolved_name, "artist already in library, marked as owned");
|
||||
} else {
|
||||
tracing::info!(name = name, "artist added to watchlist");
|
||||
tracing::info!(name = %resolved_name, "artist added to watchlist");
|
||||
}
|
||||
|
||||
Ok(WatchListEntry {
|
||||
@@ -86,16 +87,30 @@ pub async fn add_artist(
|
||||
}
|
||||
|
||||
/// Add an album to the watchlist. Auto-detects if already owned.
|
||||
///
|
||||
/// If `musicbrainz_id` is provided and a `provider` is given, album and artist
|
||||
/// details will be resolved from MusicBrainz.
|
||||
pub async fn add_album(
|
||||
conn: &DatabaseConnection,
|
||||
artist_name: &str,
|
||||
album_name: &str,
|
||||
artist_name: Option<&str>,
|
||||
album_name: Option<&str>,
|
||||
musicbrainz_id: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
) -> WatchResult<WatchListEntry> {
|
||||
let artist = queries::artists::upsert(conn, artist_name, None).await?;
|
||||
let album = queries::albums::upsert(conn, album_name, artist_name, musicbrainz_id, Some(artist.id)).await?;
|
||||
let (resolved_album, resolved_artist, resolved_mbid) =
|
||||
resolve_album_info(artist_name, album_name, musicbrainz_id, provider).await?;
|
||||
|
||||
let is_owned = matching::album_is_owned(conn, artist_name, album_name).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,
|
||||
@@ -123,28 +138,28 @@ pub async fn add_album(
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
pub async fn add_track(
|
||||
conn: &DatabaseConnection,
|
||||
artist_name: &str,
|
||||
title: &str,
|
||||
artist_name: Option<&str>,
|
||||
title: Option<&str>,
|
||||
musicbrainz_id: Option<&str>,
|
||||
provider: Option<&impl MetadataProvider>,
|
||||
) -> WatchResult<WatchListEntry> {
|
||||
let artist = queries::artists::upsert(conn, artist_name, None).await?;
|
||||
let (resolved_title, resolved_artist, resolved_mbid) =
|
||||
resolve_track_info(artist_name, title, musicbrainz_id, provider).await?;
|
||||
|
||||
let is_owned = matching::track_is_owned(conn, artist_name, title).await?;
|
||||
let artist = queries::artists::upsert(conn, &resolved_artist, None).await?;
|
||||
|
||||
// We don't have a track record to link (the track table is for indexed files).
|
||||
// Create the wanted item with just the artist reference.
|
||||
let item = queries::wanted::add(
|
||||
conn,
|
||||
ItemType::Track,
|
||||
Some(artist.id),
|
||||
None,
|
||||
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?;
|
||||
|
||||
let _ = musicbrainz_id; // Reserved for future use
|
||||
// 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 status = if is_owned {
|
||||
queries::wanted::update_status(conn, item.id, WantedStatus::Owned).await?;
|
||||
@@ -156,13 +171,123 @@ pub async fn add_track(
|
||||
Ok(WatchListEntry {
|
||||
id: item.id,
|
||||
item_type: ItemType::Track,
|
||||
name: title.to_string(),
|
||||
name: resolved_title,
|
||||
artist_name: Some(artist.name),
|
||||
status,
|
||||
added_at: item.added_at,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
) -> 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())))
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
) -> 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()),
|
||||
) {
|
||||
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
|
||||
.map_err(|e| WatchError::Other(format!("MusicBrainz lookup failed: {e}")))?;
|
||||
|
||||
if let Some(release) = results.first() {
|
||||
let album = release.title.clone();
|
||||
let artist = artist_name
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| release.artist.clone());
|
||||
Ok((album, artist, Some(mbid.to_string())))
|
||||
} else {
|
||||
Err(WatchError::Other(format!("no release found for MBID {mbid}")))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
) -> 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()),
|
||||
) {
|
||||
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())))
|
||||
}
|
||||
|
||||
/// List watchlist items with optional filters, resolved to display-friendly entries.
|
||||
pub async fn list_items(
|
||||
conn: &DatabaseConnection,
|
||||
@@ -261,16 +386,11 @@ async fn resolve_names(
|
||||
("Unknown Album".to_string(), None)
|
||||
};
|
||||
let artist_name = artist_name.or_else(|| {
|
||||
// Fall back to artist table
|
||||
item.artist_id.and_then(|id| {
|
||||
// Can't await in closure, use a placeholder
|
||||
Some(format!("Artist #{id}"))
|
||||
})
|
||||
item.artist_id.map(|id| format!("Artist #{id}"))
|
||||
});
|
||||
(album_name, artist_name)
|
||||
}
|
||||
ItemType::Track => {
|
||||
// Track wanted items may not have a track_id (we watch by artist+title, not by file)
|
||||
let artist_name = if let Some(id) = item.artist_id {
|
||||
queries::artists::get_by_id(conn, id)
|
||||
.await
|
||||
@@ -279,8 +399,6 @@ async fn resolve_names(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// We don't store the track title in wanted_items directly,
|
||||
// so we show the artist name or a placeholder
|
||||
let name = if let Some(id) = item.track_id {
|
||||
queries::tracks::get_by_id(conn, id)
|
||||
.await
|
||||
|
||||
65
src/main.rs
65
src/main.rs
@@ -5,6 +5,7 @@ use tracing_subscriber::EnvFilter;
|
||||
|
||||
use shanty_db::Database;
|
||||
use shanty_db::entities::wanted_item::WantedStatus;
|
||||
use shanty_tag::MusicBrainzClient;
|
||||
use shanty_watch::{add_album, add_artist, add_track, library_summary, list_items, remove_item};
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -52,29 +53,29 @@ enum Commands {
|
||||
enum AddCommand {
|
||||
/// Add an artist to the watchlist.
|
||||
Artist {
|
||||
/// Artist name.
|
||||
name: String,
|
||||
/// MusicBrainz ID (optional).
|
||||
/// Artist name (optional if --mbid is provided).
|
||||
name: Option<String>,
|
||||
/// MusicBrainz artist ID. If provided without a name, the name will be resolved from MusicBrainz.
|
||||
#[arg(long)]
|
||||
mbid: Option<String>,
|
||||
},
|
||||
/// Add an album to the watchlist.
|
||||
Album {
|
||||
/// Artist name.
|
||||
artist: String,
|
||||
/// Album name.
|
||||
album: String,
|
||||
/// MusicBrainz ID (optional).
|
||||
/// Artist name (optional if --mbid is provided).
|
||||
artist: Option<String>,
|
||||
/// Album name (optional if --mbid is provided).
|
||||
album: Option<String>,
|
||||
/// MusicBrainz release ID.
|
||||
#[arg(long)]
|
||||
mbid: Option<String>,
|
||||
},
|
||||
/// Add a track to the watchlist.
|
||||
Track {
|
||||
/// Artist name.
|
||||
artist: String,
|
||||
/// Track title.
|
||||
title: String,
|
||||
/// MusicBrainz ID (optional).
|
||||
/// Artist name (optional if --mbid is provided).
|
||||
artist: Option<String>,
|
||||
/// Track title (optional if --mbid is provided).
|
||||
title: Option<String>,
|
||||
/// MusicBrainz recording ID. If provided without artist/title, details will be resolved from MusicBrainz.
|
||||
#[arg(long)]
|
||||
mbid: Option<String>,
|
||||
},
|
||||
@@ -118,17 +119,39 @@ 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();
|
||||
|
||||
match cli.command {
|
||||
Commands::Add { what } => match what {
|
||||
AddCommand::Artist { name, mbid } => {
|
||||
let entry = add_artist(db.conn(), &name, mbid.as_deref()).await?;
|
||||
if name.is_none() && mbid.is_none() {
|
||||
anyhow::bail!("provide either a name or --mbid");
|
||||
}
|
||||
let entry = add_artist(
|
||||
db.conn(),
|
||||
name.as_deref(),
|
||||
mbid.as_deref(),
|
||||
mb_client.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
println!(
|
||||
"Added artist: {} (id={}, status={:?})",
|
||||
entry.name, entry.id, entry.status
|
||||
);
|
||||
}
|
||||
AddCommand::Album { artist, album, mbid } => {
|
||||
let entry = add_album(db.conn(), &artist, &album, mbid.as_deref()).await?;
|
||||
if artist.is_none() && album.is_none() && mbid.is_none() {
|
||||
anyhow::bail!("provide artist+album names or --mbid");
|
||||
}
|
||||
let entry = add_album(
|
||||
db.conn(),
|
||||
artist.as_deref(),
|
||||
album.as_deref(),
|
||||
mbid.as_deref(),
|
||||
mb_client.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
println!(
|
||||
"Added album: {} by {} (id={}, status={:?})",
|
||||
entry.name,
|
||||
@@ -138,7 +161,17 @@ async fn main() -> anyhow::Result<()> {
|
||||
);
|
||||
}
|
||||
AddCommand::Track { artist, title, mbid } => {
|
||||
let entry = add_track(db.conn(), &artist, &title, mbid.as_deref()).await?;
|
||||
if artist.is_none() && title.is_none() && mbid.is_none() {
|
||||
anyhow::bail!("provide artist+title or --mbid");
|
||||
}
|
||||
let entry = add_track(
|
||||
db.conn(),
|
||||
artist.as_deref(),
|
||||
title.as_deref(),
|
||||
mbid.as_deref(),
|
||||
mb_client.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
println!(
|
||||
"Added track: {} by {} (id={}, status={:?})",
|
||||
entry.name,
|
||||
|
||||
@@ -3,6 +3,7 @@ use sea_orm::ActiveValue::Set;
|
||||
|
||||
use shanty_db::entities::wanted_item::{ItemType, WantedStatus};
|
||||
use shanty_db::{Database, queries};
|
||||
use shanty_tag::MusicBrainzClient;
|
||||
use shanty_watch::{add_album, add_artist, add_track, library_summary, list_items, remove_item};
|
||||
|
||||
async fn test_db() -> Database {
|
||||
@@ -36,11 +37,14 @@ async fn insert_track(db: &Database, artist: &str, title: &str, album: &str) {
|
||||
queries::tracks::upsert(db.conn(), active).await.unwrap();
|
||||
}
|
||||
|
||||
// No MB client needed for tests — pass None
|
||||
const NO_MB: Option<&MusicBrainzClient> = None;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_artist_wanted() {
|
||||
let db = test_db().await;
|
||||
|
||||
let entry = add_artist(db.conn(), "Radiohead", None).await.unwrap();
|
||||
let entry = add_artist(db.conn(), Some("Radiohead"), None, NO_MB).await.unwrap();
|
||||
assert_eq!(entry.item_type, ItemType::Artist);
|
||||
assert_eq!(entry.name, "Radiohead");
|
||||
assert_eq!(entry.status, WantedStatus::Wanted);
|
||||
@@ -50,11 +54,9 @@ async fn test_add_artist_wanted() {
|
||||
async fn test_add_artist_auto_owned() {
|
||||
let db = test_db().await;
|
||||
|
||||
// Index some tracks first
|
||||
insert_track(&db, "Pink Floyd", "Time", "DSOTM").await;
|
||||
|
||||
// Add artist to watchlist — should auto-detect as owned
|
||||
let entry = add_artist(db.conn(), "Pink Floyd", None).await.unwrap();
|
||||
let entry = add_artist(db.conn(), Some("Pink Floyd"), None, NO_MB).await.unwrap();
|
||||
assert_eq!(entry.status, WantedStatus::Owned);
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ async fn test_add_artist_auto_owned() {
|
||||
async fn test_add_album_wanted() {
|
||||
let db = test_db().await;
|
||||
|
||||
let entry = add_album(db.conn(), "Radiohead", "OK Computer", None)
|
||||
let entry = add_album(db.conn(), Some("Radiohead"), Some("OK Computer"), None, NO_MB)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(entry.item_type, ItemType::Album);
|
||||
@@ -76,7 +78,7 @@ async fn test_add_album_auto_owned() {
|
||||
|
||||
insert_track(&db, "Pink Floyd", "Time", "DSOTM").await;
|
||||
|
||||
let entry = add_album(db.conn(), "Pink Floyd", "DSOTM", None)
|
||||
let entry = add_album(db.conn(), Some("Pink Floyd"), Some("DSOTM"), None, NO_MB)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(entry.status, WantedStatus::Owned);
|
||||
@@ -86,7 +88,7 @@ async fn test_add_album_auto_owned() {
|
||||
async fn test_add_track_wanted() {
|
||||
let db = test_db().await;
|
||||
|
||||
let entry = add_track(db.conn(), "Radiohead", "Creep", None)
|
||||
let entry = add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, NO_MB)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(entry.item_type, ItemType::Track);
|
||||
@@ -100,7 +102,7 @@ async fn test_add_track_auto_owned() {
|
||||
|
||||
insert_track(&db, "Pink Floyd", "Time", "DSOTM").await;
|
||||
|
||||
let entry = add_track(db.conn(), "Pink Floyd", "Time", None)
|
||||
let entry = add_track(db.conn(), Some("Pink Floyd"), Some("Time"), None, NO_MB)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(entry.status, WantedStatus::Owned);
|
||||
@@ -110,8 +112,8 @@ async fn test_add_track_auto_owned() {
|
||||
async fn test_list_items_with_filters() {
|
||||
let db = test_db().await;
|
||||
|
||||
add_artist(db.conn(), "Radiohead", None).await.unwrap();
|
||||
add_artist(db.conn(), "Tool", None).await.unwrap();
|
||||
add_artist(db.conn(), Some("Radiohead"), None, NO_MB).await.unwrap();
|
||||
add_artist(db.conn(), Some("Tool"), None, NO_MB).await.unwrap();
|
||||
|
||||
// List all
|
||||
let all = list_items(db.conn(), None, None).await.unwrap();
|
||||
@@ -140,7 +142,7 @@ async fn test_list_items_with_filters() {
|
||||
async fn test_remove_item() {
|
||||
let db = test_db().await;
|
||||
|
||||
let entry = add_artist(db.conn(), "Radiohead", None).await.unwrap();
|
||||
let entry = add_artist(db.conn(), Some("Radiohead"), None, NO_MB).await.unwrap();
|
||||
let all = list_items(db.conn(), None, None).await.unwrap();
|
||||
assert_eq!(all.len(), 1);
|
||||
|
||||
@@ -155,10 +157,10 @@ async fn test_library_summary() {
|
||||
|
||||
insert_track(&db, "Pink Floyd", "Time", "DSOTM").await;
|
||||
|
||||
add_artist(db.conn(), "Radiohead", None).await.unwrap(); // wanted
|
||||
add_artist(db.conn(), "Pink Floyd", None).await.unwrap(); // owned
|
||||
add_album(db.conn(), "Tool", "Lateralus", None).await.unwrap(); // wanted
|
||||
add_track(db.conn(), "Pink Floyd", "Time", None).await.unwrap(); // owned
|
||||
add_artist(db.conn(), Some("Radiohead"), None, NO_MB).await.unwrap();
|
||||
add_artist(db.conn(), Some("Pink Floyd"), None, NO_MB).await.unwrap();
|
||||
add_album(db.conn(), Some("Tool"), Some("Lateralus"), None, NO_MB).await.unwrap();
|
||||
add_track(db.conn(), Some("Pink Floyd"), Some("Time"), None, NO_MB).await.unwrap();
|
||||
|
||||
let summary = library_summary(db.conn()).await.unwrap();
|
||||
assert_eq!(summary.total_artists, 2);
|
||||
|
||||
Reference in New Issue
Block a user