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