diff --git a/src-tauri/src/discogs/mod.rs b/src-tauri/src/discogs/mod.rs index bf64427c6..5fd35f707 100644 --- a/src-tauri/src/discogs/mod.rs +++ b/src-tauri/src/discogs/mod.rs @@ -44,7 +44,7 @@ pub struct DiscogsImageOption { pub release_year: Option, } -/// Release details from Discogs API +/// Release details from Discogs API (internal, for artwork) #[derive(Debug, Deserialize)] struct ReleaseDetails { id: u64, @@ -63,6 +63,69 @@ struct ReleaseImage { image_type: String, } +// ============ Public Metadata Structures ============ + +/// Full release metadata from Discogs (for tag editor) +#[derive(Debug, Clone, Deserialize, serde::Serialize)] +pub struct DiscogsReleaseMetadata { + pub id: u64, + pub title: String, + pub artists: Option>, + pub year: Option, + pub genres: Option>, + pub styles: Option>, + pub labels: Option>, + pub tracklist: Option>, + pub country: Option, + /// URL to view on Discogs + pub uri: Option, +} + +/// Artist in Discogs release +#[derive(Debug, Clone, Deserialize, serde::Serialize)] +pub struct DiscogsArtist { + pub name: String, + pub id: Option, + /// Join phrase (e.g., " & ", " feat. ") + pub join: Option, +} + +/// Label in Discogs release +#[derive(Debug, Clone, Deserialize, serde::Serialize)] +pub struct DiscogsLabel { + pub name: String, + pub catno: Option, + pub id: Option, +} + +/// Track in Discogs release +#[derive(Debug, Clone, Deserialize, serde::Serialize)] +pub struct DiscogsTrack { + /// Position (e.g., "1", "A1", "1-1") + pub position: String, + pub title: String, + /// Duration as string (e.g., "3:45") + pub duration: Option, + /// Track type (e.g., "track", "heading") + #[serde(rename = "type_")] + pub track_type: Option, +} + +/// Extended search result with more metadata +#[derive(Debug, Clone, Deserialize, serde::Serialize)] +pub struct DiscogsSearchResultExtended { + pub id: u64, + pub title: String, + #[serde(rename = "type")] + pub result_type: String, + pub year: Option, + pub country: Option, + pub label: Option>, + pub catno: Option, + pub format: Option>, + pub cover_image: Option, +} + impl DiscogsClient { /// Create a new Discogs client (proxy handles credentials) pub fn new() -> Self { @@ -364,6 +427,82 @@ impl DiscogsClient { Ok(search) } + /// Search for releases with extended metadata + /// Returns up to `limit` results with detailed release information + pub async fn search_releases( + &self, + artist: &str, + album: &str, + catalog_number: Option<&str>, + limit: usize, + ) -> Result, String> { + // Build search query - prefer catalog number if available + let query = if let Some(catno) = catalog_number.filter(|s| !s.trim().is_empty()) { + catno.to_string() + } else { + format!("{} {}", artist, album) + }; + + let url = format!( + "{}/search?q={}&type=release&per_page={}", + DISCOGS_PROXY_URL, + urlencoding::encode(&query), + limit.min(25) + ); + + log::debug!( + "Searching Discogs releases for: {} - {} (catalog: {:?})", + artist, + album, + catalog_number + ); + + let response = self.client.get(&url).send().await + .map_err(|e| format!("Failed to search Discogs: {}", e))?; + + if !response.status().is_success() { + return Err(format!("Discogs search failed with status: {}", response.status())); + } + + // Parse response with extended fields + #[derive(Debug, Deserialize)] + struct ExtendedSearchResponse { + results: Vec, + } + + let search: ExtendedSearchResponse = response.json().await + .map_err(|e| format!("Failed to parse Discogs response: {}", e))?; + + // Filter to releases only + let results: Vec<_> = search.results + .into_iter() + .filter(|r| r.result_type == "release" || r.result_type == "master") + .collect(); + + log::info!("Found {} Discogs releases", results.len()); + Ok(results) + } + + /// Get full release metadata including tracklist + pub async fn get_release_metadata(&self, release_id: u64) -> Result { + let url = format!("{}/release/{}", DISCOGS_PROXY_URL, release_id); + + log::debug!("Fetching Discogs release metadata for ID: {}", release_id); + + let response = self.client.get(&url).send().await + .map_err(|e| format!("Failed to fetch release: {}", e))?; + + if !response.status().is_success() { + return Err(format!("Failed to fetch release: {}", response.status())); + } + + let metadata: DiscogsReleaseMetadata = response.json().await + .map_err(|e| format!("Failed to parse release metadata: {}", e))?; + + log::info!("Fetched Discogs release: {} ({:?})", metadata.title, metadata.year); + Ok(metadata) + } + /// Download an image to the cache directory async fn download_image(&self, image_url: &str, path: &Path) -> Option<()> { log::debug!("Downloading Discogs artwork: {}", image_url);