From eabfc9d6c3278f2927f267f5f0ec3f1e2f2eba1c Mon Sep 17 00:00:00 2001 From: kraxarn Date: Tue, 2 Jan 2024 16:12:20 +0100 Subject: [PATCH] Lazily load "appears on" --- lib/include/lib/enums.hpp | 31 +++++++++ lib/include/lib/spotify/api.hpp | 2 +- lib/src/spotifyapi/artists.cpp | 26 +++++++- src/view/artist/albumslist.cpp | 107 ++++++++++++++++++++++++++------ src/view/artist/albumslist.hpp | 4 ++ src/view/artist/view.cpp | 27 +++++++- src/view/artist/view.hpp | 2 + 7 files changed, 174 insertions(+), 25 deletions(-) diff --git a/lib/include/lib/enums.hpp b/lib/include/lib/enums.hpp index 28336db5..e04c4d23 100644 --- a/lib/include/lib/enums.hpp +++ b/lib/include/lib/enums.hpp @@ -1,5 +1,6 @@ #pragma once +#include "lib/enum/albumgroup.hpp" #include "lib/enum/mediatype.hpp" #include "lib/enum/devicetype.hpp" #include "lib/enum/playeraction.hpp" @@ -287,5 +288,35 @@ namespace lib } //endregion + + //region album_group + + static void enum_to_string(const album_group album_group, std::string &str) + { + switch (album_group) + { + case album_group::album: + str = "album"; + break; + + case album_group::single: + str = "single"; + break; + + case album_group::compilation: + str = "compilation"; + break; + + case album_group::appears_on: + str = "appears_on"; + break; + + case album_group::none: + str = ""; + break; + } + } + + //endregion }; } diff --git a/lib/include/lib/spotify/api.hpp b/lib/include/lib/spotify/api.hpp index 3ba92517..a9feccb2 100644 --- a/lib/include/lib/spotify/api.hpp +++ b/lib/include/lib/spotify/api.hpp @@ -67,7 +67,7 @@ namespace lib void related_artists(const lib::spt::artist &artist, lib::callback> &callback); - void albums(const spt::artist &artist, + void albums(const spt::artist &artist, const std::vector &groups, const paged_callback &callback) const; //endregion diff --git a/lib/src/spotifyapi/artists.cpp b/lib/src/spotifyapi/artists.cpp index 182f3e57..0a8dcfc3 100644 --- a/lib/src/spotifyapi/artists.cpp +++ b/lib/src/spotifyapi/artists.cpp @@ -1,3 +1,4 @@ +#include "lib/uri.hpp" #include "lib/spotify/api.hpp" // Currently unavailable: @@ -29,8 +30,27 @@ void lib::spt::api::related_artists(const lib::spt::artist &artist, }); } -void lib::spt::api::albums(const spt::artist &artist, const paged_callback &callback) const +void lib::spt::api::albums(const spt::artist &artist, const std::vector &groups, + const paged_callback &callback) const { - const auto url = fmt::format("artists/{}/albums?limit=50&country=from_token", artist.id); - request.get_page(url, {}, callback); + uri uri(to_full_url(fmt::format("artists/{}/albums", artist.id))); + + auto params = uri.get_search_params(); + params.insert({"limit", "50"}); + + if (!groups.empty()) + { + std::vector groups_str; + groups_str.reserve(groups.size()); + + for (const auto &group: groups) + { + groups_str.push_back(enums::to_string(group)); + } + + params.insert({"include_groups", strings::join(groups_str, ",")}); + } + + uri.set_search_params(params); + request.get_page(uri.get_url(), {}, callback); } diff --git a/src/view/artist/albumslist.cpp b/src/view/artist/albumslist.cpp index 8692a8d4..91a9b330 100644 --- a/src/view/artist/albumslist.cpp +++ b/src/view/artist/albumslist.cpp @@ -32,6 +32,9 @@ Artist::AlbumsList::AlbumsList(lib::spt::api &spotify, lib::cache &cache, QTreeWidget::connect(this, &QTreeWidget::itemEntered, this, &Artist::AlbumsList::onItemEntered); + connect(this, &QTreeWidget::itemExpanded, + this, &AlbumsList::onItemExtended); + for (auto i = lib::album_group::album; i < lib::album_group::none; i = static_cast(static_cast(i) + 1)) { @@ -49,8 +52,29 @@ auto Artist::AlbumsList::getAlbum(QTreeWidgetItem *item) -> lib::spt::album void Artist::AlbumsList::loadAlbums(const lib::spt::page &page) { setEnabled(false); + addAlbums(page.items); + + if (page.has_next()) + { + return; + } + + setEnabled(true); + + // Expand first group with items + for (const auto &group: groups) + { + if (group.second->childCount() > 0) + { + group.second->setExpanded(true); + break; + } + } +} - for (const auto &album: page.items) +void Artist::AlbumsList::addAlbums(const std::vector &albums) const +{ + for (const auto &album: albums) { const auto releaseDate = DateTime::parseIsoDate(album.release_date); // Extra spacing is intentional so year doesn't overlap with scrollbar @@ -60,10 +84,11 @@ void Artist::AlbumsList::loadAlbums(const lib::spt::page &page) auto *group = groups.at(album.album_group); auto *item = new QTreeWidgetItem(group, { - albumName, year.isEmpty() ? QString() : year + albumName, + year.isEmpty() ? QString() : year }); - Http::getAlbumImage(album.image, httpClient, cache, [item, album](const QPixmap &image) + Http::getAlbumImage(album.image, httpClient, cache, [item](const QPixmap &image) { if (item != nullptr) { @@ -84,23 +109,6 @@ void Artist::AlbumsList::loadAlbums(const lib::spt::page &page) group->addChild(item); } - - if (page.has_next()) - { - return; - } - - setEnabled(true); - - // Expand first group with items - for (const auto &group: groups) - { - if (group.second->childCount() > 0) - { - group.second->setExpanded(true); - break; - } - } } auto Artist::AlbumsList::groupToString(lib::album_group albumGroup) -> QString @@ -185,3 +193,62 @@ void Artist::AlbumsList::onItemEntered(QTreeWidgetItem *item, int column) const auto album = getAlbum(item); tooltip.set(item, album, item->icon(0)); } + +void Artist::AlbumsList::onItemExtended(const QTreeWidgetItem *item) const +{ + constexpr auto pageSize = 50; + if (item == nullptr || item->childCount() != pageSize) + { + return; + } + + if (item->text(0) != groupToString(lib::album_group::appears_on)) + { + return; + } + + const auto *view = Parent::findWidget(parentWidget()); + if (view == nullptr) + { + lib::log::debug("Parent isn't view ({})", + parent()->metaObject()->className()); + return; + } + + auto *group = groups.at(lib::album_group::appears_on); + group->setDisabled(true); + + const auto &artist = view->getArtist(); + + spotify.albums(artist, {lib::album_group::appears_on}, + [this, group](const lib::result> &result) -> bool + { + if (!result.success()) + { + StatusMessage::error(QString("Failed to load appears on albums: %1") + .arg(QString::fromStdString(result.message()))); + + return false; + } + + const auto &page = result.value(); + if (page.offset == 0) + { + group->setDisabled(true); + while (const auto *child = group->takeChild(0)) + { + delete child; + } + } + + addAlbums(page.items); + + if (page.has_next()) + { + return true; + } + + group->setDisabled(false); + return false; + }); +} diff --git a/src/view/artist/albumslist.hpp b/src/view/artist/albumslist.hpp index b5b5c552..832ec59e 100644 --- a/src/view/artist/albumslist.hpp +++ b/src/view/artist/albumslist.hpp @@ -14,11 +14,14 @@ namespace Artist { class AlbumsList: public QTreeWidget { + Q_OBJECT + public: AlbumsList(lib::spt::api &spotify, lib::cache &cache, const lib::http_client &httpClient, lib::settings &settings, QWidget *parent); void loadAlbums(const lib::spt::page &page); + void addAlbums(const std::vector &albums) const; private: lib::spt::api &spotify; @@ -35,5 +38,6 @@ namespace Artist void onItemDoubleClicked(QTreeWidgetItem *item, int column); void onContextMenu(const QPoint &pos); void onItemEntered(QTreeWidgetItem *item, int column); + void onItemExtended(const QTreeWidgetItem *item) const; }; } diff --git a/src/view/artist/view.cpp b/src/view/artist/view.cpp index 2ec52300..3661234f 100644 --- a/src/view/artist/view.cpp +++ b/src/view/artist/view.cpp @@ -58,6 +58,11 @@ Artist::View::View(lib::spt::api &spotify, const std::string &artistId, lib::cac }); } +auto Artist::View::getArtist() const -> const lib::spt::artist & +{ + return artist; +} + void Artist::View::artistLoaded(const lib::spt::artist &loadedArtist) { artist = loadedArtist; @@ -98,7 +103,13 @@ void Artist::View::artistLoaded(const lib::spt::artist &loadedArtist) }); // Albums - spotify.albums(artist, [this](const lib::result> &result) -> bool + const std::vector groups{ + lib::album_group::album, + lib::album_group::single, + lib::album_group::compilation, + }; + + spotify.albums(artist, groups, [this](const lib::result> &result) -> bool { if (!result.success()) { @@ -112,6 +123,20 @@ void Artist::View::artistLoaded(const lib::spt::artist &loadedArtist) return page.has_next(); }); + spotify.albums(artist, {lib::album_group::appears_on}, + [this](const lib::result> &result) -> bool + { + if (!result.success()) + { + lib::log::error("Failed to fetch appears on albums: {}", result.message()); + return false; + } + + const auto &page = result.value(); + albumList->addAlbums(page.items); + return false; + }); + // Related artists spotify.related_artists(artist, [this](const std::vector &artists) { diff --git a/src/view/artist/view.hpp b/src/view/artist/view.hpp index 44874cbe..2afad8c3 100644 --- a/src/view/artist/view.hpp +++ b/src/view/artist/view.hpp @@ -33,6 +33,8 @@ namespace Artist View(lib::spt::api &spotify, const std::string &artistId, lib::cache &cache, const lib::http_client &httpClient, lib::settings &settings, QWidget *parent); + auto getArtist() const -> const lib::spt::artist &; + private: void artistLoaded(const lib::spt::artist &loadedArtist); void topTracksLoaded(const std::vector &tracks);