Added the mb db download. Big upsides and downsides
This commit is contained in:
171
shanty-data/src/mb_hybrid.rs
Normal file
171
shanty-data/src/mb_hybrid.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
//! Hybrid MusicBrainz fetcher: local DB first, API fallback.
|
||||
//!
|
||||
//! Tries the local SQLite database for instant lookups. If the local DB is not
|
||||
//! configured, not available, or doesn't have the requested entity, falls back
|
||||
//! to the rate-limited MusicBrainz API.
|
||||
|
||||
use crate::error::DataResult;
|
||||
use crate::mb_local::{LocalMbStats, LocalMusicBrainzFetcher};
|
||||
use crate::musicbrainz::MusicBrainzFetcher;
|
||||
use crate::traits::MetadataFetcher;
|
||||
use crate::types::{
|
||||
ArtistInfo, ArtistSearchResult, DiscographyEntry, RecordingDetails, RecordingMatch,
|
||||
ReleaseGroupEntry, ReleaseMatch, ReleaseTrack,
|
||||
};
|
||||
|
||||
/// A [`MetadataFetcher`] that tries a local MusicBrainz SQLite database first,
|
||||
/// then falls back to the remote MusicBrainz API.
|
||||
pub struct HybridMusicBrainzFetcher {
|
||||
local: Option<LocalMusicBrainzFetcher>,
|
||||
remote: MusicBrainzFetcher,
|
||||
}
|
||||
|
||||
impl HybridMusicBrainzFetcher {
|
||||
/// Create a hybrid fetcher. If `local` is `None`, all queries go to the API.
|
||||
pub fn new(local: Option<LocalMusicBrainzFetcher>, remote: MusicBrainzFetcher) -> Self {
|
||||
Self { local, remote }
|
||||
}
|
||||
|
||||
/// Whether a local database is configured and has data.
|
||||
pub fn has_local_db(&self) -> bool {
|
||||
self.local.as_ref().is_some_and(|l| l.is_available())
|
||||
}
|
||||
|
||||
/// Get stats from the local database (if available).
|
||||
pub fn local_stats(&self) -> Option<LocalMbStats> {
|
||||
self.local
|
||||
.as_ref()
|
||||
.filter(|l| l.is_available())
|
||||
.map(|l| l.stats())
|
||||
}
|
||||
|
||||
/// Get a reference to the underlying remote fetcher (for methods not on the trait).
|
||||
pub fn remote(&self) -> &MusicBrainzFetcher {
|
||||
&self.remote
|
||||
}
|
||||
|
||||
/// Returns a reference to the local fetcher if available and populated.
|
||||
fn local_if_available(&self) -> Option<&LocalMusicBrainzFetcher> {
|
||||
self.local.as_ref().filter(|l| l.is_available())
|
||||
}
|
||||
|
||||
/// Look up an artist by MBID. Tries local first, then remote.
|
||||
pub async fn get_artist_by_mbid(&self, mbid: &str) -> DataResult<(String, Option<String>)> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Ok(result) = local.get_artist_by_mbid_sync(mbid)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
self.remote.get_artist_by_mbid(mbid).await
|
||||
}
|
||||
|
||||
/// Get detailed artist info by MBID. Tries local first, then remote.
|
||||
pub async fn get_artist_info(&self, mbid: &str) -> DataResult<ArtistInfo> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Ok(result) = local.get_artist_info_sync(mbid)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
self.remote.get_artist_info(mbid).await
|
||||
}
|
||||
|
||||
/// Get a clone of the rate limiter for sharing with other MB clients.
|
||||
pub fn limiter(&self) -> crate::http::RateLimiter {
|
||||
self.remote.limiter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Try a local search; returns `Some(results)` if non-empty, `None` to fall through.
|
||||
async fn try_local_vec<T, F: std::future::Future<Output = DataResult<Vec<T>>>>(
|
||||
f: F,
|
||||
) -> Option<DataResult<Vec<T>>> {
|
||||
let results = f.await;
|
||||
match results {
|
||||
Ok(ref r) if !r.is_empty() => Some(results),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl MetadataFetcher for HybridMusicBrainzFetcher {
|
||||
async fn search_recording(&self, artist: &str, title: &str) -> DataResult<Vec<RecordingMatch>> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Some(results) = try_local_vec(local.search_recording(artist, title)).await
|
||||
{
|
||||
return results;
|
||||
}
|
||||
self.remote.search_recording(artist, title).await
|
||||
}
|
||||
|
||||
async fn search_release(&self, artist: &str, album: &str) -> DataResult<Vec<ReleaseMatch>> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Some(results) = try_local_vec(local.search_release(artist, album)).await
|
||||
{
|
||||
return results;
|
||||
}
|
||||
self.remote.search_release(artist, album).await
|
||||
}
|
||||
|
||||
async fn get_recording(&self, mbid: &str) -> DataResult<RecordingDetails> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Ok(result) = local.get_recording(mbid).await
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
self.remote.get_recording(mbid).await
|
||||
}
|
||||
|
||||
async fn search_artist(&self, query: &str, limit: u32) -> DataResult<Vec<ArtistSearchResult>> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Some(results) = try_local_vec(local.search_artist(query, limit)).await
|
||||
{
|
||||
return results;
|
||||
}
|
||||
self.remote.search_artist(query, limit).await
|
||||
}
|
||||
|
||||
async fn get_artist_releases(
|
||||
&self,
|
||||
artist_mbid: &str,
|
||||
limit: u32,
|
||||
) -> DataResult<Vec<DiscographyEntry>> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Some(results) =
|
||||
try_local_vec(local.get_artist_releases(artist_mbid, limit)).await
|
||||
{
|
||||
return results;
|
||||
}
|
||||
self.remote.get_artist_releases(artist_mbid, limit).await
|
||||
}
|
||||
|
||||
async fn get_release_tracks(&self, release_mbid: &str) -> DataResult<Vec<ReleaseTrack>> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Ok(tracks) = local.get_release_tracks(release_mbid).await
|
||||
{
|
||||
return Ok(tracks);
|
||||
}
|
||||
self.remote.get_release_tracks(release_mbid).await
|
||||
}
|
||||
|
||||
async fn get_artist_release_groups(
|
||||
&self,
|
||||
artist_mbid: &str,
|
||||
) -> DataResult<Vec<ReleaseGroupEntry>> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Some(results) = try_local_vec(local.get_artist_release_groups(artist_mbid)).await
|
||||
{
|
||||
return results;
|
||||
}
|
||||
self.remote.get_artist_release_groups(artist_mbid).await
|
||||
}
|
||||
|
||||
async fn resolve_release_from_group(&self, release_group_mbid: &str) -> DataResult<String> {
|
||||
if let Some(local) = self.local_if_available()
|
||||
&& let Ok(result) = local.resolve_release_from_group(release_group_mbid).await
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
self.remote
|
||||
.resolve_release_from_group(release_group_mbid)
|
||||
.await
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user