From 5c2ca1e3d9dee9c7ae5c02460924499fe2eed101 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Fri, 7 Jun 2019 20:23:05 +0200 Subject: [PATCH] Add tidal add/remove favorites + more tidal fixes --- src/CMakeLists.txt | 2 + src/internet/internetcollectionview.cpp | 15 +- src/internet/internetcollectionview.h | 7 +- src/internet/internetsearchview.cpp | 45 +++- src/internet/internetsearchview.h | 14 +- src/internet/internetservice.h | 10 +- src/internet/internettabsview.cpp | 11 +- src/tidal/tidalbaserequest.cpp | 3 - src/tidal/tidalbaserequest.h | 1 + src/tidal/tidalfavoriterequest.cpp | 292 ++++++++++++++++++++++++ src/tidal/tidalfavoriterequest.h | 84 +++++++ src/tidal/tidalrequest.cpp | 18 +- src/tidal/tidalrequest.h | 4 +- src/tidal/tidalservice.cpp | 42 +++- src/tidal/tidalservice.h | 6 + src/tidal/tidalstreamurlrequest.cpp | 29 +-- src/tidal/tidalstreamurlrequest.h | 2 +- 17 files changed, 533 insertions(+), 52 deletions(-) create mode 100644 src/tidal/tidalfavoriterequest.cpp create mode 100644 src/tidal/tidalfavoriterequest.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc79277fc..834e95c35 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -893,6 +893,7 @@ optional_source(HAVE_TIDAL tidal/tidalbaserequest.cpp tidal/tidalrequest.cpp tidal/tidalstreamurlrequest.cpp + tidal/tidalfavoriterequest.cpp settings/tidalsettingspage.cpp covermanager/tidalcoverprovider.cpp HEADERS @@ -901,6 +902,7 @@ optional_source(HAVE_TIDAL tidal/tidalbaserequest.h tidal/tidalrequest.h tidal/tidalstreamurlrequest.h + tidal/tidalfavoriterequest.h settings/tidalsettingspage.h covermanager/tidalcoverprovider.h UI diff --git a/src/internet/internetcollectionview.cpp b/src/internet/internetcollectionview.cpp index 7d2e5505f..c20f2277e 100644 --- a/src/internet/internetcollectionview.cpp +++ b/src/internet/internetcollectionview.cpp @@ -301,9 +301,8 @@ void InternetCollectionView::contextMenuEvent(QContextMenuEvent *e) { context_menu_->addSeparator(); - //add_songs_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Add songs"), this, SLOT(AddSongs())); - //remove_songs_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Remove songs"), this, SLOT(RemoveSongs())); - //context_menu_->addSeparator(); + remove_songs_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Remove from favorites"), this, SLOT(RemoveSongs())); + context_menu_->addSeparator(); if (filter_) context_menu_->addMenu(filter_->menu()); @@ -321,8 +320,7 @@ void InternetCollectionView::contextMenuEvent(QContextMenuEvent *e) { add_to_playlist_->setEnabled(songs_selected); open_in_new_playlist_->setEnabled(songs_selected); add_to_playlist_enqueue_->setEnabled(songs_selected); - //add_songs_->setEnabled(songs_selected); - //remove_songs_->setEnabled(songs_selected); + remove_songs_->setEnabled(songs_selected); context_menu_->popup(e->globalPos()); @@ -374,13 +372,6 @@ void InternetCollectionView::OpenInNewPlaylist() { } - -void InternetCollectionView::AddSongs() { - - emit AddSongs(GetSelectedSongs()); - -} - void InternetCollectionView::RemoveSongs() { emit RemoveSongs(GetSelectedSongs()); diff --git a/src/internet/internetcollectionview.h b/src/internet/internetcollectionview.h index df33b6bac..586859be2 100644 --- a/src/internet/internetcollectionview.h +++ b/src/internet/internetcollectionview.h @@ -89,8 +89,7 @@ class InternetCollectionView : public AutoExpandingTreeView { void TotalArtistCountUpdated_(); void TotalAlbumCountUpdated_(); void Error(const QString &message); - void AddSongs(const SongList songs); - void RemoveSongs(const SongList songs); + void RemoveSongs(const SongList &songs); protected: // QWidget @@ -104,7 +103,6 @@ class InternetCollectionView : public AutoExpandingTreeView { void AddToPlaylistEnqueue(); void AddToPlaylistEnqueueNext(); void OpenInNewPlaylist(); - void AddSongs(); void RemoveSongs(); private: @@ -131,8 +129,7 @@ class InternetCollectionView : public AutoExpandingTreeView { QAction *add_to_playlist_enqueue_; QAction *add_to_playlist_enqueue_next_; QAction *open_in_new_playlist_; - //QAction *add_songs_; - //QAction *remove_songs_; + QAction *remove_songs_; bool is_in_keyboard_search_; diff --git a/src/internet/internetsearchview.cpp b/src/internet/internetsearchview.cpp index 181c588dd..12b880869 100644 --- a/src/internet/internetsearchview.cpp +++ b/src/internet/internetsearchview.cpp @@ -110,12 +110,15 @@ InternetSearchView::InternetSearchView(QWidget *parent) InternetSearchView::~InternetSearchView() { delete ui_; } -void InternetSearchView::Init(Application *app, InternetSearch *engine, QString settings_group, SettingsDialog::Page settings_page) { +void InternetSearchView::Init(Application *app, InternetSearch *engine, QString settings_group, SettingsDialog::Page settings_page, const bool artists, const bool albums, const bool songs) { app_ = app; engine_ = engine; settings_group_ = settings_group; settings_page_ = settings_page; + artists_ = artists; + albums_ = albums; + songs_ = songs; front_model_ = new InternetSearchModel(engine_, this); back_model_ = new InternetSearchModel(engine_, this); @@ -435,6 +438,16 @@ bool InternetSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) { context_menu_->addSeparator(); + if (artists_ || albums_ || songs_) { + if (artists_) + context_actions_ << context_menu_->addAction(IconLoader::Load("document-new"), tr("Add to artists"), this, SLOT(AddArtists())); + if (albums_) + context_actions_ << context_menu_->addAction(IconLoader::Load("document-new"), tr("Add to albums"), this, SLOT(AddAlbums())); + if (songs_) + context_actions_ << context_menu_->addAction(IconLoader::Load("document-new"), tr("Add to songs"), this, SLOT(AddSongs())); + context_menu_->addSeparator(); + } + if (ui_->results->selectionModel() && ui_->results->selectionModel()->selectedRows().length() == 1) { context_actions_ << context_menu_->addAction(IconLoader::Load("search"), tr("Search for this"), this, SLOT(SearchForThis())); } @@ -593,3 +606,33 @@ void InternetSearchView::ProgressSetMaximum(int max) { void InternetSearchView::UpdateProgress(int progress) { ui_->progressbar->setValue(progress); } + +void InternetSearchView::AddArtists() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { + emit AddArtistsSignal(internet_song_data->songs); + } + +} + +void InternetSearchView::AddAlbums() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { + emit AddAlbumsSignal(internet_song_data->songs); + } + +} + +void InternetSearchView::AddSongs() { + + MimeData *data = SelectedMimeData(); + if (!data) return; + if (const InternetSongMimeData *internet_song_data = qobject_cast(data)) { + emit AddSongsSignal(internet_song_data->songs); + } + +} diff --git a/src/internet/internetsearchview.h b/src/internet/internetsearchview.h index cf48b5ad7..9499368c2 100644 --- a/src/internet/internetsearchview.h +++ b/src/internet/internetsearchview.h @@ -59,7 +59,7 @@ class InternetSearchView : public QWidget { InternetSearchView(QWidget *parent = nullptr); ~InternetSearchView(); - void Init(Application *app, InternetSearch *engine, QString settings_group, SettingsDialog::Page settings_page); + void Init(Application *app, InternetSearch *engine, QString settings_group, SettingsDialog::Page settings_page, const bool artists = false, const bool albums = false, const bool songs = false); static const int kSwapModelsTimeoutMsec; @@ -75,8 +75,11 @@ class InternetSearchView : public QWidget { void FocusSearchField(); void OpenSettingsDialog(); -signals: + signals: void AddToPlaylist(QMimeData *data); + void AddArtistsSignal(SongList songs); + void AddAlbumsSignal(SongList songs); + void AddSongsSignal(SongList songs); private slots: void SwapModels(); @@ -104,6 +107,10 @@ signals: void SetSearchType(InternetSearch::SearchType type); void SetGroupBy(const CollectionModel::Grouping &g); + void AddArtists(); + void AddAlbums(); + void AddSongs(); + private: MimeData *SelectedMimeData(); @@ -116,6 +123,9 @@ signals: SettingsDialog::Page settings_page_; Ui_InternetSearchView *ui_; QScopedPointer group_by_dialog_; + bool artists_; + bool albums_; + bool songs_; QMenu *context_menu_; QList context_actions_; diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index 9e3cfd223..3bf7e88b6 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -109,7 +109,15 @@ class InternetService : public QObject { void SearchProgressSetMaximum(int max); void SearchUpdateProgress(int max); - void StreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType, QString error = QString()); + void AddArtists(const SongList& songs); + void AddAlbums(const SongList& songs); + void AddSongs(const SongList& songs); + + void RemoveArtists(const SongList& songs); + void RemoveAlbums(const SongList& songs); + void RemoveSongs(const SongList& songs); + + void StreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType filetype, QString error = QString()); protected: Application *app_; diff --git a/src/internet/internettabsview.cpp b/src/internet/internettabsview.cpp index f237afc4e..ee75c8647 100644 --- a/src/internet/internettabsview.cpp +++ b/src/internet/internettabsview.cpp @@ -45,7 +45,10 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I ui_->setupUi(this); - ui_->search_view->Init(app, engine, settings_group, settings_page); + ui_->search_view->Init(app, engine, settings_group, settings_page, service_->artists_collection_model(), service_->albums_collection_model(), service_->songs_collection_model()); + connect(ui_->search_view, SIGNAL(AddArtistsSignal(const SongList&)), service_, SIGNAL(AddArtists(const SongList&))); + connect(ui_->search_view, SIGNAL(AddAlbumsSignal(const SongList&)), service_, SIGNAL(AddAlbums(const SongList&))); + connect(ui_->search_view, SIGNAL(AddSongsSignal(const SongList&)), service_, SIGNAL(AddSongs(const SongList&))); if (service_->artists_collection_model()) { ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page()); @@ -55,6 +58,8 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I ui_->artists_collection->filter()->SetCollectionModel(service_->artists_collection_model()); connect(ui_->artists_collection->view(), SIGNAL(GetSongs()), SLOT(GetArtists())); + connect(ui_->artists_collection->view(), SIGNAL(RemoveSongs(const SongList&)), service_, SIGNAL(RemoveArtists(const SongList&))); + connect(ui_->artists_collection->button_refresh(), SIGNAL(clicked()), SLOT(GetArtists())); connect(ui_->artists_collection->button_close(), SIGNAL(clicked()), SLOT(AbortGetArtists())); connect(ui_->artists_collection->button_abort(), SIGNAL(clicked()), SLOT(AbortGetArtists())); @@ -83,6 +88,8 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I ui_->albums_collection->filter()->SetCollectionModel(service_->albums_collection_model()); connect(ui_->albums_collection->view(), SIGNAL(GetSongs()), SLOT(GetAlbums())); + connect(ui_->albums_collection->view(), SIGNAL(RemoveSongs(const SongList&)), service_, SIGNAL(RemoveAlbums(const SongList&))); + connect(ui_->albums_collection->button_refresh(), SIGNAL(clicked()), SLOT(GetAlbums())); connect(ui_->albums_collection->button_close(), SIGNAL(clicked()), SLOT(AbortGetAlbums())); connect(ui_->albums_collection->button_abort(), SIGNAL(clicked()), SLOT(AbortGetAlbums())); @@ -111,6 +118,8 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, I ui_->songs_collection->filter()->SetCollectionModel(service_->songs_collection_model()); connect(ui_->songs_collection->view(), SIGNAL(GetSongs()), SLOT(GetSongs())); + connect(ui_->songs_collection->view(), SIGNAL(RemoveSongs(const SongList&)), service_, SIGNAL(RemoveSongs(const SongList&))); + connect(ui_->songs_collection->button_refresh(), SIGNAL(clicked()), SLOT(GetSongs())); connect(ui_->songs_collection->button_close(), SIGNAL(clicked()), SLOT(AbortGetSongs())); connect(ui_->songs_collection->button_abort(), SIGNAL(clicked()), SLOT(AbortGetSongs())); diff --git a/src/tidal/tidalbaserequest.cpp b/src/tidal/tidalbaserequest.cpp index a1e35ee1f..920cb9672 100644 --- a/src/tidal/tidalbaserequest.cpp +++ b/src/tidal/tidalbaserequest.cpp @@ -96,9 +96,6 @@ QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, QString &error, replies_.removeAll(reply); reply->deleteLater(); } - else { - return QByteArray(); - } QByteArray data; diff --git a/src/tidal/tidalbaserequest.h b/src/tidal/tidalbaserequest.h index 0d695c4a7..15a929770 100644 --- a/src/tidal/tidalbaserequest.h +++ b/src/tidal/tidalbaserequest.h @@ -75,6 +75,7 @@ class TidalBaseRequest : public QObject { QString Error(QString error, QVariant debug = QVariant()); + QString api_url() { return QString(kApiUrl); } QString token() { return service_->token(); } QString username() { return service_->username(); } QString password() { return service_->password(); } diff --git a/src/tidal/tidalfavoriterequest.cpp b/src/tidal/tidalfavoriterequest.cpp new file mode 100644 index 000000000..f1f6ab6b4 --- /dev/null +++ b/src/tidal/tidalfavoriterequest.cpp @@ -0,0 +1,292 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/logging.h" +#include "core/network.h" +#include "core/closure.h" +#include "core/song.h" +#include "tidalservice.h" +#include "tidalbaserequest.h" +#include "tidalfavoriterequest.h" + +TidalFavoriteRequest::TidalFavoriteRequest(TidalService *service, NetworkAccessManager *network, QObject *parent) + : TidalBaseRequest(service, network, parent), + service_(service), + network_(network), + need_login_(false) {} + +TidalFavoriteRequest::~TidalFavoriteRequest() { + + while (!replies_.isEmpty()) { + QNetworkReply *reply = replies_.takeFirst(); + disconnect(reply, 0, nullptr, 0); + reply->abort(); + reply->deleteLater(); + } + +} + +QString TidalFavoriteRequest::FavoriteText(const FavoriteType type) { + + switch (type) { + case FavoriteType_Artists: + return "artists"; + case FavoriteType_Albums: + return "albums"; + case FavoriteType_Songs: + default: + return "tracks"; + } + +} + +void TidalFavoriteRequest::AddArtists(const SongList &songs) { + AddFavorites(FavoriteType_Artists, songs); +} + +void TidalFavoriteRequest::AddAlbums(const SongList &songs) { + AddFavorites(FavoriteType_Albums, songs); +} + +void TidalFavoriteRequest::AddSongs(const SongList &songs) { + AddFavorites(FavoriteType_Songs, songs); +} + +void TidalFavoriteRequest::AddFavorites(const FavoriteType type, const SongList &songs) { + + if (songs.isEmpty()) return; + + QString text; + switch (type) { + case FavoriteType_Artists: + text = "artistIds"; + break; + case FavoriteType_Albums: + text = "albumIds"; + break; + case FavoriteType_Songs: + text = "trackIds"; + break; + } + + QStringList ids_list; + for (const Song &song : songs) { + QString id; + switch (type) { + case FavoriteType_Artists: + if (song.artist_id() <= 0) continue; + id = QString::number(song.artist_id()); + break; + case FavoriteType_Albums: + if (song.album_id() <= 0) continue; + id = QString::number(song.album_id()); + break; + case FavoriteType_Songs: + if (song.song_id() <= 0) continue; + id = QString::number(song.song_id()); + break; + } + if (id.isEmpty()) continue; + if (!ids_list.contains(id)) { + ids_list << id; + } + } + if (ids_list.isEmpty()) return; + + QString ids = ids_list.join(','); + + typedef QPair EncodedParam; + typedef QList EncodedParamList; + + ParamList params = ParamList() << Param("countryCode", country_code()) + << Param(text, ids); + + QStringList query_items; + QUrlQuery url_query; + for (const Param& param : params) { + EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); + query_items << QString(encoded_param.first + "=" + encoded_param.second); + url_query.addQueryItem(encoded_param.first, encoded_param.second); + } + + QUrl url(api_url() + QString("/") + "users/" + QString::number(service_->user_id()) + "/favorites/" + FavoriteText(type)); + QNetworkRequest req(url); + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8()); + QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8(); + QNetworkReply *reply = network_->post(req, query); + NewClosure(reply, SIGNAL(finished()), this, SLOT(AddFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs); + replies_ << reply; + + qLog(Debug) << "Tidal: Sending request" << url << query; + +} + +void TidalFavoriteRequest::AddFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs) { + + if (replies_.contains(reply)) { + replies_.removeAll(reply); + reply->deleteLater(); + } + else { + return; + } + + QString error; + QByteArray data = GetReplyData(reply, error, false); + + if (reply->error() != QNetworkReply::NoError) { + return; + } + + qLog(Debug) << "Tidal:" << songs.count() << "songs added to" << FavoriteText(type) << "favorites."; + + switch (type) { + case FavoriteType_Artists: + emit ArtistsAdded(songs); + break; + case FavoriteType_Albums: + emit AlbumsAdded(songs); + break; + case FavoriteType_Songs: + emit SongsAdded(songs); + break; + } + +} + +void TidalFavoriteRequest::RemoveArtists(const SongList &songs) { + RemoveFavorites(FavoriteType_Artists, songs); +} + +void TidalFavoriteRequest::RemoveAlbums(const SongList &songs) { + RemoveFavorites(FavoriteType_Albums, songs); +} + +void TidalFavoriteRequest::RemoveSongs(const SongList &songs) { + RemoveFavorites(FavoriteType_Songs, songs); +} + +void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongList songs) { + + if (songs.isEmpty()) return; + + QList ids; + QMultiMap songs_map; + for (const Song &song : songs) { + int id = -1; + switch (type) { + case FavoriteType_Artists: + if (song.artist_id() <= 0) continue; + id = song.artist_id(); + break; + case FavoriteType_Albums: + if (song.album_id() <= 0) continue; + id = song.album_id(); + break; + case FavoriteType_Songs: + if (song.song_id() <= 0) continue; + id = song.song_id(); + break; + } + if (!ids.contains(id)) ids << id; + songs_map.insertMulti(id, song); + } + + for (int id : ids) { + SongList songs_list = songs_map.values(id); + RemoveFavorites(type, id, songs_list); + } + +} + +void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const int id, const SongList &songs) { + + typedef QPair EncodedParam; + typedef QList EncodedParamList; + + ParamList params = ParamList() << Param("countryCode", country_code()); + + QStringList query_items; + QUrlQuery url_query; + for (const Param& param : params) { + EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); + query_items << QString(encoded_param.first + "=" + encoded_param.second); + url_query.addQueryItem(encoded_param.first, encoded_param.second); + } + + QUrl url(api_url() + QString("/") + "users/" + QString::number(service_->user_id()) + "/favorites/" + FavoriteText(type) + QString("/") + QString::number(id)); + url.setQuery(url_query); + QNetworkRequest req(url); + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8()); + QNetworkReply *reply = network_->deleteResource(req); + NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoveFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs); + replies_ << reply; + + qLog(Debug) << "Tidal: Sending request" << url << "with" << songs.count() << "songs"; + +} + +void TidalFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs) { + + if (replies_.contains(reply)) { + replies_.removeAll(reply); + reply->deleteLater(); + } + else { + return; + } + + QString error; + QByteArray data = GetReplyData(reply, error, false); + if (reply->error() != QNetworkReply::NoError) { + return; + } + + qLog(Debug) << "Tidal:" << songs.count() << "songs removed from" << FavoriteText(type) << "favorites."; + + switch (type) { + case FavoriteType_Artists: + emit ArtistsRemoved(songs); + break; + case FavoriteType_Albums: + emit AlbumsRemoved(songs); + break; + case FavoriteType_Songs: + emit SongsRemoved(songs); + break; + } + +} diff --git a/src/tidal/tidalfavoriterequest.h b/src/tidal/tidalfavoriterequest.h new file mode 100644 index 000000000..45e5b16d9 --- /dev/null +++ b/src/tidal/tidalfavoriterequest.h @@ -0,0 +1,84 @@ +/* + * Strawberry Music Player + * Copyright 2018, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TIDALFAVORITEREQUEST_H +#define TIDALFAVORITEREQUEST_H + +#include "config.h" + +#include + +#include "tidalbaserequest.h" +#include "core/song.h" + +class QNetworkReply; +class TidalService; +class NetworkAccessManager; + +class TidalFavoriteRequest : public TidalBaseRequest { + Q_OBJECT + + public: + TidalFavoriteRequest(TidalService *service, NetworkAccessManager *network, QObject *parent); + ~TidalFavoriteRequest(); + + enum FavoriteType { + FavoriteType_Artists, + FavoriteType_Albums, + FavoriteType_Songs + }; + + bool need_login() { return need_login_; } + + void NeedLogin() { need_login_ = true; } + + signals: + void ArtistsAdded(const SongList &songs); + void AlbumsAdded(const SongList &songs); + void SongsAdded(const SongList &songs); + void ArtistsRemoved(const SongList &songs); + void AlbumsRemoved(const SongList &songs); + void SongsRemoved(const SongList &songs); + + private slots: + void AddArtists(const SongList &songs); + void AddAlbums(const SongList &songs); + void AddSongs(const SongList &songs); + + void RemoveArtists(const SongList &songs); + void RemoveAlbums(const SongList &songs); + void RemoveSongs(const SongList &songs); + + void AddFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs); + void RemoveFavoritesReply(QNetworkReply *reply, const FavoriteType type, const SongList &songs); + + private: + QString FavoriteText(const FavoriteType type); + void AddFavorites(const FavoriteType type, const SongList &songs); + void RemoveFavorites(const FavoriteType type, const SongList songs); + void RemoveFavorites(const FavoriteType type, const int id, const SongList &songs); + + TidalService *service_; + NetworkAccessManager *network_; + QList replies_; + bool need_login_; + +}; + +#endif // TIDALFAVORITEREQUEST_H diff --git a/src/tidal/tidalrequest.cpp b/src/tidal/tidalrequest.cpp index 4d9b0e226..8dd91396e 100644 --- a/src/tidal/tidalrequest.cpp +++ b/src/tidal/tidalrequest.cpp @@ -440,7 +440,7 @@ void TidalRequest::ArtistsFinishCheck(const int limit, const int offset, const i void TidalRequest::AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) { --albums_requests_active_; - AlbumsReceived(reply, 0, limit_requested, offset_requested); + AlbumsReceived(reply, 0, limit_requested, offset_requested, (offset_requested == 0)); } void TidalRequest::AddArtistAlbumsRequest(const int artist_id, const int offset) { @@ -474,14 +474,14 @@ void TidalRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const int art --artist_albums_requests_active_; ++artist_albums_received_; emit UpdateProgress(artist_albums_received_); - AlbumsReceived(reply, artist_id, 0, offset_requested); + AlbumsReceived(reply, artist_id, 0, offset_requested, false); } -void TidalRequest::AlbumsReceived(QNetworkReply *reply, const int artist_id, const int limit_requested, const int offset_requested) { +void TidalRequest::AlbumsReceived(QNetworkReply *reply, const int artist_id, const int limit_requested, const int offset_requested, const bool auto_login) { QString error; - QByteArray data = GetReplyData(reply, error, (type_ == QueryType_Albums|| type_ == QueryType_SearchAlbums)); + QByteArray data = GetReplyData(reply, error, auto_login); if (data.isEmpty()) { AlbumsFinishCheck(artist_id); @@ -667,10 +667,10 @@ void TidalRequest::SongsReplyReceived(QNetworkReply *reply, const int limit_requ --songs_requests_active_; if (type_ == QueryType_SearchSongs && service_->fetchalbums()) { - AlbumsReceived(reply, 0, limit_requested, offset_requested); + AlbumsReceived(reply, 0, limit_requested, offset_requested, (offset_requested == 0)); } else { - SongsReceived(reply, 0, 0, limit_requested, offset_requested); + SongsReceived(reply, 0, 0, limit_requested, offset_requested, (offset_requested == 0)); } } @@ -710,14 +710,14 @@ void TidalRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const int artis if (offset_requested == 0) { emit UpdateProgress(album_songs_received_); } - SongsReceived(reply, artist_id, album_id, 0, offset_requested, album_artist); + SongsReceived(reply, artist_id, album_id, 0, offset_requested, false, album_artist); } -void TidalRequest::SongsReceived(QNetworkReply *reply, const int artist_id, const int album_id, const int limit_requested, const int offset_requested, const QString album_artist) { +void TidalRequest::SongsReceived(QNetworkReply *reply, const int artist_id, const int album_id, const int limit_requested, const int offset_requested, const bool auto_login, const QString album_artist) { QString error; - QByteArray data = GetReplyData(reply, error, false); + QByteArray data = GetReplyData(reply, error, auto_login); if (data.isEmpty()) { SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist); diff --git a/src/tidal/tidalrequest.h b/src/tidal/tidalrequest.h index 6032ba925..7613dbc74 100644 --- a/src/tidal/tidalrequest.h +++ b/src/tidal/tidalrequest.h @@ -78,10 +78,10 @@ class TidalRequest : public TidalBaseRequest { void ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); void AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - void AlbumsReceived(QNetworkReply *reply, const int artist_id, const int limit_requested, const int offset_requested); + void AlbumsReceived(QNetworkReply *reply, const int artist_id, const int limit_requested, const int offset_requested, const bool auto_login); void SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested); - void SongsReceived(QNetworkReply *reply, const int artist_id, const int album_id, const int limit_requested, const int offset_requested, const QString album_artist = QString()); + void SongsReceived(QNetworkReply *reply, const int artist_id, const int album_id, const int limit_requested, const int offset_requested, const bool auto_login = false, const QString album_artist = QString()); void ArtistAlbumsReplyReceived(QNetworkReply *reply, const int artist_id, const int offset_requested); void AlbumSongsReplyReceived(QNetworkReply *reply, const int artist_id, const int album_id, const int offset_requested, const QString album_artist); diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 7d9ebbfb9..cdc51209e 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -52,6 +52,7 @@ #include "tidalservice.h" #include "tidalurlhandler.h" #include "tidalrequest.h" +#include "tidalfavoriterequest.h" #include "tidalstreamurlrequest.h" #include "settings/tidalsettingspage.h" @@ -87,6 +88,7 @@ TidalService::TidalService(Application *app, QObject *parent) songs_collection_sort_model_(new QSortFilterProxyModel(this)), timer_search_delay_(new QTimer(this)), timer_login_attempt_(new QTimer(this)), + favorite_request_(new TidalFavoriteRequest(this, network_, this)), search_delay_(1500), artistssearchlimit_(1), albumssearchlimit_(1), @@ -150,12 +152,36 @@ TidalService::TidalService(Application *app, QObject *parent) connect(this, SIGNAL(Login()), SLOT(SendLogin())); connect(this, SIGNAL(Login(QString, QString, QString)), SLOT(SendLogin(QString, QString, QString))); + connect(this, SIGNAL(AddArtists(const SongList&)), favorite_request_, SLOT(AddArtists(const SongList&))); + connect(this, SIGNAL(AddAlbums(const SongList&)), favorite_request_, SLOT(AddAlbums(const SongList&))); + connect(this, SIGNAL(AddSongs(const SongList&)), favorite_request_, SLOT(AddSongs(const SongList&))); + + connect(this, SIGNAL(RemoveArtists(const SongList&)), favorite_request_, SLOT(RemoveArtists(const SongList&))); + connect(this, SIGNAL(RemoveAlbums(const SongList&)), favorite_request_, SLOT(RemoveAlbums(const SongList&))); + connect(this, SIGNAL(RemoveSongs(const SongList&)), favorite_request_, SLOT(RemoveSongs(const SongList&))); + + connect(favorite_request_, SIGNAL(ArtistsAdded(const SongList&)), artists_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); + connect(favorite_request_, SIGNAL(AlbumsAdded(const SongList&)), albums_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); + connect(favorite_request_, SIGNAL(SongsAdded(const SongList&)), songs_collection_backend_, SLOT(AddOrUpdateSongs(const SongList&))); + + connect(favorite_request_, SIGNAL(ArtistsRemoved(const SongList&)), artists_collection_backend_, SLOT(DeleteSongs(const SongList&))); + connect(favorite_request_, SIGNAL(AlbumsRemoved(const SongList&)), albums_collection_backend_, SLOT(DeleteSongs(const SongList&))); + connect(favorite_request_, SIGNAL(SongsRemoved(const SongList&)), songs_collection_backend_, SLOT(DeleteSongs(const SongList&))); + ReloadSettings(); LoadSessionID(); } -TidalService::~TidalService() {} +TidalService::~TidalService() { + + while (!stream_url_requests_.isEmpty()) { + TidalStreamURLRequest *stream_url_req = stream_url_requests_.takeFirst(); + disconnect(stream_url_req, 0, nullptr, 0); + delete stream_url_req; + } + +} void TidalService::ShowConfig() { app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Tidal); @@ -565,15 +591,27 @@ void TidalService::SendSearch() { void TidalService::GetStreamURL(const QUrl &url) { TidalStreamURLRequest *stream_url_req = new TidalStreamURLRequest(this, network_, url, this); + stream_url_requests_ << stream_url_req; connect(stream_url_req, SIGNAL(TryLogin()), this, SLOT(TryLogin())); - connect(stream_url_req, SIGNAL(StreamURLFinished(QUrl, QUrl, Song::FileType, QString)), this, SIGNAL(StreamURLFinished(QUrl, QUrl, Song::FileType, QString))); + connect(stream_url_req, SIGNAL(StreamURLFinished(QUrl, QUrl, Song::FileType, QString)), this, SLOT(HandleStreamURLFinished(QUrl, QUrl, Song::FileType, QString))); connect(this, SIGNAL(LoginComplete(bool, QString)), stream_url_req, SLOT(LoginComplete(bool, QString))); stream_url_req->Process(); } +void TidalService::HandleStreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType filetype, QString error) { + + TidalStreamURLRequest *stream_url_req = qobject_cast(sender()); + if (!stream_url_req || !stream_url_requests_.contains(stream_url_req)) return; + delete stream_url_req; + stream_url_requests_.removeAll(stream_url_req); + + emit StreamURLFinished(original_url, stream_url, filetype, error); + +} + QString TidalService::LoginError(QString error, QVariant debug) { qLog(Error) << "Tidal:" << error; diff --git a/src/tidal/tidalservice.h b/src/tidal/tidalservice.h index ba2d2a07b..dd2b152b9 100644 --- a/src/tidal/tidalservice.h +++ b/src/tidal/tidalservice.h @@ -43,6 +43,8 @@ class Application; class NetworkAccessManager; class TidalUrlHandler; class TidalRequest; +class TidalFavoriteRequest; +class TidalStreamURLRequest; class CollectionBackend; class CollectionModel; @@ -133,6 +135,7 @@ class TidalService : public InternetService { void AlbumsErrorReceived(QString error); void SongsResultsReceived(SongList songs); void SongsErrorReceived(QString error); + void HandleStreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType filetype, QString error = QString()); private: typedef QPair Param; @@ -178,6 +181,7 @@ class TidalService : public InternetService { std::shared_ptr albums_request_; std::shared_ptr songs_request_; std::shared_ptr search_request_; + TidalFavoriteRequest *favorite_request_; QString token_; QString username_; @@ -205,6 +209,8 @@ class TidalService : public InternetService { bool login_sent_; int login_attempts_; + QList stream_url_requests_; + }; #endif // TIDALSERVICE_H diff --git a/src/tidal/tidalstreamurlrequest.cpp b/src/tidal/tidalstreamurlrequest.cpp index afe996bf5..bb09a203e 100644 --- a/src/tidal/tidalstreamurlrequest.cpp +++ b/src/tidal/tidalstreamurlrequest.cpp @@ -41,7 +41,13 @@ TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAcces need_login_(false) {} TidalStreamURLRequest::~TidalStreamURLRequest() { - Cancel(); + + if (reply_) { + disconnect(reply_, 0, nullptr, 0); + if (reply_->isRunning()) reply_->abort(); + reply_->deleteLater(); + } + } void TidalStreamURLRequest::LoginComplete(bool success, QString error) { @@ -71,12 +77,11 @@ void TidalStreamURLRequest::Process() { void TidalStreamURLRequest::Cancel() { - if (reply_) { - if (reply_->isRunning()) { - reply_->abort(); - } - reply_->deleteLater(); - reply_ = nullptr; + if (reply_ && reply_->isRunning()) { + reply_->abort(); + } + else { + emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, "Cancelled."); } } @@ -89,7 +94,9 @@ void TidalStreamURLRequest::GetStreamURL() { parameters << Param("soundQuality", quality()); if (reply_) { - Cancel(); + disconnect(reply_, 0, nullptr, 0); + if (reply_->isRunning()) reply_->abort(); + reply_->deleteLater(); } reply_ = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id_), parameters); connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived())); @@ -114,16 +121,15 @@ void TidalStreamURLRequest::StreamURLReceived() { emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error); return; } + reply_ = nullptr; QJsonObject json_obj = ExtractJsonObj(data, error); if (json_obj.isEmpty()) { - reply_ = nullptr; emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error); return; } if (!json_obj.contains("url") || !json_obj.contains("codec")) { - reply_ = nullptr; error = Error("Invalid Json reply, stream missing url or codec.", json_obj); emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error); return; @@ -139,7 +145,4 @@ void TidalStreamURLRequest::StreamURLReceived() { emit StreamURLFinished(original_url_, new_url, filetype, QString()); - reply_ = nullptr; - deleteLater(); - } diff --git a/src/tidal/tidalstreamurlrequest.h b/src/tidal/tidalstreamurlrequest.h index feaf53101..d4b6a01ff 100644 --- a/src/tidal/tidalstreamurlrequest.h +++ b/src/tidal/tidalstreamurlrequest.h @@ -50,7 +50,7 @@ class TidalStreamURLRequest : public TidalBaseRequest { signals: void TryLogin(); - void StreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType, QString error = QString()); + void StreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType filetype, QString error = QString()); private slots: void LoginComplete(bool success, QString error = QString());