diff --git a/rust/include/qobuz_backend.h b/rust/include/qobuz_backend.h index 959ac0f..79c7635 100644 --- a/rust/include/qobuz_backend.h +++ b/rust/include/qobuz_backend.h @@ -84,6 +84,9 @@ uint32_t qobuz_backend_viz_read(QobuzBackendOpaque *backend, float *buf, uint32_ uint32_t qobuz_backend_viz_sample_rate(const QobuzBackendOpaque *backend); uint32_t qobuz_backend_viz_channels(const QobuzBackendOpaque *backend); +// Artist releases (full paginated list per release type) +void qobuz_backend_get_artist_releases(QobuzBackendOpaque *backend, int64_t artist_id, const char *release_type, uint32_t limit, uint32_t offset); + // Playlist management void qobuz_backend_create_playlist(QobuzBackendOpaque *backend, const char *name); void qobuz_backend_delete_playlist(QobuzBackendOpaque *backend, int64_t playlist_id); diff --git a/rust/src/api/client.rs b/rust/src/api/client.rs index 035f4e0..59129ab 100644 --- a/rust/src/api/client.rs +++ b/rust/src/api/client.rs @@ -267,6 +267,28 @@ impl QobuzClient { Self::check_response(resp).await } + pub async fn get_artist_releases_list( + &self, + artist_id: i64, + release_type: &str, + limit: u32, + offset: u32, + ) -> Result { + let resp = self + .get_request("artist/getReleasesList") + .query(&[ + ("artist_id", artist_id.to_string()), + ("release_type", release_type.to_string()), + ("sort", "release_date".to_string()), + ("order", "desc".to_string()), + ("limit", limit.to_string()), + ("offset", offset.to_string()), + ]) + .send() + .await?; + Self::check_response(resp).await + } + // --- Search --- pub async fn search(&self, query: &str, offset: u32, limit: u32) -> Result { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 938b1b5..6c3fa30 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -68,6 +68,7 @@ pub const EV_POSITION: c_int = 16; pub const EV_TRACK_URL_OK: c_int = 17; pub const EV_TRACK_URL_ERR: c_int = 18; pub const EV_GENERIC_ERR: c_int = 19; +pub const EV_ARTIST_RELEASES_OK: c_int = 24; // ---------- Callback ---------- @@ -256,6 +257,38 @@ pub unsafe extern "C" fn qobuz_backend_get_artist(ptr: *mut Backend, artist_id: }); } +// ---------- Artist releases ---------- + +#[no_mangle] +pub unsafe extern "C" fn qobuz_backend_get_artist_releases( + ptr: *mut Backend, + artist_id: i64, + release_type: *const c_char, + limit: u32, + offset: u32, +) { + let inner = &(*ptr).0; + let client = inner.client.clone(); + let cb = inner.cb; let ud = inner.ud; + let rtype = CStr::from_ptr(release_type).to_string_lossy().into_owned(); + + spawn(inner, async move { + let result = client.lock().await + .get_artist_releases_list(artist_id, &rtype, limit, offset) + .await; + let (ev, json) = match result { + Ok(r) => { + // Wrap with the release_type so Qt can route to the right section + let mut obj = r.as_object().cloned().unwrap_or_default(); + obj.insert("release_type".to_string(), serde_json::Value::String(rtype)); + (EV_ARTIST_RELEASES_OK, serde_json::to_string(&obj).unwrap_or_default()) + } + Err(e) => (EV_GENERIC_ERR, err_json(&e.to_string())), + }; + call_cb(cb, ud, ev, &json); + }); +} + // ---------- Playlist ---------- #[no_mangle] diff --git a/src/backend/qobuzbackend.cpp b/src/backend/qobuzbackend.cpp index 166816f..b77df82 100644 --- a/src/backend/qobuzbackend.cpp +++ b/src/backend/qobuzbackend.cpp @@ -61,6 +61,12 @@ void QobuzBackend::getArtist(qint64 artistId) qobuz_backend_get_artist(m_backend, artistId); } +void QobuzBackend::getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit, quint32 offset) +{ + qobuz_backend_get_artist_releases(m_backend, artistId, + releaseType.toUtf8().constData(), limit, offset); +} + void QobuzBackend::getPlaylist(qint64 playlistId, quint32 offset, quint32 limit) { qobuz_backend_get_playlist(m_backend, playlistId, offset, limit); @@ -242,6 +248,9 @@ void QobuzBackend::onEvent(int eventType, const QString &json) case EV_ARTIST_OK: emit artistLoaded(obj); break; + case 24: // EV_ARTIST_RELEASES_OK + emit artistReleasesLoaded(obj["release_type"].toString(), obj["items"].toArray()); + break; case EV_ARTIST_ERR: emit error(obj["error"].toString()); break; diff --git a/src/backend/qobuzbackend.hpp b/src/backend/qobuzbackend.hpp index daee721..6da5d1c 100644 --- a/src/backend/qobuzbackend.hpp +++ b/src/backend/qobuzbackend.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -29,6 +30,7 @@ public: void search(const QString &query, quint32 offset = 0, quint32 limit = 20); void getAlbum(const QString &albumId); void getArtist(qint64 artistId); + void getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit = 500, quint32 offset = 0); void getPlaylist(qint64 playlistId, quint32 offset = 0, quint32 limit = 500); // --- favorites --- @@ -83,6 +85,7 @@ signals: void searchResult(const QJsonObject &result); void albumLoaded(const QJsonObject &album); void artistLoaded(const QJsonObject &artist); + void artistReleasesLoaded(const QString &releaseType, const QJsonArray &items); void playlistLoaded(const QJsonObject &playlist); void playlistCreated(const QJsonObject &playlist); void playlistDeleted(const QJsonObject &result); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f9d6c4d..39a2676 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -84,6 +84,8 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent) connect(m_backend, &QobuzBackend::favArtistsLoaded, this, &MainWindow::onFavArtistsLoaded); connect(m_backend, &QobuzBackend::albumLoaded, this, &MainWindow::onAlbumLoaded); connect(m_backend, &QobuzBackend::artistLoaded, this, &MainWindow::onArtistLoaded); + connect(m_backend, &QobuzBackend::artistReleasesLoaded, + m_content, &MainContent::updateArtistReleases); connect(m_backend, &QobuzBackend::playlistLoaded, this, &MainWindow::onPlaylistLoaded); connect(m_backend, &QobuzBackend::playlistCreated, this, &MainWindow::onPlaylistCreated); connect(m_backend, &QobuzBackend::playlistDeleted, this, [this](const QJsonObject &) { @@ -334,7 +336,7 @@ void MainWindow::onArtistLoaded(const QJsonObject &artist) { m_content->showArtist(artist); statusBar()->showMessage( - tr("Artist: %1").arg(artist["name"].toString()), 4000); + tr("Artist: %1").arg(artist["name"].toObject()["display"].toString()), 4000); } void MainWindow::onPlaylistLoaded(const QJsonObject &playlist) @@ -358,6 +360,9 @@ void MainWindow::onSearchAlbumSelected(const QString &albumId) void MainWindow::onSearchArtistSelected(qint64 artistId) { m_backend->getArtist(artistId); + // Fire release-type requests in parallel — each updates its section when it arrives + for (const char *type : {"album", "epSingle", "live", "compilation"}) + m_backend->getArtistReleases(artistId, QString::fromLatin1(type)); statusBar()->showMessage(tr("Loading artist…")); } diff --git a/src/view/artistview.cpp b/src/view/artistview.cpp index ca5efb9..99c31b3 100644 --- a/src/view/artistview.cpp +++ b/src/view/artistview.cpp @@ -195,32 +195,29 @@ void ArtistView::setArtist(const QJsonObject &artist) m_topTracks->loadTracks(topTracks); m_topTracksSection->setVisible(!topTracks.isEmpty()); - // releases is an array of {type, has_more, items[]} - const QJsonArray releases = artist["releases"].toArray(); - QJsonArray albums, eps, live, compilations; - for (const QJsonValue &rv : releases) { - const QJsonObject rg = rv.toObject(); - const QString type = rg["type"].toString(); - const QJsonArray items = rg["items"].toArray(); - if (type == QStringLiteral("album")) - albums = items; - else if (type == QStringLiteral("epSingle")) - eps = items; - else if (type == QStringLiteral("live")) - live = items; - else if (type == QStringLiteral("compilation")) - compilations = items; - } - - m_secAlbums->setAlbums(albums); - m_secEps->setAlbums(eps); - m_secLive->setAlbums(live); - m_secCompilations->setAlbums(compilations); + // Release sections are populated asynchronously via setReleases(). + // Clear them now so stale data from a previous artist isn't shown. + m_secAlbums->setAlbums({}); + m_secEps->setAlbums({}); + m_secLive->setAlbums({}); + m_secCompilations->setAlbums({}); m_secOther->setAlbums({}); - - m_secAlbums->setVisible(!m_secAlbums->isEmpty()); - m_secEps->setVisible(!m_secEps->isEmpty()); - m_secLive->setVisible(!m_secLive->isEmpty()); - m_secCompilations->setVisible(!m_secCompilations->isEmpty()); + m_secAlbums->setVisible(false); + m_secEps->setVisible(false); + m_secLive->setVisible(false); + m_secCompilations->setVisible(false); m_secOther->setVisible(false); } + +void ArtistView::setReleases(const QString &releaseType, const QJsonArray &items) +{ + ArtistSection *sec = nullptr; + if (releaseType == QStringLiteral("album")) sec = m_secAlbums; + else if (releaseType == QStringLiteral("epSingle")) sec = m_secEps; + else if (releaseType == QStringLiteral("live")) sec = m_secLive; + else if (releaseType == QStringLiteral("compilation")) sec = m_secCompilations; + else sec = m_secOther; + + sec->setAlbums(items); + sec->setVisible(!sec->isEmpty()); +} diff --git a/src/view/artistview.hpp b/src/view/artistview.hpp index 735aa56..0d6f767 100644 --- a/src/view/artistview.hpp +++ b/src/view/artistview.hpp @@ -45,6 +45,8 @@ public: explicit ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent = nullptr); void setArtist(const QJsonObject &artist); + /// Update a specific release section when getReleasesList responds. + void setReleases(const QString &releaseType, const QJsonArray &items); signals: void albumSelected(const QString &albumId); diff --git a/src/view/maincontent.cpp b/src/view/maincontent.cpp index 5e2cd61..66411c7 100644 --- a/src/view/maincontent.cpp +++ b/src/view/maincontent.cpp @@ -107,3 +107,8 @@ void MainContent::showArtist(const QJsonObject &artist) m_artistView->setArtist(artist); m_stack->setCurrentIndex(4); } + +void MainContent::updateArtistReleases(const QString &releaseType, const QJsonArray &items) +{ + m_artistView->setReleases(releaseType, items); +} diff --git a/src/view/maincontent.hpp b/src/view/maincontent.hpp index f9c7e28..9d096b4 100644 --- a/src/view/maincontent.hpp +++ b/src/view/maincontent.hpp @@ -31,6 +31,7 @@ public: void showFavAlbums(const QJsonObject &result); void showFavArtists(const QJsonObject &result); void showArtist(const QJsonObject &artist); + void updateArtistReleases(const QString &releaseType, const QJsonArray &items); signals: void albumRequested(const QString &albumId);