Formatting
This commit is contained in:
@@ -9,6 +9,6 @@ pub mod matching;
|
|||||||
|
|
||||||
pub use error::{WatchError, WatchResult};
|
pub use error::{WatchError, WatchResult};
|
||||||
pub use library::{
|
pub use library::{
|
||||||
AddSummary, LibrarySummary, WatchListEntry, add_album, add_artist, add_track,
|
AddSummary, LibrarySummary, WatchListEntry, add_album, add_artist, add_track, library_summary,
|
||||||
library_summary, list_items, remove_item,
|
list_items, remove_item,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,11 +84,12 @@ pub async fn add_artist(
|
|||||||
.search_artist(&resolved_name, 1)
|
.search_artist(&resolved_name, 1)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| WatchError::Other(format!("artist search failed: {e}")))?;
|
.map_err(|e| WatchError::Other(format!("artist search failed: {e}")))?;
|
||||||
results
|
results.into_iter().next().map(|a| a.mbid).ok_or_else(|| {
|
||||||
.into_iter()
|
WatchError::Other(format!(
|
||||||
.next()
|
"artist '{}' not found on MusicBrainz",
|
||||||
.map(|a| a.mbid)
|
resolved_name
|
||||||
.ok_or_else(|| WatchError::Other(format!("artist '{}' not found on MusicBrainz", resolved_name)))?
|
))
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,7 +118,14 @@ pub async fn add_artist(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for track in &tracks {
|
for track in &tracks {
|
||||||
match add_track_inner(conn, &resolved_name, &track.title, Some(&track.recording_mbid)).await {
|
match add_track_inner(
|
||||||
|
conn,
|
||||||
|
&resolved_name,
|
||||||
|
&track.title,
|
||||||
|
Some(&track.recording_mbid),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(true) => summary.tracks_added += 1,
|
Ok(true) => summary.tracks_added += 1,
|
||||||
Ok(false) => summary.tracks_already_owned += 1,
|
Ok(false) => summary.tracks_already_owned += 1,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -153,11 +161,12 @@ pub async fn add_album(
|
|||||||
.search_release(&resolved_artist, &resolved_album)
|
.search_release(&resolved_artist, &resolved_album)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| WatchError::Other(format!("album search failed: {e}")))?;
|
.map_err(|e| WatchError::Other(format!("album search failed: {e}")))?;
|
||||||
results
|
results.into_iter().next().map(|r| r.mbid).ok_or_else(|| {
|
||||||
.into_iter()
|
WatchError::Other(format!(
|
||||||
.next()
|
"album '{}' not found on MusicBrainz",
|
||||||
.map(|r| r.mbid)
|
resolved_album
|
||||||
.ok_or_else(|| WatchError::Other(format!("album '{}' not found on MusicBrainz", resolved_album)))?
|
))
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -171,7 +180,14 @@ pub async fn add_album(
|
|||||||
let mut summary = AddSummary::default();
|
let mut summary = AddSummary::default();
|
||||||
|
|
||||||
for track in &tracks {
|
for track in &tracks {
|
||||||
match add_track_inner(conn, &resolved_artist, &track.title, Some(&track.recording_mbid)).await {
|
match add_track_inner(
|
||||||
|
conn,
|
||||||
|
&resolved_artist,
|
||||||
|
&track.title,
|
||||||
|
Some(&track.recording_mbid),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(true) => summary.tracks_added += 1,
|
Ok(true) => summary.tracks_added += 1,
|
||||||
Ok(false) => summary.tracks_already_owned += 1,
|
Ok(false) => summary.tracks_already_owned += 1,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -269,9 +285,8 @@ async fn resolve_artist_info(
|
|||||||
return Ok((n.to_string(), mbid.map(String::from)));
|
return Ok((n.to_string(), mbid.map(String::from)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mbid = mbid.ok_or_else(|| {
|
let mbid =
|
||||||
WatchError::Other("either a name or --mbid is required".into())
|
mbid.ok_or_else(|| WatchError::Other("either a name or --mbid is required".into()))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
// Search for artist by MBID to get the name
|
// Search for artist by MBID to get the name
|
||||||
let results = provider
|
let results = provider
|
||||||
@@ -282,7 +297,10 @@ async fn resolve_artist_info(
|
|||||||
if let Some(artist) = results.into_iter().next() {
|
if let Some(artist) = results.into_iter().next() {
|
||||||
Ok((artist.name, Some(mbid.to_string())))
|
Ok((artist.name, Some(mbid.to_string())))
|
||||||
} else {
|
} else {
|
||||||
Ok((format!("Artist [{}]", &mbid[..8.min(mbid.len())]), Some(mbid.to_string())))
|
Ok((
|
||||||
|
format!("Artist [{}]", &mbid[..8.min(mbid.len())]),
|
||||||
|
Some(mbid.to_string()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +315,11 @@ async fn resolve_album_info(
|
|||||||
album_name.filter(|s| !s.is_empty()),
|
album_name.filter(|s| !s.is_empty()),
|
||||||
artist_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)));
|
return Ok((
|
||||||
|
album.to_string(),
|
||||||
|
artist.to_string(),
|
||||||
|
mbid.map(String::from),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mbid = mbid.ok_or_else(|| {
|
let mbid = mbid.ok_or_else(|| {
|
||||||
@@ -317,7 +339,9 @@ async fn resolve_album_info(
|
|||||||
.unwrap_or_else(|| release.artist.clone());
|
.unwrap_or_else(|| release.artist.clone());
|
||||||
Ok((album, artist, Some(mbid.to_string())))
|
Ok((album, artist, Some(mbid.to_string())))
|
||||||
} else {
|
} else {
|
||||||
Err(WatchError::Other(format!("no release found for MBID {mbid}")))
|
Err(WatchError::Other(format!(
|
||||||
|
"no release found for MBID {mbid}"
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,9 +359,8 @@ async fn resolve_track_info(
|
|||||||
return Ok((t.to_string(), a.to_string(), mbid.map(String::from)));
|
return Ok((t.to_string(), a.to_string(), mbid.map(String::from)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mbid = mbid.ok_or_else(|| {
|
let mbid =
|
||||||
WatchError::Other("either artist+title or --mbid is required".into())
|
mbid.ok_or_else(|| WatchError::Other("either artist+title or --mbid is required".into()))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let details = provider
|
let details = provider
|
||||||
.get_recording(mbid)
|
.get_recording(mbid)
|
||||||
@@ -345,8 +368,14 @@ async fn resolve_track_info(
|
|||||||
.map_err(|e| WatchError::Other(format!("MusicBrainz lookup failed: {e}")))?;
|
.map_err(|e| WatchError::Other(format!("MusicBrainz lookup failed: {e}")))?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
title.filter(|s| !s.is_empty()).map(String::from).unwrap_or(details.title),
|
title
|
||||||
artist_name.filter(|s| !s.is_empty()).map(String::from).unwrap_or(details.artist),
|
.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()),
|
Some(mbid.to_string()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/main.rs
29
src/main.rs
@@ -96,7 +96,9 @@ fn parse_status(s: &str) -> anyhow::Result<WantedStatus> {
|
|||||||
"available" => Ok(WantedStatus::Available),
|
"available" => Ok(WantedStatus::Available),
|
||||||
"downloaded" => Ok(WantedStatus::Downloaded),
|
"downloaded" => Ok(WantedStatus::Downloaded),
|
||||||
"owned" => Ok(WantedStatus::Owned),
|
"owned" => Ok(WantedStatus::Owned),
|
||||||
_ => anyhow::bail!("unknown status: {s} (expected wanted, available, downloaded, or owned)"),
|
_ => {
|
||||||
|
anyhow::bail!("unknown status: {s} (expected wanted, available, downloaded, or owned)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,16 +130,15 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if name.is_none() && mbid.is_none() {
|
if name.is_none() && mbid.is_none() {
|
||||||
anyhow::bail!("provide either a name or --mbid");
|
anyhow::bail!("provide either a name or --mbid");
|
||||||
}
|
}
|
||||||
let summary = add_artist(
|
let summary =
|
||||||
db.conn(),
|
add_artist(db.conn(), name.as_deref(), mbid.as_deref(), &mb_client).await?;
|
||||||
name.as_deref(),
|
|
||||||
mbid.as_deref(),
|
|
||||||
&mb_client,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
println!("Artist watch: {summary}");
|
println!("Artist watch: {summary}");
|
||||||
}
|
}
|
||||||
AddCommand::Album { artist, album, mbid } => {
|
AddCommand::Album {
|
||||||
|
artist,
|
||||||
|
album,
|
||||||
|
mbid,
|
||||||
|
} => {
|
||||||
if artist.is_none() && album.is_none() && mbid.is_none() {
|
if artist.is_none() && album.is_none() && mbid.is_none() {
|
||||||
anyhow::bail!("provide artist+album names or --mbid");
|
anyhow::bail!("provide artist+album names or --mbid");
|
||||||
}
|
}
|
||||||
@@ -151,7 +152,11 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
println!("Album watch: {summary}");
|
println!("Album watch: {summary}");
|
||||||
}
|
}
|
||||||
AddCommand::Track { artist, title, mbid } => {
|
AddCommand::Track {
|
||||||
|
artist,
|
||||||
|
title,
|
||||||
|
mbid,
|
||||||
|
} => {
|
||||||
if artist.is_none() && title.is_none() && mbid.is_none() {
|
if artist.is_none() && title.is_none() && mbid.is_none() {
|
||||||
anyhow::bail!("provide artist+title or --mbid");
|
anyhow::bail!("provide artist+title or --mbid");
|
||||||
}
|
}
|
||||||
@@ -180,8 +185,8 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
println!("Watchlist is empty.");
|
println!("Watchlist is empty.");
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"{:<5} {:<8} {:<12} {:<30} {}",
|
"{:<5} {:<8} {:<12} {:<30} ARTIST",
|
||||||
"ID", "TYPE", "STATUS", "NAME", "ARTIST"
|
"ID", "TYPE", "STATUS", "NAME"
|
||||||
);
|
);
|
||||||
for e in &entries {
|
for e in &entries {
|
||||||
println!(
|
println!(
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ use crate::error::WatchResult;
|
|||||||
|
|
||||||
/// Normalize a string for fuzzy comparison: NFC unicode, lowercase, trim.
|
/// Normalize a string for fuzzy comparison: NFC unicode, lowercase, trim.
|
||||||
pub fn normalize(s: &str) -> String {
|
pub fn normalize(s: &str) -> String {
|
||||||
s.nfc().collect::<String>().to_lowercase().trim().to_string()
|
s.nfc()
|
||||||
|
.collect::<String>()
|
||||||
|
.to_lowercase()
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if an artist is "owned" — i.e., any tracks by this artist exist in the indexed library.
|
/// Check if an artist is "owned" — i.e., any tracks by this artist exist in the indexed library.
|
||||||
@@ -52,7 +56,9 @@ pub async fn album_is_owned(
|
|||||||
let norm_album = normalize(album_name);
|
let norm_album = normalize(album_name);
|
||||||
|
|
||||||
// Try exact lookup
|
// Try exact lookup
|
||||||
if let Some(album) = queries::albums::find_by_name_and_artist(conn, album_name, artist_name).await? {
|
if let Some(album) =
|
||||||
|
queries::albums::find_by_name_and_artist(conn, album_name, artist_name).await?
|
||||||
|
{
|
||||||
let tracks = queries::tracks::get_by_album(conn, album.id).await?;
|
let tracks = queries::tracks::get_by_album(conn, album.id).await?;
|
||||||
if !tracks.is_empty() {
|
if !tracks.is_empty() {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
|
|||||||
@@ -42,7 +42,11 @@ async fn insert_track(db: &Database, artist: &str, title: &str, album: &str) {
|
|||||||
struct MockProvider;
|
struct MockProvider;
|
||||||
|
|
||||||
impl MetadataProvider for MockProvider {
|
impl MetadataProvider for MockProvider {
|
||||||
async fn search_recording(&self, _artist: &str, _title: &str) -> TagResult<Vec<RecordingMatch>> {
|
async fn search_recording(
|
||||||
|
&self,
|
||||||
|
_artist: &str,
|
||||||
|
_title: &str,
|
||||||
|
) -> TagResult<Vec<RecordingMatch>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
async fn search_release(&self, _artist: &str, _album: &str) -> TagResult<Vec<ReleaseMatch>> {
|
async fn search_release(&self, _artist: &str, _album: &str) -> TagResult<Vec<ReleaseMatch>> {
|
||||||
@@ -69,7 +73,11 @@ impl MetadataProvider for MockProvider {
|
|||||||
score: 100,
|
score: 100,
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
async fn get_artist_releases(&self, _mbid: &str, _limit: u32) -> TagResult<Vec<DiscographyEntry>> {
|
async fn get_artist_releases(
|
||||||
|
&self,
|
||||||
|
_mbid: &str,
|
||||||
|
_limit: u32,
|
||||||
|
) -> TagResult<Vec<DiscographyEntry>> {
|
||||||
Ok(vec![DiscographyEntry {
|
Ok(vec![DiscographyEntry {
|
||||||
mbid: "release-123".into(),
|
mbid: "release-123".into(),
|
||||||
title: "Test Album".into(),
|
title: "Test Album".into(),
|
||||||
@@ -97,7 +105,10 @@ impl MetadataProvider for MockProvider {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_artist_release_groups(&self, _artist_mbid: &str) -> TagResult<Vec<shanty_tag::provider::ReleaseGroupEntry>> {
|
async fn get_artist_release_groups(
|
||||||
|
&self,
|
||||||
|
_artist_mbid: &str,
|
||||||
|
) -> TagResult<Vec<shanty_tag::provider::ReleaseGroupEntry>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +144,13 @@ async fn test_add_album_expands_to_tracks() {
|
|||||||
let db = test_db().await;
|
let db = test_db().await;
|
||||||
let provider = MockProvider;
|
let provider = MockProvider;
|
||||||
|
|
||||||
let summary = add_album(db.conn(), Some("Test Artist"), Some("Test Album"), None, &provider)
|
let summary = add_album(
|
||||||
|
db.conn(),
|
||||||
|
Some("Test Artist"),
|
||||||
|
Some("Test Album"),
|
||||||
|
None,
|
||||||
|
&provider,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(summary.tracks_added, 2);
|
assert_eq!(summary.tracks_added, 2);
|
||||||
|
|||||||
Reference in New Issue
Block a user