From 35f448c34fc527450b65153d89cb87c88745036c Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Tue, 12 Feb 2019 21:58:03 +0100 Subject: [PATCH] Add artist search in internet view, and use album artist --- src/core/song.cpp | 2 +- src/deezer/deezerservice.cpp | 35 +-- src/deezer/deezerservice.h | 6 +- src/internet/internetsearch.cpp | 16 +- src/internet/internetsearch.h | 17 +- src/internet/internetsearchmodel.cpp | 70 ++++-- src/internet/internetsearchmodel.h | 2 +- src/internet/internetsearchview.cpp | 43 ++-- src/internet/internetsearchview.h | 10 +- src/internet/internetsearchview.ui | 15 +- src/internet/internetservice.h | 2 +- src/settings/tidalsettingspage.cpp | 2 + src/settings/tidalsettingspage.ui | 43 ++++ src/tidal/tidalservice.cpp | 329 ++++++++++++++++++++------- src/tidal/tidalservice.h | 40 +++- 15 files changed, 446 insertions(+), 186 deletions(-) diff --git a/src/core/song.cpp b/src/core/song.cpp index d8f77464..34813bcc 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -144,7 +144,7 @@ const QString Song::kEmbeddedCover = "(embedded)"; const QRegExp Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$"); const QRegExp Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered) ?((\\)|\\])?)$"); -const QRegExp Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Live) ?((\\)|\\])?)$"); +const QRegExp Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Live|Remastered Version) ?((\\)|\\])?)$"); struct Song::Private : public QSharedData { diff --git a/src/deezer/deezerservice.cpp b/src/deezer/deezerservice.cpp index 98a30c52..3642a91d 100644 --- a/src/deezer/deezerservice.cpp +++ b/src/deezer/deezerservice.cpp @@ -349,8 +349,6 @@ QJsonObject DeezerService::ExtractJsonObj(QByteArray &data) { QJsonParseError error; QJsonDocument json_doc = QJsonDocument::fromJson(data, &error); - - //qLog(Debug) << json_doc; if (error.error != QJsonParseError::NoError) { Error("Reply from server missing Json data.", data); @@ -373,8 +371,6 @@ QJsonObject DeezerService::ExtractJsonObj(QByteArray &data) { return QJsonObject(); } - //qLog(Debug) << json_obj; - return json_obj; } @@ -412,11 +408,11 @@ QJsonValue DeezerService::ExtractData(QByteArray &data) { } -int DeezerService::Search(const QString &text, InternetSearch::SearchBy searchby) { +int DeezerService::Search(const QString &text, InternetSearch::SearchType searchby) { pending_search_id_ = next_pending_search_id_; pending_search_text_ = text; - pending_searchby_ = searchby; + pending_search_type_ = searchby; next_pending_search_id_++; @@ -469,12 +465,13 @@ void DeezerService::SendSearch() { QList parameters; parameters << Param("q", search_text_); QString searchparam; - switch (pending_searchby_) { - case InternetSearch::SearchBy_Songs: + switch (pending_search_type_) { + case InternetSearch::SearchType_Songs: searchparam = "search/track"; parameters << Param("limit", QString::number(songssearchlimit_)); break; - case InternetSearch::SearchBy_Albums: + case InternetSearch::SearchType_Albums: + case InternetSearch::SearchType_Artists: default: searchparam = "search/album"; parameters << Param("limit", QString::number(albumssearchlimit_)); @@ -511,23 +508,19 @@ void DeezerService::SearchFinished(QNetworkReply *reply, int id) { return; } - //qLog(Debug) << json_data; - for (const QJsonValue &value : json_data) { - //qLog(Debug) << value; + if (!value.isObject()) { Error("Invalid Json reply, data is not an object.", value); continue; } QJsonObject json_obj = value.toObject(); - //qLog(Debug) << json_obj; if (!json_obj.contains("id") || !json_obj.contains("type")) { Error("Invalid Json reply, item is missing ID or type.", json_obj); continue; } - //int id = json_obj["id"].toInt(); QString type = json_obj["type"].toString(); if (!json_obj.contains("artist")) { @@ -575,7 +568,7 @@ void DeezerService::SearchFinished(QNetworkReply *reply, int id) { album = json_album["title"].toString(); cover = json_album[coversize_].toString(); if (!fetchalbums_) { - Song song = ParseSong(album_id, album, cover, value); + Song song = ParseSong(album_id, album, artist, cover, value); songs_ << song; continue; } @@ -668,14 +661,12 @@ void DeezerService::GetAlbumFinished(QNetworkReply *reply, int search_id, int al bool compilation = false; bool multidisc = false; - Song first_song; SongList songs; for (const QJsonValue &value : json_data) { - Song song = ParseSong(album_ctx->id, album_ctx->album, album_ctx->cover, value); + Song song = ParseSong(album_ctx->id, album_ctx->album, album_ctx->artist, album_ctx->cover, value); if (!song.is_valid()) continue; if (song.disc() >= 2) multidisc = true; - if (song.is_compilation() || (first_song.is_valid() && song.artist() != first_song.artist())) compilation = true; - if (!first_song.is_valid()) first_song = song; + if (song.is_compilation()) compilation = true; songs << song; } for (Song &song : songs) { @@ -692,7 +683,7 @@ void DeezerService::GetAlbumFinished(QNetworkReply *reply, int search_id, int al } -Song DeezerService::ParseSong(const int album_id, const QString &album, const QString &album_cover, const QJsonValue &value) { +Song DeezerService::ParseSong(const int album_id, const QString &album, const QString &album_artist, const QString &album_cover, const QJsonValue &value) { if (!value.isObject()) { Error("Invalid Json reply, track is not an object.", value); @@ -700,8 +691,6 @@ Song DeezerService::ParseSong(const int album_id, const QString &album, const QS } QJsonObject json_obj = value.toObject(); - //qLog(Debug) << json_obj; - if ( !json_obj.contains("id") || !json_obj.contains("title") || @@ -738,6 +727,7 @@ Song DeezerService::ParseSong(const int album_id, const QString &album, const QS song.set_source(Song::Source_Deezer); song.set_id(song_id); song.set_album_id(album_id); + if (artist != album_artist) song.set_albumartist(album_artist); song.set_artist(artist); song.set_album(album); song.set_title(title); @@ -760,7 +750,6 @@ Song DeezerService::ParseSong(const int album_id, const QString &album, const QS } } song.set_url(url); - song.set_valid(true); return song; diff --git a/src/deezer/deezerservice.h b/src/deezer/deezerservice.h index 9f69c522..6aecc99b 100644 --- a/src/deezer/deezerservice.h +++ b/src/deezer/deezerservice.h @@ -70,7 +70,7 @@ class DeezerService : public InternetService { void ReloadSettings(); void Logout(); - int Search(const QString &query, InternetSearch::SearchBy searchby); + int Search(const QString &query, InternetSearch::SearchType searchby); void CancelSearch(); const bool app_id() { return kAppID; } @@ -116,7 +116,7 @@ class DeezerService : public InternetService { void SendSearch(); DeezerAlbumContext *CreateAlbum(const int album_id, const QString &artist, const QString &album, const QString &cover); void GetAlbum(const DeezerAlbumContext *album_ctx); - Song ParseSong(const int album_id, const QString &album, const QString &album_cover, const QJsonValue &value); + Song ParseSong(const int album_id, const QString &album, const QString &album_artist, const QString &album_cover, const QJsonValue &value); void CheckFinish(); void Error(QString error, QVariant debug = QString()); @@ -145,7 +145,7 @@ class DeezerService : public InternetService { int pending_search_id_; int next_pending_search_id_; QString pending_search_text_; - InternetSearch::SearchBy pending_searchby_; + InternetSearch::SearchType pending_search_type_; int search_id_; QString search_text_; diff --git a/src/internet/internetsearch.cpp b/src/internet/internetsearch.cpp index f1e5895c..1b7b82e3 100644 --- a/src/internet/internetsearch.cpp +++ b/src/internet/internetsearch.cpp @@ -69,7 +69,7 @@ InternetSearch::InternetSearch(Application *app, Song::Source source, QObject *p cover_loader_options_.scale_output_image_ = true; connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage))); - connect(this, SIGNAL(SearchAsyncSig(int, QString, SearchBy)), this, SLOT(DoSearchAsync(int, QString, SearchBy))); + connect(this, SIGNAL(SearchAsyncSig(int, QString, SearchType)), this, SLOT(DoSearchAsync(int, QString, SearchType))); connect(this, SIGNAL(ResultsAvailable(int, InternetSearch::ResultList)), SLOT(ResultsAvailableSlot(int, InternetSearch::ResultList))); connect(this, SIGNAL(ArtLoaded(int, QImage)), SLOT(ArtLoadedSlot(int, QImage))); connect(service_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatusSlot(QString))); @@ -113,29 +113,29 @@ bool InternetSearch::Matches(const QStringList &tokens, const QString &string) { } -int InternetSearch::SearchAsync(const QString &query, SearchBy searchby) { +int InternetSearch::SearchAsync(const QString &query, SearchType type) { const int id = searches_next_id_++; - emit SearchAsyncSig(id, query, searchby); + emit SearchAsyncSig(id, query, type); return id; } -void InternetSearch::SearchAsync(int id, const QString &query, SearchBy searchby) { +void InternetSearch::SearchAsync(int id, const QString &query, SearchType type) { - const int service_id = service_->Search(query, searchby); + const int service_id = service_->Search(query, type); pending_searches_[service_id] = PendingState(id, TokenizeQuery(query)); } -void InternetSearch::DoSearchAsync(int id, const QString &query, SearchBy searchby) { +void InternetSearch::DoSearchAsync(int id, const QString &query, SearchType type) { int timer_id = startTimer(kDelayedSearchTimeoutMs); delayed_searches_[timer_id].id_ = id; delayed_searches_[timer_id].query_ = query; - delayed_searches_[timer_id].searchby_ = searchby; + delayed_searches_[timer_id].type_ = type; } @@ -186,7 +186,7 @@ void InternetSearch::CancelSearch(int id) { void InternetSearch::timerEvent(QTimerEvent *e) { QMap::iterator it = delayed_searches_.find(e->timerId()); if (it != delayed_searches_.end()) { - SearchAsync(it.value().id_, it.value().query_, it.value().searchby_); + SearchAsync(it.value().id_, it.value().query_, it.value().type_); delayed_searches_.erase(it); return; } diff --git a/src/internet/internetsearch.h b/src/internet/internetsearch.h index ff22a1c1..47c4a7de 100644 --- a/src/internet/internetsearch.h +++ b/src/internet/internetsearch.h @@ -45,9 +45,10 @@ class InternetSearch : public QObject { InternetSearch(Application *app, Song::Source source, QObject *parent = nullptr); ~InternetSearch(); - enum SearchBy { - SearchBy_Songs = 1, - SearchBy_Albums = 2, + enum SearchType { + SearchType_Artists = 1, + SearchType_Albums = 2, + SearchType_Songs = 3, }; struct Result { @@ -63,7 +64,7 @@ class InternetSearch : public QObject { Song::Source source() const { return source_; } InternetService *service() const { return service_; } - int SearchAsync(const QString &query, SearchBy searchby); + int SearchAsync(const QString &query, SearchType type); int LoadArtAsync(const InternetSearch::Result &result); void CancelSearch(int id); @@ -74,7 +75,7 @@ class InternetSearch : public QObject { MimeData *LoadTracks(const ResultList &results); signals: - void SearchAsyncSig(int id, const QString &query, SearchBy searchby); + void SearchAsyncSig(int id, const QString &query, SearchType type); void ResultsAvailable(int id, const InternetSearch::ResultList &results); void AddResults(int id, const InternetSearch::ResultList &results); void SearchError(const int id, const QString error); @@ -112,7 +113,7 @@ class InternetSearch : public QObject { static bool Matches(const QStringList &tokens, const QString &string); private slots: - void DoSearchAsync(int id, const QString &query, SearchBy searchby); + void DoSearchAsync(int id, const QString &query, SearchType type); void SearchDone(int id, const SongList &songs); void HandleError(const int id, const QString error); void ResultsAvailableSlot(int id, InternetSearch::ResultList results); @@ -125,7 +126,7 @@ class InternetSearch : public QObject { void UpdateProgressSlot(int max); private: - void SearchAsync(int id, const QString &query, SearchBy searchby); + void SearchAsync(int id, const QString &query, SearchType type); void HandleLoadedArt(int id, const QImage &image); bool FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const; QString PixmapCacheKey(const InternetSearch::Result &result) const; @@ -137,7 +138,7 @@ class InternetSearch : public QObject { struct DelayedSearch { int id_; QString query_; - SearchBy searchby_; + SearchType type_; }; static const int kArtHeight; diff --git a/src/internet/internetsearchmodel.cpp b/src/internet/internetsearchmodel.cpp index 5ce96b37..8b551dd8 100644 --- a/src/internet/internetsearchmodel.cpp +++ b/src/internet/internetsearchmodel.cpp @@ -41,17 +41,15 @@ InternetSearchModel::InternetSearchModel(InternetSearch *engine, QObject *parent engine_(engine), proxy_(nullptr), use_pretty_covers_(true), - artist_icon_(IconLoader::Load("folder-sound")) { + artist_icon_(IconLoader::Load("folder-sound")), + album_icon_(IconLoader::Load("cdcase")) + { group_by_[0] = CollectionModel::GroupBy_Artist; group_by_[1] = CollectionModel::GroupBy_Album; group_by_[2] = CollectionModel::GroupBy_None; - QIcon nocover = IconLoader::Load("cdcase"); - no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - //no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - album_icon_ = no_cover_icon_; + no_cover_icon_ = album_icon_.pixmap(album_icon_.availableSizes().last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } @@ -93,6 +91,19 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem int year = 0; switch (group_by_[level]) { + + case CollectionModel::GroupBy_AlbumArtist: + if (s.is_compilation()) { + display_text = tr("Various artists"); + sort_text = "aaaaaa"; + } + else { + display_text = CollectionModel::TextOrUnknown(s.effective_albumartist()); + sort_text = CollectionModel::SortTextForArtist(s.effective_albumartist()); + } + has_artist_icon = true; + break; + case CollectionModel::GroupBy_Artist: if (s.is_compilation()) { display_text = tr("Various artists"); @@ -134,28 +145,42 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem break; case CollectionModel::GroupBy_Composer: - display_text = s.composer(); + display_text = CollectionModel::TextOrUnknown(s.composer()); + sort_text = CollectionModel::SortTextForArtist(s.composer()); + has_album_icon = true; + break; + case CollectionModel::GroupBy_Performer: - display_text = s.performer(); + display_text = CollectionModel::TextOrUnknown(s.performer()); + sort_text = CollectionModel::SortTextForArtist(s.performer()); + has_album_icon = true; + break; + case CollectionModel::GroupBy_Disc: display_text = s.disc(); - case CollectionModel::GroupBy_Grouping: - display_text = s.grouping(); - case CollectionModel::GroupBy_Genre: - if (display_text.isNull()) display_text = s.genre(); - case CollectionModel::GroupBy_Album: - unique_tag = s.album_id(); - if (display_text.isNull()) { - display_text = s.album(); - } - // fallthrough - case CollectionModel::GroupBy_AlbumArtist: - if (display_text.isNull()) display_text = s.effective_albumartist(); - display_text = CollectionModel::TextOrUnknown(display_text); sort_text = CollectionModel::SortTextForArtist(display_text); has_album_icon = true; break; + case CollectionModel::GroupBy_Grouping: + display_text = CollectionModel::TextOrUnknown(s.grouping()); + sort_text = CollectionModel::SortTextForArtist(s.grouping()); + has_album_icon = true; + break; + + case CollectionModel::GroupBy_Genre: + display_text = CollectionModel::TextOrUnknown(s.genre()); + sort_text = CollectionModel::SortTextForArtist(s.genre()); + has_album_icon = true; + break; + + case CollectionModel::GroupBy_Album: + display_text = CollectionModel::TextOrUnknown(s.album()); + sort_text = CollectionModel::SortTextForArtist(s.album()); + unique_tag = s.album_id(); + has_album_icon = true; + break; + case CollectionModel::GroupBy_FileType: display_text = s.TextForFiletype(); sort_text = display_text; @@ -180,6 +205,9 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem return parent; } + if (display_text.isEmpty()) display_text = "Unknown"; + if (sort_text.isEmpty()) sort_text = "Unknown"; + // Find a container for this level key->group_[level] = display_text + QString::number(unique_tag); QStandardItem *container = containers_[*key]; diff --git a/src/internet/internetsearchmodel.h b/src/internet/internetsearchmodel.h index 1a416546..11b5d03c 100644 --- a/src/internet/internetsearchmodel.h +++ b/src/internet/internetsearchmodel.h @@ -81,8 +81,8 @@ class InternetSearchModel : public QStandardItemModel { QSortFilterProxyModel *proxy_; bool use_pretty_covers_; QIcon artist_icon_; - QPixmap no_cover_icon_; QIcon album_icon_; + QPixmap no_cover_icon_; CollectionModel::Grouping group_by_; QMap containers_; diff --git a/src/internet/internetsearchview.cpp b/src/internet/internetsearchview.cpp index e60ba52e..5b74fffc 100644 --- a/src/internet/internetsearchview.cpp +++ b/src/internet/internetsearchview.cpp @@ -144,8 +144,9 @@ InternetSearchView::InternetSearchView(Application *app, InternetSearch *engine, settings_menu->addAction(IconLoader::Load("configure"), QString("Configure %1...").arg(Song::TextForSource(engine->source())), this, SLOT(OpenSettingsDialog())); ui_->settings->setMenu(settings_menu); - connect(ui_->radiobutton_searchbyalbums, SIGNAL(clicked(bool)), SLOT(SearchByAlbumsClicked(bool))); - connect(ui_->radiobutton_searchbysongs, SIGNAL(clicked(bool)), SLOT(SearchBySongsClicked(bool))); + connect(ui_->radiobutton_search_artists, SIGNAL(clicked(bool)), SLOT(SearchArtistsClicked(bool))); + connect(ui_->radiobutton_search_albums, SIGNAL(clicked(bool)), SLOT(SearchAlbumsClicked(bool))); + connect(ui_->radiobutton_search_songs, SIGNAL(clicked(bool)), SLOT(SearchSongsClicked(bool))); connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*))); @@ -180,18 +181,21 @@ void InternetSearchView::ReloadSettings() { // Internet search settings s.beginGroup(settings_group_); - searchby_ = InternetSearch::SearchBy(s.value("searchby", int(InternetSearch::SearchBy_Songs)).toInt()); - switch (searchby_) { - case InternetSearch::SearchBy_Songs: - ui_->radiobutton_searchbysongs->setChecked(true); + search_type_ = InternetSearch::SearchType(s.value("type", int(InternetSearch::SearchType_Artists)).toInt()); + switch (search_type_) { + case InternetSearch::SearchType_Artists: + ui_->radiobutton_search_artists->setChecked(true); break; - case InternetSearch::SearchBy_Albums: - ui_->radiobutton_searchbyalbums->setChecked(true); + case InternetSearch::SearchType_Albums: + ui_->radiobutton_search_albums->setChecked(true); + break; + case InternetSearch::SearchType_Songs: + ui_->radiobutton_search_songs->setChecked(true); break; } SetGroupBy(CollectionModel::Grouping( - CollectionModel::GroupBy(s.value("group_by1", int(CollectionModel::GroupBy_Artist)).toInt()), + CollectionModel::GroupBy(s.value("group_by1", int(CollectionModel::GroupBy_AlbumArtist)).toInt()), CollectionModel::GroupBy(s.value("group_by2", int(CollectionModel::GroupBy_Album)).toInt()), CollectionModel::GroupBy(s.value("group_by3", int(CollectionModel::GroupBy_None)).toInt()))); s.endGroup(); @@ -233,7 +237,7 @@ void InternetSearchView::TextEdited(const QString &text) { } else { ui_->progressbar->reset(); - last_search_id_ = engine_->SearchAsync(trimmed, searchby_); + last_search_id_ = engine_->SearchAsync(trimmed, search_type_); } } @@ -293,7 +297,6 @@ void InternetSearchView::LazyLoadArt(const QModelIndex &proxy_index) { // Is this an album? const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt()); if (container_type != CollectionModel::GroupBy_Album && - container_type != CollectionModel::GroupBy_AlbumArtist && container_type != CollectionModel::GroupBy_YearAlbum && container_type != CollectionModel::GroupBy_OriginalYearAlbum) { return; @@ -545,19 +548,23 @@ void InternetSearchView::SetGroupBy(const CollectionModel::Grouping &g) { } -void InternetSearchView::SearchBySongsClicked(bool checked) { - SetSearchBy(InternetSearch::SearchBy_Songs); +void InternetSearchView::SearchArtistsClicked(bool checked) { + SetSearchType(InternetSearch::SearchType_Artists); } -void InternetSearchView::SearchByAlbumsClicked(bool checked) { - SetSearchBy(InternetSearch::SearchBy_Albums); +void InternetSearchView::SearchAlbumsClicked(bool checked) { + SetSearchType(InternetSearch::SearchType_Albums); } -void InternetSearchView::SetSearchBy(InternetSearch::SearchBy searchby) { - searchby_ = searchby; +void InternetSearchView::SearchSongsClicked(bool checked) { + SetSearchType(InternetSearch::SearchType_Songs); +} + +void InternetSearchView::SetSearchType(InternetSearch::SearchType type) { + search_type_ = type; QSettings s; s.beginGroup(settings_group_); - s.setValue("searchby", int(searchby)); + s.setValue("type", int(search_type_)); s.endGroup(); TextEdited(ui_->search->text()); } diff --git a/src/internet/internetsearchview.h b/src/internet/internetsearchview.h index 064d3a8e..dc943bc7 100644 --- a/src/internet/internetsearchview.h +++ b/src/internet/internetsearchview.h @@ -43,7 +43,6 @@ #include "settings/settingsdialog.h" #include "playlist/playlistmanager.h" #include "internetsearch.h" -//#include "settings/internetsettingspage.h" class Application; class GroupByDialog; @@ -93,10 +92,11 @@ signals: void SearchForThis(); - void SearchBySongsClicked(bool); - void SearchByAlbumsClicked(bool); + void SearchArtistsClicked(bool); + void SearchAlbumsClicked(bool); + void SearchSongsClicked(bool); void GroupByClicked(QAction *action); - void SetSearchBy(InternetSearch::SearchBy searchby); + void SetSearchType(InternetSearch::SearchType type); void SetGroupBy(const CollectionModel::Grouping &g); private: @@ -133,7 +133,7 @@ signals: QTimer *swap_models_timer_; - InternetSearch::SearchBy searchby_; + InternetSearch::SearchType search_type_; bool error_; }; diff --git a/src/internet/internetsearchview.ui b/src/internet/internetsearchview.ui index 3d0bdb85..83154281 100644 --- a/src/internet/internetsearchview.ui +++ b/src/internet/internetsearchview.ui @@ -72,7 +72,7 @@ true - Search by + Search for 10 @@ -80,14 +80,21 @@ - + + + ar&tists + + + + + a&lbums - + son&gs @@ -208,7 +215,7 @@ 0 0 398 - 502 + 521 diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index b63c66a1..a61203c0 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -52,7 +52,7 @@ class InternetService : public QObject { virtual void InitialLoadSettings() {} virtual void ReloadSettings() {} virtual QIcon Icon() { return Song::IconForSource(source_); } - virtual int Search(const QString &query, InternetSearch::SearchBy searchby) = 0; + virtual int Search(const QString &query, InternetSearch::SearchType type) = 0; virtual void CancelSearch() = 0; public slots: diff --git a/src/settings/tidalsettingspage.cpp b/src/settings/tidalsettingspage.cpp index cae65991..dea2ae5e 100644 --- a/src/settings/tidalsettingspage.cpp +++ b/src/settings/tidalsettingspage.cpp @@ -81,6 +81,7 @@ void TidalSettingsPage::Load() { else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password))); dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "HIGH"); ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt()); + ui_->spinbox_artistssearchlimit->setValue(s.value("artistssearchlimit", 5).toInt()); ui_->spinbox_albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt()); ui_->spinbox_songssearchlimit->setValue(s.value("songssearchlimit", 100).toInt()); ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool()); @@ -101,6 +102,7 @@ void TidalSettingsPage::Save() { s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64())); s.setValue("quality", ui_->combobox_quality->itemData(ui_->combobox_quality->currentIndex())); s.setValue("searchdelay", ui_->spinbox_searchdelay->value()); + s.setValue("artistssearchlimit", ui_->spinbox_artistssearchlimit->value()); s.setValue("albumssearchlimit", ui_->spinbox_albumssearchlimit->value()); s.setValue("songssearchlimit", ui_->spinbox_songssearchlimit->value()); s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked()); diff --git a/src/settings/tidalsettingspage.ui b/src/settings/tidalsettingspage.ui index a448aab5..4e6bd3e0 100644 --- a/src/settings/tidalsettingspage.ui +++ b/src/settings/tidalsettingspage.ui @@ -162,6 +162,49 @@ + + + + + + + 150 + 0 + + + + Artists search limit + + + + + + + 1 + + + 100 + + + 50 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 3e8285d8..6cb8eda1 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -63,15 +63,14 @@ const char *TidalService::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng=="; const int TidalService::kLoginAttempts = 1; const int TidalService::kTimeResetLoginAttempts = 60000; -typedef QPair Param; - TidalService::TidalService(Application *app, QObject *parent) : InternetService(Song::Source_Tidal, "Tidal", "tidal", app, parent), network_(new NetworkAccessManager(this)), url_handler_(new TidalUrlHandler(app, this)), - timer_searchdelay_(new QTimer(this)), + timer_search_delay_(new QTimer(this)), timer_login_attempt_(new QTimer(this)), - searchdelay_(1500), + search_delay_(1500), + artistssearchlimit_(1), albumssearchlimit_(1), songssearchlimit_(1), fetchalbums_(false), @@ -79,14 +78,17 @@ TidalService::TidalService(Application *app, QObject *parent) pending_search_id_(0), next_pending_search_id_(1), search_id_(0), - albums_requested_(0), - albums_received_(0), + artist_search_(false), + artist_albums_requested_(0), + artist_albums_received_(0), + album_songs_requested_(0), + album_songs_received_(0), login_sent_(false), login_attempts_(0) { - timer_searchdelay_->setSingleShot(true); - connect(timer_searchdelay_, SIGNAL(timeout()), SLOT(StartSearch())); + timer_search_delay_->setSingleShot(true); + connect(timer_search_delay_, SIGNAL(timeout()), SLOT(StartSearch())); timer_login_attempt_->setSingleShot(true); connect(timer_login_attempt_, SIGNAL(timeout()), SLOT(ResetLoginAttempts())); @@ -116,7 +118,8 @@ void TidalService::ReloadSettings() { if (password.isEmpty()) password_.clear(); else password_ = QString::fromUtf8(QByteArray::fromBase64(password)); quality_ = s.value("quality").toString(); - searchdelay_ = s.value("searchdelay", 1500).toInt(); + search_delay_ = s.value("searchdelay", 1500).toInt(); + artistssearchlimit_ = s.value("artistssearchlimit", 5).toInt(); albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt(); songssearchlimit_ = s.value("songssearchlimit", 100).toInt(); fetchalbums_ = s.value("fetchalbums", false).toBool(); @@ -333,7 +336,7 @@ QNetworkReply *TidalService::CreateRequest(const QString &ressource_name, const req.setRawHeader("X-Tidal-SessionId", session_id_.toUtf8()); QNetworkReply *reply = network_->get(req); - //qLog(Debug) << "Tidal: Sending request" << url; + qLog(Debug) << "Tidal: Sending request" << url; return reply; @@ -401,8 +404,6 @@ QJsonObject TidalService::ExtractJsonObj(QByteArray &data) { QJsonParseError error; QJsonDocument json_doc = QJsonDocument::fromJson(data, &error); - - //qLog(Debug) << json_doc; if (error.error != QJsonParseError::NoError) { Error("Reply from server missing Json data.", data); @@ -425,8 +426,6 @@ QJsonObject TidalService::ExtractJsonObj(QByteArray &data) { return QJsonObject(); } - //qLog(Debug) << json_obj; - return json_obj; } @@ -434,32 +433,36 @@ QJsonObject TidalService::ExtractJsonObj(QByteArray &data) { QJsonValue TidalService::ExtractItems(QByteArray &data) { QJsonObject json_obj = ExtractJsonObj(data); - if (json_obj.isEmpty()) return QJsonArray(); + if (json_obj.isEmpty()) return QJsonValue(); + return ExtractItems(json_obj); + +} + +QJsonValue TidalService::ExtractItems(QJsonObject &json_obj) { if (!json_obj.contains("items")) { Error("Json reply is missing items.", json_obj); return QJsonArray(); } QJsonValue json_items = json_obj["items"]; - return json_items; } -int TidalService::Search(const QString &text, InternetSearch::SearchBy searchby) { +int TidalService::Search(const QString &text, InternetSearch::SearchType type) { pending_search_id_ = next_pending_search_id_; pending_search_text_ = text; - pending_searchby_ = searchby; + pending_search_type_ = type; next_pending_search_id_++; if (text.isEmpty()) { - timer_searchdelay_->stop(); + timer_search_delay_->stop(); return pending_search_id_; } - timer_searchdelay_->setInterval(searchdelay_); - timer_searchdelay_->start(); + timer_search_delay_->setInterval(search_delay_); + timer_search_delay_->start(); return pending_search_id_; @@ -487,79 +490,215 @@ void TidalService::CancelSearch() { } void TidalService::ClearSearch() { + search_id_ = 0; search_text_.clear(); search_error_.clear(); - albums_requested_ = 0; - albums_received_ = 0; - requests_album_.clear(); + artist_search_ = false; + artist_albums_requested_ = 0; + artist_albums_received_ = 0; + album_songs_requested_ = 0; + album_songs_received_ = 0; + requests_artist_albums_.clear(); + requests_album_songs_.clear(); requests_song_.clear(); + requests_artist_album_.clear(); songs_.clear(); + } void TidalService::SendSearch() { emit UpdateStatus("Searching..."); - QList parameters; - parameters << Param("query", search_text_); - - QString searchparam; - switch (pending_searchby_) { - case InternetSearch::SearchBy_Songs: - searchparam = "search/tracks"; - parameters << Param("limit", QString::number(songssearchlimit_)); + switch (pending_search_type_) { + case InternetSearch::SearchType_Artists: + SendArtistsSearch(); + break; + case InternetSearch::SearchType_Albums: + SendAlbumsSearch(); + break; + case InternetSearch::SearchType_Songs: + SendSongsSearch(); break; - case InternetSearch::SearchBy_Albums: default: - searchparam = "search/albums"; - parameters << Param("limit", QString::number(albumssearchlimit_)); + Error("Invalid search type."); break; } - QNetworkReply *reply = CreateRequest(searchparam, parameters); - NewClosure(reply, SIGNAL(finished()), this, SLOT(SearchFinished(QNetworkReply*, int)), reply, search_id_); - } -void TidalService::SearchFinished(QNetworkReply *reply, int id) { +void TidalService::SendArtistsSearch() { + + artist_search_ = true; + + QList parameters; + parameters << Param("query", search_text_); + parameters << Param("limit", QString::number(artistssearchlimit_)); + QNetworkReply *reply = CreateRequest("search/artists", parameters); + NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistsReceived(QNetworkReply*, int)), reply, search_id_); + +} + +void TidalService::SendAlbumsSearch() { + + QList parameters; + parameters << Param("query", search_text_); + parameters << Param("limit", QString::number(albumssearchlimit_)); + QNetworkReply *reply = CreateRequest("search/albums", parameters); + NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, search_id_, 0); + +} + +void TidalService::SendSongsSearch() { + + QList parameters; + parameters << Param("query", search_text_); + parameters << Param("limit", QString::number(songssearchlimit_)); + QNetworkReply *reply = CreateRequest("search/tracks", parameters); + NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, search_id_, 0); + +} + +void TidalService::ArtistsReceived(QNetworkReply *reply, int search_id) { reply->deleteLater(); - if (id != search_id_) return; + if (search_id != search_id_) return; QByteArray data = GetReplyData(reply, true); if (data.isEmpty()) { + artist_search_ = false; CheckFinish(); return; } + QJsonValue json_value = ExtractItems(data); if (!json_value.isArray()) { + artist_search_ = false; CheckFinish(); return; } QJsonArray json_items = json_value.toArray(); if (json_items.isEmpty()) { + artist_search_ = false; Error("No match."); return; } - //qLog(Debug) << json_items; - - QVector albums; for (const QJsonValue &value : json_items) { - //qLog(Debug) << value; if (!value.isObject()) { qLog(Error) << "Tidal: Invalid Json reply, item not a object."; qLog(Debug) << value; continue; } QJsonObject json_obj = value.toObject(); - //qLog(Debug) << json_obj; - int album_id(0); - QString album(""); - if (json_obj.contains("type")) { - // This was a albums search + + if (!json_obj.contains("id") || !json_obj.contains("name")) { + qLog(Error) << "Tidal: Invalid Json reply, item missing type or album."; + qLog(Debug) << json_obj; + continue; + } + + int artist_id = json_obj["id"].toInt(); + if (requests_artist_albums_.contains(artist_id)) continue; + requests_artist_albums_.append(artist_id); + GetAlbums(artist_id); + artist_albums_requested_++; + if (artist_albums_requested_ >= artistssearchlimit_) break; + + } + + if (artist_albums_requested_ > 0) { + emit UpdateStatus(QString("Retrieving albums for %1 artist%2...").arg(artist_albums_requested_).arg(artist_albums_requested_ == 1 ? "" : "s")); + emit ProgressSetMaximum(artist_albums_requested_); + emit UpdateProgress(0); + } + + CheckFinish(); + +} + +void TidalService::GetAlbums(const int artist_id, const int offset) { + + QList parameters; + if (offset > 0) parameters << Param("offset", QString::number(offset)); + QNetworkReply *reply = CreateRequest(QString("artists/%1/albums").arg(artist_id), parameters); + NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int, int)), reply, search_id_, artist_id, offset); + +} + +void TidalService::AlbumsReceived(QNetworkReply *reply, int search_id, int artist_id, int offset_requested) { + + reply->deleteLater(); + + if (search_id != search_id_) return; + + if (artist_search_) { + if (!requests_artist_albums_.contains(artist_id)) return; + artist_albums_received_++; + emit UpdateProgress(artist_albums_received_); + } + + QByteArray data = GetReplyData(reply, true); + if (data.isEmpty()) { + AlbumsFinished(artist_id, offset_requested); + return; + } + + QJsonObject json_obj = ExtractJsonObj(data); + if (json_obj.isEmpty()) { + AlbumsFinished(artist_id, offset_requested); + return; + } + + int limit = 0; + int offset = 0; + int total_albums = 0; + if (artist_search_) { // This was a list of albums by artist + if (!json_obj.contains("limit") || + !json_obj.contains("offset") || + !json_obj.contains("totalNumberOfItems") || + !json_obj.contains("items")) { + AlbumsFinished(artist_id, offset_requested); + Error("Json object missing values.", json_obj); + return; + } + limit = json_obj["limit"].toInt(); + offset = json_obj["offset"].toInt(); + total_albums = json_obj["totalNumberOfItems"].toInt(); + if (offset != offset_requested) { + AlbumsFinished(artist_id, offset_requested, total_albums, limit); + Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested)); + return; + } + } + + QJsonValue json_value = ExtractItems(json_obj); + if (!json_value.isArray()) { + AlbumsFinished(artist_id, offset_requested, total_albums, limit); + return; + } + QJsonArray json_items = json_value.toArray(); + if (json_items.isEmpty()) { + if (!artist_search_) Error("No match."); + AlbumsFinished(artist_id, offset_requested, total_albums, limit); + return; + } + + int albums = 0; + for (const QJsonValue &value : json_items) { + albums++; + if (!value.isObject()) { + qLog(Error) << "Tidal: Invalid Json reply, item not a object."; + qLog(Debug) << value; + continue; + } + QJsonObject json_obj = value.toObject(); + + int album_id = 0; + QString album; + if (json_obj.contains("type")) { // This was a albums search if (!json_obj.contains("id") || !json_obj.contains("title")) { qLog(Error) << "Tidal: Invalid Json reply, item is missing ID or title."; qLog(Debug) << json_obj; @@ -568,8 +707,7 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) { album_id = json_obj["id"].toInt(); album = json_obj["title"].toString(); } - else if (json_obj.contains("album")) { - // This was a tracks search + else if (json_obj.contains("album")) { // This was a tracks search if (!fetchalbums_) { Song song = ParseSong(0, value); songs_ << song; @@ -589,6 +727,7 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) { } album_id = json_album["id"].toInt(); album = json_album["title"].toString(); + } else { qLog(Error) << "Tidal: Invalid Json reply, item missing type or album."; @@ -596,7 +735,7 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) { continue; } - if (requests_album_.contains(album_id)) continue; + if (requests_album_songs_.contains(album_id)) continue; if (!json_obj.contains("artist") || !json_obj.contains("title") || !json_obj.contains("audioQuality")) { qLog(Error) << "Tidal: Invalid Json reply, item missing artist, title or audioQuality."; @@ -622,45 +761,77 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) { //qLog(Debug) << "Tidal:" << artist << album << quality << copyright; - QString artist_album(QString("%1-%2").arg(artist).arg(album)); - if (albums.contains(artist_album)) { + QPair artist_album(artist.toLower(), album.toLower()); + if (requests_artist_album_.contains(artist_album)) { qLog(Debug) << "Tidal: Skipping duplicate album" << artist << album << quality << copyright; continue; } - albums.insert(0, artist_album); + requests_artist_album_.append(artist_album); - requests_album_.insert(album_id, album_id); - GetAlbum(album_id); - albums_requested_++; - if (albums_requested_ >= albumssearchlimit_) break; + requests_album_songs_.insert(album_id, artist); + album_songs_requested_++; + if (album_songs_requested_ >= albumssearchlimit_) break; } - if (albums_requested_ > 0) { - emit UpdateStatus(QString("Retrieving %1 album%2...").arg(albums_requested_).arg(albums_requested_ == 1 ? "" : "s")); - emit ProgressSetMaximum(albums_requested_); - emit UpdateProgress(0); + AlbumsFinished(artist_id, offset_requested, total_albums, limit, albums); + +} + +void TidalService::AlbumsFinished(const int artist_id, const int offset_requested, const int total_albums, const int limit, const int albums) { + + if (artist_search_) { // This is a artist search. + if (albums > limit) { + Error("Albums returned does not match limit returned!"); + } + int offset_next = offset_requested + albums; + if (album_songs_requested_ < albumssearchlimit_ && offset_next < total_albums) { + GetAlbums(artist_id, offset_next); + artist_albums_requested_++; + } + else if (artist_albums_received_ >= artist_albums_requested_) { // Artist search is finished. + artist_search_ = false; + } + } + + if (!artist_search_) { + // Get songs for the albums. + QHashIterator i(requests_album_songs_); + while (i.hasNext()) { + i.next(); + GetSongs(i.key()); + } + + if (album_songs_requested_ > 0) { + emit UpdateStatus(QString("Retrieving songs for %1 album%2...").arg(album_songs_requested_).arg(album_songs_requested_ == 1 ? "" : "s")); + emit ProgressSetMaximum(album_songs_requested_); + emit UpdateProgress(0); + } } CheckFinish(); } -void TidalService::GetAlbum(const int album_id) { +void TidalService::GetSongs(const int album_id) { QList parameters; QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(album_id), parameters); - NewClosure(reply, SIGNAL(finished()), this, SLOT(GetAlbumFinished(QNetworkReply*, int, int)), reply, search_id_, album_id); + NewClosure(reply, SIGNAL(finished()), this, SLOT(SongsReceived(QNetworkReply*, int, int)), reply, search_id_, album_id); } -void TidalService::GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id) { +void TidalService::SongsReceived(QNetworkReply *reply, int search_id, int album_id) { reply->deleteLater(); if (search_id != search_id_) return; - if (!requests_album_.contains(album_id)) return; - albums_received_++; - emit UpdateProgress(albums_received_); + if (!requests_album_songs_.contains(album_id)) return; + QString album_artist = requests_album_songs_[album_id]; + + album_songs_received_++; + if (!artist_search_) { + emit UpdateProgress(album_songs_received_); + } QByteArray data = GetReplyData(reply); if (data.isEmpty()) { @@ -682,14 +853,12 @@ void TidalService::GetAlbumFinished(QNetworkReply *reply, int search_id, int alb bool compilation = false; bool multidisc = false; - Song first_song; SongList songs; for (const QJsonValue &value : json_items) { - Song song = ParseSong(album_id, value); + Song song = ParseSong(album_id, value, album_artist); if (!song.is_valid()) continue; if (song.disc() >= 2) multidisc = true; - if (song.is_compilation() || (first_song.is_valid() && song.artist() != first_song.artist())) compilation = true; - if (!first_song.is_valid()) first_song = song; + if (song.is_compilation()) compilation = true; songs << song; } for (Song &song : songs) { @@ -705,7 +874,7 @@ void TidalService::GetAlbumFinished(QNetworkReply *reply, int search_id, int alb } -Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &value) { +Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &value, QString album_artist) { Song song; @@ -716,8 +885,6 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val } QJsonObject json_obj = value.toObject(); - //qLog(Debug) << json_obj; - if ( !json_obj.contains("album") || !json_obj.contains("allowStreaming") || @@ -786,7 +953,6 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val if (!allow_streaming || !stream_ready) { qLog(Error) << "Tidal: Skipping song" << artist << album << title << "because allowStreaming is false OR streamReady is false."; - //qLog(Debug) << json_obj; return song; } @@ -798,8 +964,9 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val song.set_source(Song::Source_Tidal); song.set_id(song_id); song.set_album_id(album_id); - song.set_artist(artist); + if (album_artist != artist) song.set_albumartist(album_artist); song.set_album(album); + song.set_artist(artist); song.set_title(title); song.set_track(track); song.set_disc(disc); @@ -836,11 +1003,11 @@ void TidalService::GetStreamURL(const QUrl &url) { QNetworkReply *reply = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id), parameters); - NewClosure(reply, SIGNAL(finished()), this, SLOT(GetStreamURLFinished(QNetworkReply*, int, QUrl)), reply, song_id, url); + NewClosure(reply, SIGNAL(finished()), this, SLOT(StreamURLReceived(QNetworkReply*, int, QUrl)), reply, song_id, url); } -void TidalService::GetStreamURLFinished(QNetworkReply *reply, const int song_id, const QUrl original_url) { +void TidalService::StreamURLReceived(QNetworkReply *reply, const int song_id, const QUrl original_url) { reply->deleteLater(); if (requests_song_.contains(song_id)) requests_song_.remove(song_id); @@ -892,7 +1059,7 @@ void TidalService::CheckFinish() { if (search_id_ == 0) return; - if (!login_sent_ && albums_requested_ <= albums_received_) { + if (!login_sent_ && !artist_search_ && artist_albums_requested_ <= artist_albums_received_ && album_songs_requested_ <= album_songs_received_) { if (songs_.isEmpty()) { if (search_error_.isEmpty()) emit SearchError(search_id_, "Unknown error"); else emit SearchError(search_id_, search_error_); diff --git a/src/tidal/tidalservice.h b/src/tidal/tidalservice.h index 40ebdf72..ccf92d41 100644 --- a/src/tidal/tidalservice.h +++ b/src/tidal/tidalservice.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -54,7 +55,7 @@ class TidalService : public InternetService { void ReloadSettings(); void Logout(); - int Search(const QString &query, InternetSearch::SearchBy searchby); + int Search(const QString &query, InternetSearch::SearchType type); void CancelSearch(); const bool login_sent() { return login_sent_; } @@ -84,20 +85,29 @@ class TidalService : public InternetService { void HandleAuthReply(QNetworkReply *reply); void ResetLoginAttempts(); void StartSearch(); - void SearchFinished(QNetworkReply *reply, int search_id); - void GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id); - void GetStreamURLFinished(QNetworkReply *reply, const int song_id, const QUrl original_url); + void ArtistsReceived(QNetworkReply *reply, int search_id); + void AlbumsReceived(QNetworkReply *reply, int search_id, int artist_id, int offset_requested = 0); + void AlbumsFinished(const int artist_id, const int offset_requested, const int total_albums = 0, const int limit = 0, const int albums = 0); + void SongsReceived(QNetworkReply *reply, int search_id, int album_id); + void StreamURLReceived(QNetworkReply *reply, const int song_id, const QUrl original_url); private: + typedef QPair Param; + void ClearSearch(); void LoadSessionID(); QNetworkReply *CreateRequest(const QString &ressource_name, const QList> ¶ms); QByteArray GetReplyData(QNetworkReply *reply, const bool sendlogin = false); QJsonObject ExtractJsonObj(QByteArray &data); QJsonValue ExtractItems(QByteArray &data); + QJsonValue ExtractItems(QJsonObject &json_obj); void SendSearch(); - void GetAlbum(const int album_id); - Song ParseSong(const int album_id_requested, const QJsonValue &value); + void SendArtistsSearch(); + void SendAlbumsSearch(); + void SendSongsSearch(); + void GetAlbums(const int artist_id, const int offset = 0); + void GetSongs(const int album_id); + Song ParseSong(const int album_id_requested, const QJsonValue &value, QString album_artist = QString()); void CheckFinish(); void Error(QString error, QVariant debug = QVariant()); @@ -110,13 +120,14 @@ class TidalService : public InternetService { NetworkAccessManager *network_; TidalUrlHandler *url_handler_; - QTimer *timer_searchdelay_; + QTimer *timer_search_delay_; QTimer *timer_login_attempt_; QString username_; QString password_; QString quality_; - int searchdelay_; + int search_delay_; + int artistssearchlimit_; int albumssearchlimit_; int songssearchlimit_; bool fetchalbums_; @@ -130,14 +141,19 @@ class TidalService : public InternetService { int pending_search_id_; int next_pending_search_id_; QString pending_search_text_; - InternetSearch::SearchBy pending_searchby_; + InternetSearch::SearchType pending_search_type_; int search_id_; QString search_text_; - QHash requests_album_; + bool artist_search_; + QList requests_artist_albums_; + QHash requests_album_songs_; QHash requests_song_; - int albums_requested_; - int albums_received_; + QList> requests_artist_album_; + int artist_albums_requested_; + int artist_albums_received_; + int album_songs_requested_; + int album_songs_received_; SongList songs_; QString search_error_; bool login_sent_;