Add support for MusicBrainz's Browse API #228
@ -3,7 +3,7 @@ use std::{thread, time};
|
||||
use musichoard::{
|
||||
collection::musicbrainz::Mbid,
|
||||
external::musicbrainz::{
|
||||
api::{browse::BrowseReleaseGroupRequest, MusicBrainzClient, PageSettings},
|
||||
api::{browse::BrowseReleaseGroupRequest, MusicBrainzClient, NextPage, PageSettings},
|
||||
http::MusicBrainzHttp,
|
||||
},
|
||||
};
|
||||
@ -66,20 +66,19 @@ fn main() {
|
||||
println!("{rg:?}\n");
|
||||
}
|
||||
|
||||
let offset = response.release_group_offset;
|
||||
let offset = response.page.release_group_offset;
|
||||
let count = response.release_groups.len();
|
||||
response_counts.push(count);
|
||||
let total = response.release_group_count;
|
||||
let total = response.page.release_group_count;
|
||||
|
||||
println!("Release group offset : {offset}");
|
||||
println!("Release groups in this response: {count}");
|
||||
println!("Release groups in total : {total}");
|
||||
|
||||
let next_offset = offset + count;
|
||||
if next_offset == total {
|
||||
break;
|
||||
match response.page.next_page_offset(count) {
|
||||
NextPage::Offset(next_offset) => paging.with_offset(next_offset),
|
||||
NextPage::Complete => break,
|
||||
}
|
||||
paging.with_offset(next_offset);
|
||||
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
}
|
||||
|
38
src/external/musicbrainz/api/browse.rs
vendored
38
src/external/musicbrainz/api/browse.rs
vendored
@ -6,14 +6,31 @@ use crate::{
|
||||
collection::musicbrainz::Mbid,
|
||||
external::musicbrainz::{
|
||||
api::{
|
||||
Error, MbReleaseGroupMeta, MusicBrainzClient, PageSettings, SerdeMbReleaseGroupMeta,
|
||||
MB_BASE_URL,
|
||||
ApiDisplay, Error, MbReleaseGroupMeta, MusicBrainzClient, NextPage, PageSettings,
|
||||
SerdeMbReleaseGroupMeta, MB_BASE_URL,
|
||||
},
|
||||
IMusicBrainzHttp,
|
||||
},
|
||||
};
|
||||
|
||||
use super::ApiDisplay;
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all(deserialize = "kebab-case"))]
|
||||
pub struct BrowseReleaseGroupPage {
|
||||
pub release_group_offset: usize,
|
||||
pub release_group_count: usize,
|
||||
}
|
||||
|
||||
impl BrowseReleaseGroupPage {
|
||||
pub fn next_page_offset(&self, page_count: usize) -> NextPage {
|
||||
NextPage::next_page_offset(
|
||||
self.release_group_offset,
|
||||
self.release_group_count,
|
||||
page_count,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type SerdeBrowseReleaseGroupPage = BrowseReleaseGroupPage;
|
||||
|
||||
impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
|
||||
pub fn browse_release_group(
|
||||
@ -60,24 +77,22 @@ impl<'a> BrowseReleaseGroupRequest<'a> {
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct BrowseReleaseGroupResponse {
|
||||
pub release_group_offset: usize,
|
||||
pub release_group_count: usize,
|
||||
pub release_groups: Vec<MbReleaseGroupMeta>,
|
||||
pub page: BrowseReleaseGroupPage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all(deserialize = "kebab-case"))]
|
||||
struct DeserializeBrowseReleaseGroupResponse {
|
||||
release_group_offset: usize,
|
||||
release_group_count: usize,
|
||||
release_groups: Option<Vec<SerdeMbReleaseGroupMeta>>,
|
||||
#[serde(flatten)]
|
||||
page: SerdeBrowseReleaseGroupPage,
|
||||
}
|
||||
|
||||
impl From<DeserializeBrowseReleaseGroupResponse> for BrowseReleaseGroupResponse {
|
||||
fn from(value: DeserializeBrowseReleaseGroupResponse) -> Self {
|
||||
BrowseReleaseGroupResponse {
|
||||
release_group_offset: value.release_group_offset,
|
||||
release_group_count: value.release_group_count,
|
||||
page: value.page,
|
||||
release_groups: value
|
||||
.release_groups
|
||||
.map(|rgs| rgs.into_iter().map(Into::into).collect())
|
||||
@ -120,14 +135,15 @@ mod tests {
|
||||
)]),
|
||||
};
|
||||
let de_response = DeserializeBrowseReleaseGroupResponse {
|
||||
page: SerdeBrowseReleaseGroupPage {
|
||||
release_group_offset: de_release_group_offset,
|
||||
release_group_count: de_release_group_count,
|
||||
},
|
||||
release_groups: Some(vec![de_meta.clone()]),
|
||||
};
|
||||
|
||||
let response = BrowseReleaseGroupResponse {
|
||||
release_group_offset: de_release_group_offset,
|
||||
release_group_count: de_release_group_count,
|
||||
page: de_response.page,
|
||||
release_groups: vec![de_meta.clone().into()],
|
||||
};
|
||||
|
||||
|
16
src/external/musicbrainz/api/mod.rs
vendored
16
src/external/musicbrainz/api/mod.rs
vendored
@ -84,6 +84,22 @@ impl PageSettings {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum NextPage {
|
||||
Offset(usize),
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl NextPage {
|
||||
pub fn next_page_offset(offset: usize, total_count: usize, page_count: usize) -> NextPage {
|
||||
let next_offset = offset + page_count;
|
||||
if next_offset < total_count {
|
||||
NextPage::Offset(next_offset)
|
||||
} else {
|
||||
NextPage::Complete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct MbArtistMeta {
|
||||
pub id: Mbid,
|
||||
|
16
src/external/musicbrainz/api/search/artist.rs
vendored
16
src/external/musicbrainz/api/search/artist.rs
vendored
@ -3,7 +3,10 @@ use std::fmt;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::external::musicbrainz::api::{
|
||||
search::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin},
|
||||
search::{
|
||||
query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin},
|
||||
SearchPage, SerdeSearchPage,
|
||||
},
|
||||
MbArtistMeta, SerdeMbArtistMeta,
|
||||
};
|
||||
|
||||
@ -26,18 +29,22 @@ impl_term!(string, SearchArtist<'a>, String, &'a str);
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SearchArtistResponse {
|
||||
pub artists: Vec<SearchArtistResponseArtist>,
|
||||
pub page: SearchPage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all(deserialize = "kebab-case"))]
|
||||
pub struct DeserializeSearchArtistResponse {
|
||||
artists: Vec<DeserializeSearchArtistResponseArtist>,
|
||||
#[serde(flatten)]
|
||||
page: SerdeSearchPage,
|
||||
}
|
||||
|
||||
impl From<DeserializeSearchArtistResponse> for SearchArtistResponse {
|
||||
fn from(value: DeserializeSearchArtistResponse) -> Self {
|
||||
SearchArtistResponse {
|
||||
artists: value.artists.into_iter().map(Into::into).collect(),
|
||||
page: value.page,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,6 +84,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn de_response() -> DeserializeSearchArtistResponse {
|
||||
let de_offset = 24;
|
||||
let de_count = 124;
|
||||
let de_artist = DeserializeSearchArtistResponseArtist {
|
||||
score: 67,
|
||||
meta: SerdeMbArtistMeta {
|
||||
@ -88,6 +97,10 @@ mod tests {
|
||||
};
|
||||
DeserializeSearchArtistResponse {
|
||||
artists: vec![de_artist.clone()],
|
||||
page: SerdeSearchPage {
|
||||
offset: de_offset,
|
||||
count: de_count,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,6 +114,7 @@ mod tests {
|
||||
meta: a.meta.into(),
|
||||
})
|
||||
.collect(),
|
||||
page: de_response.page,
|
||||
}
|
||||
}
|
||||
|
||||
|
18
src/external/musicbrainz/api/search/mod.rs
vendored
18
src/external/musicbrainz/api/search/mod.rs
vendored
@ -9,6 +9,7 @@ pub use release_group::{
|
||||
};
|
||||
|
||||
use paste::paste;
|
||||
use serde::Deserialize;
|
||||
use url::form_urlencoded;
|
||||
|
||||
use crate::external::musicbrainz::{
|
||||
@ -22,6 +23,23 @@ use crate::external::musicbrainz::{
|
||||
IMusicBrainzHttp,
|
||||
};
|
||||
|
||||
use super::NextPage;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all(deserialize = "kebab-case"))]
|
||||
pub struct SearchPage {
|
||||
pub offset: usize,
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
impl SearchPage {
|
||||
pub fn next_page_offset(&self, page_count: usize) -> NextPage {
|
||||
NextPage::next_page_offset(self.offset, self.count, page_count)
|
||||
}
|
||||
}
|
||||
|
||||
pub type SerdeSearchPage = SearchPage;
|
||||
|
||||
macro_rules! impl_search_entity {
|
||||
($name:ident, $entity:literal) => {
|
||||
paste! {
|
||||
|
@ -5,7 +5,10 @@ use serde::Deserialize;
|
||||
use crate::{
|
||||
collection::{album::AlbumDate, musicbrainz::Mbid},
|
||||
external::musicbrainz::api::{
|
||||
search::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin},
|
||||
search::{
|
||||
query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin},
|
||||
SearchPage, SerdeSearchPage,
|
||||
},
|
||||
ApiDisplay, MbReleaseGroupMeta, SerdeMbReleaseGroupMeta,
|
||||
},
|
||||
};
|
||||
@ -50,18 +53,22 @@ impl_term!(rgid, SearchReleaseGroup<'a>, Rgid, &'a Mbid);
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SearchReleaseGroupResponse {
|
||||
pub release_groups: Vec<SearchReleaseGroupResponseReleaseGroup>,
|
||||
pub page: SearchPage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all(deserialize = "kebab-case"))]
|
||||
pub struct DeserializeSearchReleaseGroupResponse {
|
||||
release_groups: Vec<DeserializeSearchReleaseGroupResponseReleaseGroup>,
|
||||
#[serde(flatten)]
|
||||
page: SerdeSearchPage,
|
||||
}
|
||||
|
||||
impl From<DeserializeSearchReleaseGroupResponse> for SearchReleaseGroupResponse {
|
||||
fn from(value: DeserializeSearchReleaseGroupResponse) -> Self {
|
||||
SearchReleaseGroupResponse {
|
||||
release_groups: value.release_groups.into_iter().map(Into::into).collect(),
|
||||
page: value.page,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,6 +116,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn de_response() -> DeserializeSearchReleaseGroupResponse {
|
||||
let de_offset = 26;
|
||||
let de_count = 126;
|
||||
let de_release_group = DeserializeSearchReleaseGroupResponseReleaseGroup {
|
||||
score: 67,
|
||||
meta: SerdeMbReleaseGroupMeta {
|
||||
@ -121,6 +130,10 @@ mod tests {
|
||||
};
|
||||
DeserializeSearchReleaseGroupResponse {
|
||||
release_groups: vec![de_release_group.clone()],
|
||||
page: SerdeSearchPage {
|
||||
offset: de_offset,
|
||||
count: de_count,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,6 +147,7 @@ mod tests {
|
||||
meta: rg.meta.into(),
|
||||
})
|
||||
.collect(),
|
||||
page: de_response.page,
|
||||
}
|
||||
}
|
||||
|
||||
|
4
src/tui/lib/external/musicbrainz/api/mod.rs
vendored
4
src/tui/lib/external/musicbrainz/api/mod.rs
vendored
@ -57,7 +57,7 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
||||
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error> {
|
||||
let query = SearchArtistRequest::new().string(&artist.id.name);
|
||||
|
||||
let paging = PageSettings::with_max_limit();
|
||||
let paging = PageSettings::default();
|
||||
let mb_response = self.client.search_artist(&query, &paging)?;
|
||||
|
||||
Ok(mb_response
|
||||
@ -83,7 +83,7 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
||||
.and()
|
||||
.release_group(&album.id.title);
|
||||
|
||||
let paging = PageSettings::with_max_limit();
|
||||
let paging = PageSettings::default();
|
||||
let mb_response = self.client.search_release_group(&query, &paging)?;
|
||||
|
||||
Ok(mb_response
|
||||
|
Loading…
Reference in New Issue
Block a user