1 /*
2  * Strawberry Music Player
3  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
4  *
5  * Strawberry is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Strawberry is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include "config.h"
21 
22 #include <QObject>
23 #include <QByteArray>
24 #include <QString>
25 #include <QUrl>
26 #include <QImage>
27 #include <QImageReader>
28 #include <QNetworkRequest>
29 #include <QNetworkReply>
30 #include <QJsonObject>
31 #include <QJsonArray>
32 #include <QJsonValue>
33 #include <QtDebug>
34 
35 #include "core/logging.h"
36 #include "core/networkaccessmanager.h"
37 #include "core/song.h"
38 #include "core/timeconstants.h"
39 #include "core/application.h"
40 #include "core/imageutils.h"
41 #include "covermanager/albumcoverloader.h"
42 #include "tidalservice.h"
43 #include "tidalurlhandler.h"
44 #include "tidalbaserequest.h"
45 #include "tidalrequest.h"
46 
47 const char *TidalRequest::kResourcesUrl = "https://resources.tidal.com";
48 const int TidalRequest::kMaxConcurrentArtistsRequests = 3;
49 const int TidalRequest::kMaxConcurrentAlbumsRequests = 3;
50 const int TidalRequest::kMaxConcurrentSongsRequests = 3;
51 const int TidalRequest::kMaxConcurrentArtistAlbumsRequests = 3;
52 const int TidalRequest::kMaxConcurrentAlbumSongsRequests = 3;
53 const int TidalRequest::kMaxConcurrentAlbumCoverRequests = 1;
54 
TidalRequest(TidalService * service,TidalUrlHandler * url_handler,Application * app,NetworkAccessManager * network,QueryType type,QObject * parent)55 TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QueryType type, QObject *parent)
56     : TidalBaseRequest(service, network, parent),
57       service_(service),
58       url_handler_(url_handler),
59       app_(app),
60       network_(network),
61       type_(type),
62       fetchalbums_(service->fetchalbums()),
63       coversize_(service_->coversize()),
64       query_id_(-1),
65       finished_(false),
66       artists_requests_active_(0),
67       artists_total_(0),
68       artists_received_(0),
69       albums_requests_active_(0),
70       songs_requests_active_(0),
71       artist_albums_requests_active_(0),
72       artist_albums_requested_(0),
73       artist_albums_received_(0),
74       album_songs_requests_active_(0),
75       album_songs_requested_(0),
76       album_songs_received_(0),
77       album_covers_requests_active_(),
78       album_covers_requested_(0),
79       album_covers_received_(0),
80       need_login_(false),
81       no_results_(false) {}
82 
~TidalRequest()83 TidalRequest::~TidalRequest() {
84 
85   while (!replies_.isEmpty()) {
86     QNetworkReply *reply = replies_.takeFirst();
87     QObject::disconnect(reply, nullptr, this, nullptr);
88     if (reply->isRunning()) reply->abort();
89     reply->deleteLater();
90   }
91 
92   while (!album_cover_replies_.isEmpty()) {
93     QNetworkReply *reply = album_cover_replies_.takeFirst();
94     QObject::disconnect(reply, nullptr, this, nullptr);
95     if (reply->isRunning()) reply->abort();
96     reply->deleteLater();
97   }
98 
99 }
100 
LoginComplete(const bool success,const QString & error)101 void TidalRequest::LoginComplete(const bool success, const QString &error) {
102 
103   if (!need_login_) return;
104   need_login_ = false;
105 
106   if (!success) {
107     Error(error);
108     return;
109   }
110 
111   Process();
112 
113 }
114 
Process()115 void TidalRequest::Process() {
116 
117   if (!service_->authenticated()) {
118     emit UpdateStatus(query_id_, tr("Authenticating..."));
119     need_login_ = true;
120     service_->TryLogin();
121     return;
122   }
123 
124   switch (type_) {
125     case QueryType::QueryType_Artists:
126       GetArtists();
127       break;
128     case QueryType::QueryType_Albums:
129       GetAlbums();
130       break;
131     case QueryType::QueryType_Songs:
132       GetSongs();
133       break;
134     case QueryType::QueryType_SearchArtists:
135       ArtistsSearch();
136       break;
137     case QueryType::QueryType_SearchAlbums:
138       AlbumsSearch();
139       break;
140     case QueryType::QueryType_SearchSongs:
141       SongsSearch();
142       break;
143     default:
144       Error("Invalid query type.");
145       break;
146   }
147 
148 }
149 
Search(const int query_id,const QString & search_text)150 void TidalRequest::Search(const int query_id, const QString &search_text) {
151   query_id_ = query_id;
152   search_text_ = search_text;
153 }
154 
GetArtists()155 void TidalRequest::GetArtists() {
156 
157   emit UpdateStatus(query_id_, tr("Retrieving artists..."));
158   emit UpdateProgress(query_id_, 0);
159   AddArtistsRequest();
160 
161 }
162 
AddArtistsRequest(const int offset,const int limit)163 void TidalRequest::AddArtistsRequest(const int offset, const int limit) {
164 
165   Request request;
166   request.limit = limit;
167   request.offset = offset;
168   artists_requests_queue_.enqueue(request);
169   if (artists_requests_active_ < kMaxConcurrentArtistsRequests) FlushArtistsRequests();
170 
171 }
172 
FlushArtistsRequests()173 void TidalRequest::FlushArtistsRequests() {
174 
175   while (!artists_requests_queue_.isEmpty() && artists_requests_active_ < kMaxConcurrentArtistsRequests) {
176 
177     Request request = artists_requests_queue_.dequeue();
178     ++artists_requests_active_;
179 
180     ParamList parameters;
181     if (type_ == QueryType_SearchArtists) parameters << Param("query", search_text_);
182     if (request.limit > 0) parameters << Param("limit", QString::number(request.limit));
183     if (request.offset > 0) parameters << Param("offset", QString::number(request.offset));
184     QNetworkReply *reply(nullptr);
185     if (type_ == QueryType_Artists) {
186       reply = CreateRequest(QString("users/%1/favorites/artists").arg(service_->user_id()), parameters);
187     }
188     if (type_ == QueryType_SearchArtists) {
189       reply = CreateRequest("search/artists", parameters);
190     }
191     if (!reply) continue;
192     replies_ << reply;
193     QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { ArtistsReplyReceived(reply, request.limit, request.offset); });
194 
195   }
196 
197 }
198 
GetAlbums()199 void TidalRequest::GetAlbums() {
200 
201   emit UpdateStatus(query_id_, tr("Retrieving albums..."));
202   emit UpdateProgress(query_id_, 0);
203   AddAlbumsRequest();
204 
205 }
206 
AddAlbumsRequest(const int offset,const int limit)207 void TidalRequest::AddAlbumsRequest(const int offset, const int limit) {
208 
209   Request request;
210   request.limit = limit;
211   request.offset = offset;
212   albums_requests_queue_.enqueue(request);
213   if (albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests();
214 
215 }
216 
FlushAlbumsRequests()217 void TidalRequest::FlushAlbumsRequests() {
218 
219   while (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) {
220 
221     Request request = albums_requests_queue_.dequeue();
222     ++albums_requests_active_;
223 
224     ParamList parameters;
225     if (type_ == QueryType_SearchAlbums) parameters << Param("query", search_text_);
226     if (request.limit > 0) parameters << Param("limit", QString::number(request.limit));
227     if (request.offset > 0) parameters << Param("offset", QString::number(request.offset));
228     QNetworkReply *reply(nullptr);
229     if (type_ == QueryType_Albums) {
230       reply = CreateRequest(QString("users/%1/favorites/albums").arg(service_->user_id()), parameters);
231     }
232     if (type_ == QueryType_SearchAlbums) {
233       reply = CreateRequest("search/albums", parameters);
234     }
235     if (!reply) continue;
236     replies_ << reply;
237     QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { AlbumsReplyReceived(reply, request.limit, request.offset); });
238 
239   }
240 
241 }
242 
GetSongs()243 void TidalRequest::GetSongs() {
244 
245   emit UpdateStatus(query_id_, tr("Retrieving songs..."));
246   emit UpdateProgress(query_id_, 0);
247   AddSongsRequest();
248 
249 }
250 
AddSongsRequest(const int offset,const int limit)251 void TidalRequest::AddSongsRequest(const int offset, const int limit) {
252 
253   Request request;
254   request.limit = limit;
255   request.offset = offset;
256   songs_requests_queue_.enqueue(request);
257   if (songs_requests_active_ < kMaxConcurrentSongsRequests) FlushSongsRequests();
258 
259 }
260 
FlushSongsRequests()261 void TidalRequest::FlushSongsRequests() {
262 
263   while (!songs_requests_queue_.isEmpty() && songs_requests_active_ < kMaxConcurrentSongsRequests) {
264 
265     Request request = songs_requests_queue_.dequeue();
266     ++songs_requests_active_;
267 
268     ParamList parameters;
269     if (type_ == QueryType_SearchSongs) parameters << Param("query", search_text_);
270     if (request.limit > 0) parameters << Param("limit", QString::number(request.limit));
271     if (request.offset > 0) parameters << Param("offset", QString::number(request.offset));
272     QNetworkReply *reply(nullptr);
273     if (type_ == QueryType_Songs) {
274       reply = CreateRequest(QString("users/%1/favorites/tracks").arg(service_->user_id()), parameters);
275     }
276     if (type_ == QueryType_SearchSongs) {
277       reply = CreateRequest("search/tracks", parameters);
278     }
279     if (!reply) continue;
280     replies_ << reply;
281     QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { SongsReplyReceived(reply, request.limit, request.offset); });
282 
283   }
284 
285 }
286 
ArtistsSearch()287 void TidalRequest::ArtistsSearch() {
288 
289   emit UpdateStatus(query_id_, tr("Searching..."));
290   emit UpdateProgress(query_id_, 0);
291   AddArtistsSearchRequest();
292 
293 }
294 
AddArtistsSearchRequest(const int offset)295 void TidalRequest::AddArtistsSearchRequest(const int offset) {
296 
297   AddArtistsRequest(offset, service_->artistssearchlimit());
298 
299 }
300 
AlbumsSearch()301 void TidalRequest::AlbumsSearch() {
302 
303   emit UpdateStatus(query_id_, tr("Searching..."));
304   emit UpdateProgress(query_id_, 0);
305   AddAlbumsSearchRequest();
306 
307 }
308 
AddAlbumsSearchRequest(const int offset)309 void TidalRequest::AddAlbumsSearchRequest(const int offset) {
310 
311   AddAlbumsRequest(offset, service_->albumssearchlimit());
312 
313 }
314 
SongsSearch()315 void TidalRequest::SongsSearch() {
316 
317   emit UpdateStatus(query_id_, tr("Searching..."));
318   emit UpdateProgress(query_id_, 0);
319   AddSongsSearchRequest();
320 
321 }
322 
AddSongsSearchRequest(const int offset)323 void TidalRequest::AddSongsSearchRequest(const int offset) {
324 
325   AddSongsRequest(offset, service_->songssearchlimit());
326 
327 }
328 
ArtistsReplyReceived(QNetworkReply * reply,const int limit_requested,const int offset_requested)329 void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) {
330 
331   if (!replies_.contains(reply)) return;
332   replies_.removeAll(reply);
333   QObject::disconnect(reply, nullptr, this, nullptr);
334   reply->deleteLater();
335 
336   QByteArray data = GetReplyData(reply, (offset_requested == 0));
337 
338   --artists_requests_active_;
339 
340   if (finished_) return;
341 
342   if (data.isEmpty()) {
343     ArtistsFinishCheck();
344     return;
345   }
346 
347   QJsonObject json_obj = ExtractJsonObj(data);
348   if (json_obj.isEmpty()) {
349     ArtistsFinishCheck();
350     return;
351   }
352 
353   if (!json_obj.contains("limit") ||
354       !json_obj.contains("offset") ||
355       !json_obj.contains("totalNumberOfItems") ||
356       !json_obj.contains("items")) {
357     ArtistsFinishCheck();
358     Error("Json object missing values.", json_obj);
359     return;
360   }
361   //int limit = json_obj["limit"].toInt();
362   int offset = json_obj["offset"].toInt();
363   int artists_total = json_obj["totalNumberOfItems"].toInt();
364 
365   if (offset_requested == 0) {
366     artists_total_ = artists_total;
367   }
368   else if (artists_total != artists_total_) {
369     Error(QString("totalNumberOfItems returned does not match previous totalNumberOfItems! %1 != %2").arg(artists_total).arg(artists_total_));
370     ArtistsFinishCheck();
371     return;
372   }
373 
374   if (offset != offset_requested) {
375     Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
376     ArtistsFinishCheck();
377     return;
378   }
379 
380   if (offset_requested == 0) {
381     emit ProgressSetMaximum(query_id_, artists_total_);
382     emit UpdateProgress(query_id_, artists_received_);
383   }
384 
385   QJsonValue value_items = ExtractItems(json_obj);
386   if (!value_items.isArray()) {
387     ArtistsFinishCheck();
388     return;
389   }
390 
391   QJsonArray array_items = value_items.toArray();
392   if (array_items.isEmpty()) {  // Empty array means no results
393     if (offset_requested == 0) no_results_ = true;
394     ArtistsFinishCheck();
395     return;
396   }
397 
398   int artists_received = 0;
399   for (const QJsonValueRef value_item : array_items) {
400 
401     ++artists_received;
402 
403     if (!value_item.isObject()) {
404       Error("Invalid Json reply, item in array is not a object.");
405       continue;
406     }
407     QJsonObject obj_item = value_item.toObject();
408 
409     if (obj_item.contains("item")) {
410       QJsonValue json_item = obj_item["item"];
411       if (!json_item.isObject()) {
412         Error("Invalid Json reply, item in array is not a object.", json_item);
413         continue;
414       }
415       obj_item = json_item.toObject();
416     }
417 
418     if (!obj_item.contains("id") || !obj_item.contains("name")) {
419       Error("Invalid Json reply, item missing id or album.", obj_item);
420       continue;
421     }
422 
423     QString artist_id;
424     if (obj_item["id"].isString()) {
425       artist_id = obj_item["id"].toString();
426     }
427     else {
428       artist_id = QString::number(obj_item["id"].toInt());
429     }
430     if (artist_albums_requests_pending_.contains(artist_id)) continue;
431     artist_albums_requests_pending_.append(artist_id);
432 
433   }
434   artists_received_ += artists_received;
435 
436   if (offset_requested != 0) emit UpdateProgress(query_id_, artists_received_);
437 
438   ArtistsFinishCheck(limit_requested, offset, artists_received);
439 
440 }
441 
ArtistsFinishCheck(const int limit,const int offset,const int artists_received)442 void TidalRequest::ArtistsFinishCheck(const int limit, const int offset, const int artists_received) {
443 
444   if (finished_) return;
445 
446   if ((limit == 0 || limit > artists_received) && artists_received_ < artists_total_) {
447     int offset_next = offset + artists_received;
448     if (offset_next > 0 && offset_next < artists_total_) {
449       if (type_ == QueryType_Artists) AddArtistsRequest(offset_next);
450       else if (type_ == QueryType_SearchArtists) AddArtistsSearchRequest(offset_next);
451     }
452   }
453 
454   if (!artists_requests_queue_.isEmpty() && artists_requests_active_ < kMaxConcurrentArtistsRequests) FlushArtistsRequests();
455 
456   if (artists_requests_queue_.isEmpty() && artists_requests_active_ <= 0) {  // Artist query is finished, get all albums for all artists.
457 
458     // Get artist albums
459     for (const QString &artist_id : artist_albums_requests_pending_) {
460       AddArtistAlbumsRequest(artist_id);
461       ++artist_albums_requested_;
462     }
463     artist_albums_requests_pending_.clear();
464 
465     if (artist_albums_requested_ > 0) {
466       if (artist_albums_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving albums for %1 artist...").arg(artist_albums_requested_));
467       else emit UpdateStatus(query_id_, tr("Retrieving albums for %1 artists...").arg(artist_albums_requested_));
468       emit ProgressSetMaximum(query_id_, artist_albums_requested_);
469       emit UpdateProgress(query_id_, 0);
470     }
471 
472   }
473 
474   FinishCheck();
475 
476 }
477 
AlbumsReplyReceived(QNetworkReply * reply,const int limit_requested,const int offset_requested)478 void TidalRequest::AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) {
479   --albums_requests_active_;
480   AlbumsReceived(reply, QString(), limit_requested, offset_requested, (offset_requested == 0));
481   if (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests();
482 }
483 
AddArtistAlbumsRequest(const QString & artist_id,const int offset)484 void TidalRequest::AddArtistAlbumsRequest(const QString &artist_id, const int offset) {
485 
486   Request request;
487   request.artist_id = artist_id;
488   request.offset = offset;
489   artist_albums_requests_queue_.enqueue(request);
490   if (artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests();
491 
492 }
493 
FlushArtistAlbumsRequests()494 void TidalRequest::FlushArtistAlbumsRequests() {
495 
496   while (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) {
497 
498     Request request = artist_albums_requests_queue_.dequeue();
499     ++artist_albums_requests_active_;
500 
501     ParamList parameters;
502     if (request.offset > 0) parameters << Param("offset", QString::number(request.offset));
503     QNetworkReply *reply = CreateRequest(QString("artists/%1/albums").arg(request.artist_id), parameters);
504     QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { ArtistAlbumsReplyReceived(reply, request.artist_id, request.offset); });
505     replies_ << reply;
506 
507   }
508 
509 }
510 
ArtistAlbumsReplyReceived(QNetworkReply * reply,const QString & artist_id,const int offset_requested)511 void TidalRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const QString &artist_id, const int offset_requested) {
512 
513   --artist_albums_requests_active_;
514   ++artist_albums_received_;
515   emit UpdateProgress(query_id_, artist_albums_received_);
516   AlbumsReceived(reply, artist_id, 0, offset_requested, false);
517   if (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests();
518 
519 }
520 
AlbumsReceived(QNetworkReply * reply,const QString & artist_id_requested,const int limit_requested,const int offset_requested,const bool auto_login)521 void TidalRequest::AlbumsReceived(QNetworkReply *reply, const QString &artist_id_requested, const int limit_requested, const int offset_requested, const bool auto_login) {
522 
523   if (!replies_.contains(reply)) return;
524   replies_.removeAll(reply);
525   QObject::disconnect(reply, nullptr, this, nullptr);
526   reply->deleteLater();
527 
528   QByteArray data = GetReplyData(reply, auto_login);
529 
530   if (finished_) return;
531 
532   if (data.isEmpty()) {
533     AlbumsFinishCheck(artist_id_requested);
534     return;
535   }
536 
537   QJsonObject json_obj = ExtractJsonObj(data);
538   if (json_obj.isEmpty()) {
539     AlbumsFinishCheck(artist_id_requested);
540     return;
541   }
542 
543   if (!json_obj.contains("limit") ||
544       !json_obj.contains("offset") ||
545       !json_obj.contains("totalNumberOfItems") ||
546       !json_obj.contains("items")) {
547     Error("Json object missing values.", json_obj);
548     AlbumsFinishCheck(artist_id_requested);
549     return;
550   }
551 
552   //int limit = json_obj["limit"].toInt();
553   int offset = json_obj["offset"].toInt();
554   int albums_total = json_obj["totalNumberOfItems"].toInt();
555 
556   if (offset != offset_requested) {
557     Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
558     AlbumsFinishCheck(artist_id_requested);
559     return;
560   }
561 
562   QJsonValue value_items = ExtractItems(json_obj);
563   if (!value_items.isArray()) {
564     AlbumsFinishCheck(artist_id_requested);
565     return;
566   }
567   QJsonArray array_items = value_items.toArray();
568   if (array_items.isEmpty()) {
569     if ((type_ == QueryType_Albums || type_ == QueryType_SearchAlbums || (type_ == QueryType_SearchSongs && fetchalbums_)) && offset_requested == 0) {
570       no_results_ = true;
571     }
572     AlbumsFinishCheck(artist_id_requested);
573     return;
574   }
575 
576   int albums_received = 0;
577   for (const QJsonValueRef value_item : array_items) {
578 
579     ++albums_received;
580 
581     if (!value_item.isObject()) {
582       Error("Invalid Json reply, item in array is not a object.");
583       continue;
584     }
585     QJsonObject obj_item = value_item.toObject();
586 
587     if (obj_item.contains("item")) {
588       QJsonValue json_item = obj_item["item"];
589       if (!json_item.isObject()) {
590         Error("Invalid Json reply, item in array is not a object.", json_item);
591         continue;
592       }
593       obj_item = json_item.toObject();
594     }
595 
596     QString album_id;
597     QString album;
598     bool album_explicit = false;
599     if (obj_item.contains("type")) {  // This was a albums request or search
600       if (!obj_item.contains("id") || !obj_item.contains("title")) {
601         Error("Invalid Json reply, item is missing ID or title.", obj_item);
602         continue;
603       }
604       if (obj_item["id"].isString()) {
605         album_id = obj_item["id"].toString();
606       }
607       else {
608         album_id = QString::number(obj_item["id"].toInt());
609       }
610       album = obj_item["title"].toString();
611       if (service_->album_explicit() && obj_item.contains("explicit")) {
612         album_explicit = obj_item["explicit"].toVariant().toBool();
613         if (album_explicit && !album.isEmpty()) {
614           album.append(" (Explicit)");
615         }
616       }
617     }
618     else if (obj_item.contains("album")) {  // This was a tracks request or search
619       QJsonValue value_album = obj_item["album"];
620       if (!value_album.isObject()) {
621         Error("Invalid Json reply, item album is not a object.", value_album);
622         continue;
623       }
624       QJsonObject obj_album = value_album.toObject();
625       if (!obj_album.contains("id") || !obj_album.contains("title")) {
626         Error("Invalid Json reply, item album is missing ID or title.", obj_album);
627         continue;
628       }
629       if (obj_album["id"].isString()) {
630         album_id = obj_album["id"].toString();
631       }
632       else {
633         album_id = QString::number(obj_album["id"].toInt());
634       }
635       album = obj_album["title"].toString();
636       if (service_->album_explicit() && obj_album.contains("explicit")) {
637         album_explicit = obj_album["explicit"].toVariant().toBool();
638         if (album_explicit && !album.isEmpty()) {
639           album.append(" (Explicit)");
640         }
641       }
642     }
643     else {
644       Error("Invalid Json reply, item missing type or album.", obj_item);
645       continue;
646     }
647 
648     if (album_songs_requests_pending_.contains(album_id)) continue;
649 
650     if (!obj_item.contains("artist") || !obj_item.contains("title") || !obj_item.contains("audioQuality")) {
651       Error("Invalid Json reply, item missing artist, title or audioQuality.", obj_item);
652       continue;
653     }
654     QJsonValue value_artist = obj_item["artist"];
655     if (!value_artist.isObject()) {
656       Error("Invalid Json reply, item artist is not a object.", value_artist);
657       continue;
658     }
659     QJsonObject obj_artist = value_artist.toObject();
660     if (!obj_artist.contains("id") || !obj_artist.contains("name")) {
661       Error("Invalid Json reply, item artist missing id or name.", obj_artist);
662       continue;
663     }
664 
665     QString artist_id;
666     if (obj_artist["id"].isString()) {
667       artist_id = obj_artist["id"].toString();
668     }
669     else {
670       artist_id = QString::number(obj_artist["id"].toInt());
671     }
672     QString artist = obj_artist["name"].toString();
673 
674     //QString quality = obj_item["audioQuality"].toString();
675     //QString copyright = obj_item["copyright"].toString();
676 
677     //qLog(Debug) << "Tidal:" << artist << album << quality << copyright;
678 
679     Request request;
680     if (artist_id_requested.isEmpty()) {
681       request.artist_id = artist_id;
682     }
683     else {
684       request.artist_id = artist_id_requested;
685     }
686     request.album_id = album_id;
687     request.album_artist = artist;
688     request.album = album;
689     request.album_explicit = album_explicit;
690     album_songs_requests_pending_.insert(album_id, request);
691 
692   }
693 
694   AlbumsFinishCheck(artist_id_requested, limit_requested, offset, albums_total, albums_received);
695 
696 }
697 
AlbumsFinishCheck(const QString & artist_id,const int limit,const int offset,const int albums_total,const int albums_received)698 void TidalRequest::AlbumsFinishCheck(const QString &artist_id, const int limit, const int offset, const int albums_total, const int albums_received) {
699 
700   if (finished_) return;
701 
702   if (limit == 0 || limit > albums_received) {
703     int offset_next = offset + albums_received;
704     if (offset_next > 0 && offset_next < albums_total) {
705       switch (type_) {
706         case QueryType_Albums:
707           AddAlbumsRequest(offset_next);
708           break;
709         case QueryType_SearchAlbums:
710           AddAlbumsSearchRequest(offset_next);
711           break;
712         case QueryType_Artists:
713         case QueryType_SearchArtists:
714           AddArtistAlbumsRequest(artist_id, offset_next);
715           break;
716         default:
717           break;
718       }
719     }
720   }
721 
722   if (
723       albums_requests_queue_.isEmpty() &&
724       albums_requests_active_ <= 0 &&
725       artist_albums_requests_queue_.isEmpty() &&
726       artist_albums_requests_active_ <= 0
727       ) { // Artist albums query is finished, get all songs for all albums.
728 
729     // Get songs for all the albums.
730 
731     for (QHash<QString, Request> ::iterator it = album_songs_requests_pending_.begin(); it != album_songs_requests_pending_.end(); ++it) {
732       Request request = it.value();
733       AddAlbumSongsRequest(request.artist_id, request.album_id, request.album_artist, request.album, request.album_explicit);
734     }
735     album_songs_requests_pending_.clear();
736 
737     if (album_songs_requested_ > 0) {
738       if (album_songs_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving songs for %1 album...").arg(album_songs_requested_));
739       else emit UpdateStatus(query_id_, tr("Retrieving songs for %1 albums...").arg(album_songs_requested_));
740       emit ProgressSetMaximum(query_id_, album_songs_requested_);
741       emit UpdateProgress(query_id_, 0);
742     }
743   }
744 
745   FinishCheck();
746 
747 }
748 
SongsReplyReceived(QNetworkReply * reply,const int limit_requested,const int offset_requested)749 void TidalRequest::SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) {
750 
751   --songs_requests_active_;
752   if (type_ == QueryType_SearchSongs && fetchalbums_) {
753     AlbumsReceived(reply, QString(), limit_requested, offset_requested, (offset_requested == 0));
754   }
755   else {
756     SongsReceived(reply, QString(), QString(), limit_requested, offset_requested, (offset_requested == 0));
757   }
758 
759 }
760 
AddAlbumSongsRequest(const QString & artist_id,const QString & album_id,const QString & album_artist,const QString & album,const bool album_explicit,const int offset)761 void TidalRequest::AddAlbumSongsRequest(const QString &artist_id, const QString &album_id, const QString &album_artist, const QString &album, const bool album_explicit, const int offset) {
762 
763   Request request;
764   request.artist_id = artist_id;
765   request.album_id = album_id;
766   request.album_artist = album_artist;
767   request.album = album;
768   request.album_explicit = album_explicit;
769   request.offset = offset;
770   album_songs_requests_queue_.enqueue(request);
771   ++album_songs_requested_;
772   if (album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests();
773 
774 }
775 
FlushAlbumSongsRequests()776 void TidalRequest::FlushAlbumSongsRequests() {
777 
778   while (!album_songs_requests_queue_.isEmpty() && album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) {
779 
780     Request request = album_songs_requests_queue_.dequeue();
781     ++album_songs_requests_active_;
782     ParamList parameters;
783     if (request.offset > 0) parameters << Param("offset", QString::number(request.offset));
784     QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(request.album_id), parameters);
785     replies_ << reply;
786     QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { AlbumSongsReplyReceived(reply, request.artist_id, request.album_id, request.offset, request.album_artist, request.album, request.album_explicit); });
787 
788   }
789 
790 }
791 
AlbumSongsReplyReceived(QNetworkReply * reply,const QString & artist_id,const QString & album_id,const int offset_requested,const QString & album_artist,const QString & album,const bool album_explicit)792 void TidalRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const QString &artist_id, const QString &album_id, const int offset_requested, const QString &album_artist, const QString &album, const bool album_explicit) {
793 
794   --album_songs_requests_active_;
795   ++album_songs_received_;
796   if (offset_requested == 0) {
797     emit UpdateProgress(query_id_, album_songs_received_);
798   }
799   SongsReceived(reply, artist_id, album_id, 0, offset_requested, false, album_artist, album, album_explicit);
800 
801 }
802 
SongsReceived(QNetworkReply * reply,const QString & artist_id,const QString & album_id,const int limit_requested,const int offset_requested,const bool auto_login,const QString & album_artist,const QString & album,const bool album_explicit)803 void TidalRequest::SongsReceived(QNetworkReply *reply, const QString &artist_id, const QString &album_id, const int limit_requested, const int offset_requested, const bool auto_login, const QString &album_artist, const QString &album, const bool album_explicit) {
804 
805   if (!replies_.contains(reply)) return;
806   replies_.removeAll(reply);
807   QObject::disconnect(reply, nullptr, this, nullptr);
808   reply->deleteLater();
809 
810   QByteArray data = GetReplyData(reply, auto_login);
811 
812   if (finished_) return;
813 
814   if (data.isEmpty()) {
815     SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist, album, album_explicit);
816     return;
817   }
818 
819   QJsonObject json_obj = ExtractJsonObj(data);
820   if (json_obj.isEmpty()) {
821     SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist, album, album_explicit);
822     return;
823   }
824 
825   if (!json_obj.contains("limit") ||
826       !json_obj.contains("offset") ||
827       !json_obj.contains("totalNumberOfItems") ||
828       !json_obj.contains("items")) {
829     Error("Json object missing values.", json_obj);
830     SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist, album, album_explicit);
831     return;
832   }
833 
834   //int limit = json_obj["limit"].toInt();
835   int offset = json_obj["offset"].toInt();
836   int songs_total = json_obj["totalNumberOfItems"].toInt();
837 
838   if (offset != offset_requested) {
839     Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
840     SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist, album, album_explicit);
841     return;
842   }
843 
844   QJsonValue json_value = ExtractItems(json_obj);
845   if (!json_value.isArray()) {
846     SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist, album, album_explicit);
847     return;
848   }
849 
850   QJsonArray json_items = json_value.toArray();
851   if (json_items.isEmpty()) {
852     if ((type_ == QueryType_Songs || type_ == QueryType_SearchSongs) && offset_requested == 0) {
853       no_results_ = true;
854     }
855     SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, 0, album_artist, album, album_explicit);
856     return;
857   }
858 
859   bool compilation = false;
860   bool multidisc = false;
861   SongList songs;
862   int songs_received = 0;
863   for (const QJsonValueRef value_item : json_items) {
864 
865     if (!value_item.isObject()) {
866       Error("Invalid Json reply, track is not a object.");
867       continue;
868     }
869     QJsonObject obj_item = value_item.toObject();
870 
871     if (obj_item.contains("item")) {
872       QJsonValue item = obj_item["item"];
873       if (!item.isObject()) {
874         Error("Invalid Json reply, item is not a object.", item);
875         continue;
876       }
877       obj_item = item.toObject();
878     }
879 
880     ++songs_received;
881     Song song(Song::Source_Tidal);
882     ParseSong(song, obj_item, artist_id, album_id, album_artist, album, album_explicit);
883     if (!song.is_valid()) continue;
884     if (song.disc() >= 2) multidisc = true;
885     if (song.is_compilation()) compilation = true;
886     songs << song;
887   }
888 
889   for (Song song : songs) {
890     if (compilation) song.set_compilation_detected(true);
891     if (!multidisc) song.set_disc(0);
892     songs_.insert(song.song_id(), song);
893   }
894 
895   SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, songs_total, songs_received, album_artist, album, album_explicit);
896 
897 }
898 
SongsFinishCheck(const QString & artist_id,const QString & album_id,const int limit,const int offset,const int songs_total,const int songs_received,const QString & album_artist,const QString & album,const bool album_explicit)899 void TidalRequest::SongsFinishCheck(const QString &artist_id, const QString &album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist, const QString &album, const bool album_explicit) {
900 
901   if (finished_) return;
902 
903   if (limit == 0 || limit > songs_received) {
904     int offset_next = offset + songs_received;
905     if (offset_next > 0 && offset_next < songs_total) {
906       switch (type_) {
907         case QueryType_Songs:
908           AddSongsRequest(offset_next);
909           break;
910         case QueryType_SearchSongs:
911           // If artist_id and album_id isn't zero it means that it's a songs search where we fetch all albums too. So fallthrough.
912           if (artist_id.isEmpty() && album_id.isEmpty()) {
913             AddSongsSearchRequest(offset_next);
914             break;
915           }
916           // fallthrough
917         case QueryType_Artists:
918         case QueryType_SearchArtists:
919         case QueryType_Albums:
920         case QueryType_SearchAlbums:
921           AddAlbumSongsRequest(artist_id, album_id, album_artist, album, album_explicit, offset_next);
922           break;
923         default:
924           break;
925       }
926     }
927   }
928 
929   if (!songs_requests_queue_.isEmpty() && songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests();
930   if (!album_songs_requests_queue_.isEmpty() && album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) FlushAlbumSongsRequests();
931 
932   if (
933       service_->download_album_covers() &&
934       IsQuery() &&
935       songs_requests_queue_.isEmpty() &&
936       songs_requests_active_ <= 0 &&
937       album_songs_requests_queue_.isEmpty() &&
938       album_songs_requests_active_ <= 0 &&
939       album_cover_requests_queue_.isEmpty() &&
940       album_covers_received_ <= 0 &&
941       album_covers_requests_sent_.isEmpty() &&
942       album_songs_received_ >= album_songs_requested_
943   ) {
944     GetAlbumCovers();
945   }
946 
947   FinishCheck();
948 
949 }
950 
ParseSong(Song & song,const QJsonObject & json_obj,const QString & artist_id_requested,const QString & album_id_requested,const QString & album_artist,const QString &,const bool album_explicit)951 QString TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const QString &artist_id_requested, const QString &album_id_requested, const QString &album_artist, const QString&, const bool album_explicit) {
952 
953   Q_UNUSED(artist_id_requested);
954 
955   if (
956       !json_obj.contains("album") ||
957       !json_obj.contains("allowStreaming") ||
958       !json_obj.contains("artist") ||
959       !json_obj.contains("artists") ||
960       !json_obj.contains("audioQuality") ||
961       !json_obj.contains("duration") ||
962       !json_obj.contains("id") ||
963       !json_obj.contains("streamReady") ||
964       !json_obj.contains("title") ||
965       !json_obj.contains("trackNumber") ||
966       !json_obj.contains("url") ||
967       !json_obj.contains("volumeNumber") ||
968       !json_obj.contains("copyright")
969     ) {
970     Error("Invalid Json reply, track is missing one or more values.", json_obj);
971     return QString();
972   }
973 
974   QJsonValue value_artist = json_obj["artist"];
975   QJsonValue value_album = json_obj["album"];
976   QJsonValue json_duration = json_obj["duration"];
977   //QJsonArray array_artists = json_obj["artists"].toArray();
978 
979   QString song_id;
980   if (json_obj["id"].isString()) {
981     song_id = json_obj["id"].toString();
982   }
983   else {
984     song_id = QString::number(json_obj["id"].toInt());
985   }
986 
987   QString title = json_obj["title"].toString();
988   //QString urlstr = json_obj["url"].toString();
989   int track = json_obj["trackNumber"].toInt();
990   int disc = json_obj["volumeNumber"].toInt();
991   bool allow_streaming = json_obj["allowStreaming"].toBool();
992   bool stream_ready = json_obj["streamReady"].toBool();
993   QString copyright = json_obj["copyright"].toString();
994 
995   if (!value_artist.isObject()) {
996     Error("Invalid Json reply, track artist is not a object.", value_artist);
997     return QString();
998   }
999   QJsonObject obj_artist = value_artist.toObject();
1000   if (!obj_artist.contains("id") || !obj_artist.contains("name")) {
1001     Error("Invalid Json reply, track artist is missing id or name.", obj_artist);
1002     return QString();
1003   }
1004   QString artist_id;
1005   if (obj_artist["id"].isString()) {
1006     artist_id = obj_artist["id"].toString();
1007   }
1008   else {
1009     artist_id = QString::number(obj_artist["id"].toInt());
1010   }
1011   QString artist = obj_artist["name"].toString();
1012 
1013   if (!value_album.isObject()) {
1014     Error("Invalid Json reply, track album is not a object.", value_album);
1015     return QString();
1016   }
1017   QJsonObject obj_album = value_album.toObject();
1018   if (!obj_album.contains("id") || !obj_album.contains("title") || !obj_album.contains("cover")) {
1019     Error("Invalid Json reply, track album is missing id, title or cover.", obj_album);
1020     return QString();
1021   }
1022   QString album_id;
1023   if (obj_album["id"].isString()) {
1024     album_id = obj_album["id"].toString();
1025   }
1026   else {
1027     album_id = QString::number(obj_album["id"].toInt());
1028   }
1029   if (!album_id_requested.isEmpty() && album_id_requested != album_id) {
1030     Error("Invalid Json reply, track album id is wrong.", obj_album);
1031     return QString();
1032   }
1033   QString album = obj_album["title"].toString();
1034   if (album_explicit) album.append(" (Explicit)");
1035   QString cover = obj_album["cover"].toString();
1036 
1037   if (!allow_streaming) {
1038     Warn(QString("Song %1 %2 %3 is not allowStreaming").arg(artist, album, title));
1039     return QString();
1040   }
1041 
1042   if (!stream_ready) {
1043     Warn(QString("Song %1 %2 %3 is not streamReady").arg(artist, album, title));
1044     return QString();
1045   }
1046 
1047   QUrl url;
1048   url.setScheme(url_handler_->scheme());
1049   url.setPath(song_id);
1050 
1051   QVariant q_duration = json_duration.toVariant();
1052   quint64 duration = 0;
1053   if (q_duration.isValid()) {
1054     duration = q_duration.toLongLong() * kNsecPerSec;
1055   }
1056   else {
1057     Error("Invalid duration for song.", json_duration);
1058     return QString();
1059   }
1060 
1061   cover = cover.replace("-", "/");
1062   QUrl cover_url(QString("%1/images/%2/%3.jpg").arg(kResourcesUrl, cover, coversize_));
1063 
1064   title.remove(Song::kTitleRemoveMisc);
1065 
1066   //qLog(Debug) << "id" << song_id << "track" << track << "disc" << disc << "title" << title << "album" << album << "album artist" << album_artist << "artist" << artist << cover << allow_streaming << url;
1067 
1068   song.set_source(Song::Source_Tidal);
1069   song.set_song_id(song_id);
1070   song.set_album_id(album_id);
1071   song.set_artist_id(artist_id);
1072   if (album_artist != artist) song.set_albumartist(album_artist);
1073   song.set_album(album);
1074   song.set_artist(artist);
1075   song.set_title(title);
1076   song.set_track(track);
1077   song.set_disc(disc);
1078   song.set_url(url);
1079   song.set_length_nanosec(duration);
1080   song.set_art_automatic(cover_url);
1081   song.set_comment(copyright);
1082   song.set_directory_id(0);
1083   song.set_filetype(Song::FileType_Stream);
1084   song.set_filesize(0);
1085   song.set_mtime(0);
1086   song.set_ctime(0);
1087   song.set_valid(true);
1088 
1089   return song_id;
1090 
1091 }
1092 
GetAlbumCovers()1093 void TidalRequest::GetAlbumCovers() {
1094 
1095   const SongList songs = songs_.values();
1096   for (const Song &song : songs) {
1097     AddAlbumCoverRequest(song);
1098   }
1099   FlushAlbumCoverRequests();
1100 
1101   if (album_covers_requested_ == 1) emit UpdateStatus(query_id_, tr("Retrieving album cover for %1 album...").arg(album_covers_requested_));
1102   else emit UpdateStatus(query_id_, tr("Retrieving album covers for %1 albums...").arg(album_covers_requested_));
1103   emit ProgressSetMaximum(query_id_, album_covers_requested_);
1104   emit UpdateProgress(query_id_, 0);
1105 
1106 }
1107 
AddAlbumCoverRequest(const Song & song)1108 void TidalRequest::AddAlbumCoverRequest(const Song &song) {
1109 
1110   if (album_covers_requests_sent_.contains(song.album_id())) {
1111     album_covers_requests_sent_.insert(song.album_id(), song.song_id());
1112     return;
1113   }
1114 
1115   AlbumCoverRequest request;
1116   request.album_id = song.album_id();
1117   request.url = song.art_automatic();
1118   request.filename = app_->album_cover_loader()->CoverFilePath(song.source(), song.effective_albumartist(), song.effective_album(), song.album_id(), QString(), request.url);
1119   if (request.filename.isEmpty()) return;
1120 
1121   album_covers_requests_sent_.insert(song.album_id(), song.song_id());
1122   ++album_covers_requested_;
1123 
1124   album_cover_requests_queue_.enqueue(request);
1125 
1126 }
1127 
FlushAlbumCoverRequests()1128 void TidalRequest::FlushAlbumCoverRequests() {
1129 
1130   while (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) {
1131 
1132     AlbumCoverRequest request = album_cover_requests_queue_.dequeue();
1133     ++album_covers_requests_active_;
1134 
1135     QNetworkRequest req(request.url);
1136 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
1137     req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
1138 #else
1139     req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
1140 #endif
1141     QNetworkReply *reply = network_->get(req);
1142     album_cover_replies_ << reply;
1143     QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { AlbumCoverReceived(reply, request.album_id, request.url, request.filename); });
1144 
1145   }
1146 
1147 }
1148 
AlbumCoverReceived(QNetworkReply * reply,const QString & album_id,const QUrl & url,const QString & filename)1149 void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url, const QString &filename) {
1150 
1151   if (album_cover_replies_.contains(reply)) {
1152     album_cover_replies_.removeAll(reply);
1153     QObject::disconnect(reply, nullptr, this, nullptr);
1154     reply->deleteLater();
1155   }
1156   else {
1157     AlbumCoverFinishCheck();
1158     return;
1159   }
1160 
1161   --album_covers_requests_active_;
1162   ++album_covers_received_;
1163 
1164   if (finished_) return;
1165 
1166   emit UpdateProgress(query_id_, album_covers_received_);
1167 
1168   if (!album_covers_requests_sent_.contains(album_id)) {
1169     AlbumCoverFinishCheck();
1170     return;
1171   }
1172 
1173   if (reply->error() != QNetworkReply::NoError) {
1174     Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
1175     album_covers_requests_sent_.remove(album_id);
1176     AlbumCoverFinishCheck();
1177     return;
1178   }
1179 
1180   if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
1181     Error(QString("Received HTTP code %1 for %2.").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()).arg(url.toString()));
1182     if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
1183     AlbumCoverFinishCheck();
1184     return;
1185   }
1186 
1187   QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
1188   if (mimetype.contains(';')) {
1189     mimetype = mimetype.left(mimetype.indexOf(';'));
1190   }
1191   if (!ImageUtils::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !ImageUtils::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
1192     Error(QString("Unsupported mimetype for image reader %1 for %2").arg(mimetype, url.toString()));
1193     if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
1194     AlbumCoverFinishCheck();
1195     return;
1196   }
1197 
1198   QByteArray data = reply->readAll();
1199   if (data.isEmpty()) {
1200     Error(QString("Received empty image data for %1").arg(url.toString()));
1201     if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
1202     AlbumCoverFinishCheck();
1203     return;
1204   }
1205 
1206   QList<QByteArray> format_list = ImageUtils::ImageFormatsForMimeType(mimetype.toUtf8());
1207   char *format = nullptr;
1208   if (!format_list.isEmpty()) {
1209     format = format_list.first().data();
1210   }
1211 
1212   QImage image;
1213   if (image.loadFromData(data, format)) {
1214     if (image.save(filename, format)) {
1215       while (album_covers_requests_sent_.contains(album_id)) {
1216         const QString song_id = album_covers_requests_sent_.take(album_id);
1217         if (songs_.contains(song_id)) {
1218           songs_[song_id].set_art_automatic(QUrl::fromLocalFile(filename));
1219         }
1220       }
1221     }
1222     else {
1223       Error(QString("Error saving image data to %1").arg(filename));
1224       if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
1225     }
1226   }
1227   else {
1228     if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
1229     Error(QString("Error decoding image data from %1").arg(url.toString()));
1230   }
1231 
1232   AlbumCoverFinishCheck();
1233 
1234 }
1235 
AlbumCoverFinishCheck()1236 void TidalRequest::AlbumCoverFinishCheck() {
1237 
1238   if (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests)
1239     FlushAlbumCoverRequests();
1240 
1241   FinishCheck();
1242 
1243 }
1244 
FinishCheck()1245 void TidalRequest::FinishCheck() {
1246 
1247   if (
1248       !finished_ &&
1249       !need_login_ &&
1250       albums_requests_queue_.isEmpty() &&
1251       artists_requests_queue_.isEmpty() &&
1252       songs_requests_queue_.isEmpty() &&
1253       artist_albums_requests_queue_.isEmpty() &&
1254       album_songs_requests_queue_.isEmpty() &&
1255       album_cover_requests_queue_.isEmpty() &&
1256       artist_albums_requests_pending_.isEmpty() &&
1257       album_songs_requests_pending_.isEmpty() &&
1258       album_covers_requests_sent_.isEmpty() &&
1259       artists_requests_active_ <= 0 &&
1260       albums_requests_active_ <= 0 &&
1261       songs_requests_active_ <= 0 &&
1262       artist_albums_requests_active_ <= 0 &&
1263       artist_albums_received_ >= artist_albums_requested_ &&
1264       album_songs_requests_active_ <= 0 &&
1265       album_songs_received_ >= album_songs_requested_ &&
1266       album_covers_requested_ <= album_covers_received_ &&
1267       album_covers_requests_active_ <= 0 &&
1268       album_covers_received_ >= album_covers_requested_
1269   ) {
1270     finished_ = true;
1271     if (no_results_ && songs_.isEmpty()) {
1272       if (IsSearch())
1273         emit Results(query_id_, SongMap(), tr("No match."));
1274       else
1275         emit Results(query_id_, SongMap(), QString());
1276     }
1277     else {
1278       if (songs_.isEmpty() && errors_.isEmpty()) {
1279         emit Results(query_id_, songs_, tr("Unknown error"));
1280       }
1281       else {
1282         emit Results(query_id_, songs_, ErrorsToHTML(errors_));
1283       }
1284     }
1285   }
1286 
1287 }
1288 
Error(const QString & error,const QVariant & debug)1289 void TidalRequest::Error(const QString &error, const QVariant &debug) {
1290 
1291   if (!error.isEmpty()) {
1292     errors_ << error;
1293     qLog(Error) << "Tidal:" << error;
1294   }
1295 
1296   if (debug.isValid()) qLog(Debug) << debug;
1297 
1298   FinishCheck();
1299 
1300 }
1301 
Warn(const QString & error,const QVariant & debug)1302 void TidalRequest::Warn(const QString &error, const QVariant &debug) {
1303 
1304   qLog(Error) << "Tidal:" << error;
1305   if (debug.isValid()) qLog(Debug) << debug;
1306 
1307 }
1308