Refactor Tidal, Spotify, Qobuz, Subsonic and cover providers

Use common HTTP, Json and OAuthenticator class
This commit is contained in:
Jonas Kvinge
2025-03-08 23:11:07 +01:00
parent 7de8a44709
commit cd516c37b9
81 changed files with 2429 additions and 3968 deletions

View File

@@ -1,6 +1,6 @@
/*
* Strawberry Music Player
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,7 +21,6 @@
#include <utility>
#include <QObject>
#include <QList>
#include <QByteArray>
#include <QByteArrayList>
@@ -35,6 +34,7 @@
#include <QJsonArray>
#include <QJsonValue>
#include <QTimer>
#include <QScopeGuard>
#include "includes/shared_ptr.h"
#include "core/logging.h"
@@ -107,24 +107,6 @@ TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler,
}
TidalRequest::~TidalRequest() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
QObject::disconnect(reply, nullptr, this, nullptr);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}
while (!album_cover_replies_.isEmpty()) {
QNetworkReply *reply = album_cover_replies_.takeFirst();
QObject::disconnect(reply, nullptr, this, nullptr);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}
}
void TidalRequest::Process() {
switch (query_type_) {
@@ -227,7 +209,7 @@ void TidalRequest::FlushArtistsRequests() {
while (!artists_requests_queue_.isEmpty() && artists_requests_active_ < kMaxConcurrentArtistsRequests) {
Request request = artists_requests_queue_.dequeue();
const Request request = artists_requests_queue_.dequeue();
ParamList parameters;
if (query_type_ == Type::SearchArtists) parameters << Param(u"query"_s, search_text_);
@@ -241,7 +223,6 @@ void TidalRequest::FlushArtistsRequests() {
reply = CreateRequest(u"search/artists"_s, parameters);
}
if (!reply) continue;
replies_ << reply;
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { ArtistsReplyReceived(reply, request.limit, request.offset); });
++artists_requests_active_;
@@ -275,7 +256,7 @@ void TidalRequest::FlushAlbumsRequests() {
while (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) {
Request request = albums_requests_queue_.dequeue();
const Request request = albums_requests_queue_.dequeue();
ParamList parameters;
if (query_type_ == Type::SearchAlbums) parameters << Param(u"query"_s, search_text_);
@@ -289,7 +270,6 @@ void TidalRequest::FlushAlbumsRequests() {
reply = CreateRequest(u"search/albums"_s, parameters);
}
if (!reply) continue;
replies_ << reply;
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { AlbumsReplyReceived(reply, request.limit, request.offset); });
++albums_requests_active_;
@@ -323,7 +303,7 @@ void TidalRequest::FlushSongsRequests() {
while (!songs_requests_queue_.isEmpty() && songs_requests_active_ < kMaxConcurrentSongsRequests) {
Request request = songs_requests_queue_.dequeue();
const Request request = songs_requests_queue_.dequeue();
ParamList parameters;
if (query_type_ == Type::SearchSongs) parameters << Param(u"query"_s, search_text_);
@@ -337,7 +317,6 @@ void TidalRequest::FlushSongsRequests() {
reply = CreateRequest(u"search/tracks"_s, parameters);
}
if (!reply) continue;
replies_ << reply;
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { SongsReplyReceived(reply, request.limit, request.offset); });
++songs_requests_active_;
@@ -395,48 +374,44 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply);
const JsonObjectResult json_object_result = ParseJsonObject(reply);
--artists_requests_active_;
++artists_requests_received_;
if (finished_) return;
if (data.isEmpty()) {
ArtistsFinishCheck();
return;
}
int offset = 0;
int artists_received = 0;
const QScopeGuard finish_check = qScopeGuard([this, limit_requested, &offset, &artists_received]() { ArtistsFinishCheck(limit_requested, offset, artists_received); });
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
ArtistsFinishCheck();
if (!json_object_result.success()) {
Error(json_object_result.error_message);
return;
}
const QJsonObject &json_object = json_object_result.json_object;
if (!json_obj.contains("limit"_L1) ||
!json_obj.contains("offset"_L1) ||
!json_obj.contains("totalNumberOfItems"_L1) ||
!json_obj.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_obj);
ArtistsFinishCheck();
if (!json_object.contains("limit"_L1) ||
!json_object.contains("offset"_L1) ||
!json_object.contains("totalNumberOfItems"_L1) ||
!json_object.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_object);
return;
}
//int limit = json_obj["limit"].toInt();
int offset = json_obj["offset"_L1].toInt();
int artists_total = json_obj["totalNumberOfItems"_L1].toInt();
//int limit = json_object["limit"].toInt();
offset = json_object["offset"_L1].toInt();
const int artists_total = json_object["totalNumberOfItems"_L1].toInt();
if (offset_requested == 0) {
artists_total_ = artists_total;
}
else if (artists_total != artists_total_) {
Error(QStringLiteral("totalNumberOfItems returned does not match previous totalNumberOfItems! %1 != %2").arg(artists_total).arg(artists_total_));
ArtistsFinishCheck();
return;
}
if (offset != offset_requested) {
Error(QStringLiteral("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
ArtistsFinishCheck();
return;
}
@@ -444,19 +419,17 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
Q_EMIT UpdateProgress(query_id_, GetProgress(artists_received_, artists_total_));
}
QJsonValue value_items = ExtractItems(json_obj);
if (!value_items.isArray()) {
ArtistsFinishCheck();
const JsonArrayResult json_array_result = GetJsonArray(json_object, u"items"_s);
if (!json_array_result.success()) {
Error(json_array_result.error_message);
return;
}
const QJsonArray array_items = value_items.toArray();
const QJsonArray &array_items = json_array_result.json_array;
if (array_items.isEmpty()) { // Empty array means no results
ArtistsFinishCheck();
return;
}
int artists_received = 0;
for (const QJsonValue &value_item : array_items) {
++artists_received;
@@ -465,30 +438,30 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
Error(u"Invalid Json reply, item in array is not a object."_s);
continue;
}
QJsonObject obj_item = value_item.toObject();
QJsonObject object_item = value_item.toObject();
if (obj_item.contains("item"_L1)) {
QJsonValue json_item = obj_item["item"_L1];
if (object_item.contains("item"_L1)) {
const QJsonValue json_item = object_item["item"_L1];
if (!json_item.isObject()) {
Error(u"Invalid Json reply, item in array is not a object."_s, json_item);
continue;
}
obj_item = json_item.toObject();
object_item = json_item.toObject();
}
if (!obj_item.contains("id"_L1) || !obj_item.contains("name"_L1)) {
Error(u"Invalid Json reply, item missing id or album."_s, obj_item);
if (!object_item.contains("id"_L1) || !object_item.contains("name"_L1)) {
Error(u"Invalid Json reply, item missing id or album."_s, object_item);
continue;
}
Artist artist;
if (obj_item["id"_L1].isString()) {
artist.artist_id = obj_item["id"_L1].toString();
if (object_item["id"_L1].isString()) {
artist.artist_id = object_item["id"_L1].toString();
}
else {
artist.artist_id = QString::number(obj_item["id"_L1].toInt());
artist.artist_id = QString::number(object_item["id"_L1].toInt());
}
artist.artist = obj_item["name"_L1].toString();
artist.artist = object_item["name"_L1].toString();
if (artist_albums_requests_pending_.contains(artist.artist_id)) continue;
@@ -501,8 +474,6 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
if (offset_requested != 0) Q_EMIT UpdateProgress(query_id_, GetProgress(artists_received_, artists_total_));
ArtistsFinishCheck(limit_requested, offset, artists_received);
}
void TidalRequest::ArtistsFinishCheck(const int limit, const int offset, const int artists_received) {
@@ -569,7 +540,6 @@ void TidalRequest::FlushArtistAlbumsRequests() {
if (request.offset > 0) parameters << Param(u"offset"_s, QString::number(request.offset));
QNetworkReply *reply = CreateRequest(QStringLiteral("artists/%1/albums").arg(request.artist.artist_id), parameters);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { ArtistAlbumsReplyReceived(reply, request.artist, request.offset); });
replies_ << reply;
++artist_albums_requests_active_;
@@ -593,52 +563,54 @@ void TidalRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_req
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply);
const JsonObjectResult json_object_result = ParseJsonObject(reply);
if (finished_) return;
if (data.isEmpty()) {
AlbumsFinishCheck(artist_requested);
int offset = 0;
int albums_total = 0;
int albums_received = 0;
const QScopeGuard finish_check = qScopeGuard([this, artist_requested, limit_requested, &offset, &albums_total, &albums_received]() { AlbumsFinishCheck(artist_requested, limit_requested, offset, albums_total, albums_received); });
if (!json_object_result.success()) {
Error(json_object_result.error_message);
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
AlbumsFinishCheck(artist_requested);
const QJsonObject &json_object = json_object_result.json_object;
if (json_object.isEmpty()) {
Error(json_object_result.error_message);
return;
}
if (!json_obj.contains("limit"_L1) ||
!json_obj.contains("offset"_L1) ||
!json_obj.contains("totalNumberOfItems"_L1) ||
!json_obj.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_obj);
AlbumsFinishCheck(artist_requested);
if (!json_object.contains("limit"_L1) ||
!json_object.contains("offset"_L1) ||
!json_object.contains("totalNumberOfItems"_L1) ||
!json_object.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_object);
return;
}
//int limit = json_obj["limit"].toInt();
int offset = json_obj["offset"_L1].toInt();
int albums_total = json_obj["totalNumberOfItems"_L1].toInt();
offset = json_object["offset"_L1].toInt();
albums_total = json_object["totalNumberOfItems"_L1].toInt();
if (offset != offset_requested) {
Error(QStringLiteral("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
AlbumsFinishCheck(artist_requested);
return;
}
QJsonValue value_items = ExtractItems(json_obj);
if (!value_items.isArray()) {
AlbumsFinishCheck(artist_requested);
const JsonArrayResult json_array_result = GetJsonArray(json_object, u"items"_s);
if (!json_array_result.success()) {
Error(json_array_result.error_message);
return;
}
const QJsonArray array_items = value_items.toArray();
const QJsonArray &array_items = json_array_result.json_array;
if (array_items.isEmpty()) {
AlbumsFinishCheck(artist_requested);
return;
}
int albums_received = 0;
for (const QJsonValue &value_item : array_items) {
++albums_received;
@@ -647,79 +619,79 @@ void TidalRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_req
Error(u"Invalid Json reply, item in array is not a object."_s);
continue;
}
QJsonObject obj_item = value_item.toObject();
QJsonObject object_item = value_item.toObject();
if (obj_item.contains("item"_L1)) {
QJsonValue json_item = obj_item["item"_L1];
if (object_item.contains("item"_L1)) {
const QJsonValue json_item = object_item["item"_L1];
if (!json_item.isObject()) {
Error(u"Invalid Json reply, item in array is not a object."_s, json_item);
continue;
}
obj_item = json_item.toObject();
object_item = json_item.toObject();
}
Album album;
if (obj_item.contains("type"_L1)) { // This was an albums request or search
if (!obj_item.contains("id"_L1) || !obj_item.contains("title"_L1)) {
Error(u"Invalid Json reply, item is missing ID or title."_s, obj_item);
if (object_item.contains("type"_L1)) { // This was an albums request or search
if (!object_item.contains("id"_L1) || !object_item.contains("title"_L1)) {
Error(u"Invalid Json reply, item is missing ID or title."_s, object_item);
continue;
}
if (obj_item["id"_L1].isString()) {
album.album_id = obj_item["id"_L1].toString();
if (object_item["id"_L1].isString()) {
album.album_id = object_item["id"_L1].toString();
}
else {
album.album_id = QString::number(obj_item["id"_L1].toInt());
album.album_id = QString::number(object_item["id"_L1].toInt());
}
album.album = obj_item["title"_L1].toString();
if (service_->album_explicit() && obj_item.contains("explicit"_L1)) {
album.album_explicit = obj_item["explicit"_L1].toVariant().toBool();
album.album = object_item["title"_L1].toString();
if (service_->album_explicit() && object_item.contains("explicit"_L1)) {
album.album_explicit = object_item["explicit"_L1].toVariant().toBool();
if (album.album_explicit && !album.album.isEmpty()) {
album.album.append(" (Explicit)"_L1);
}
}
}
else if (obj_item.contains("album"_L1)) { // This was a tracks request or search
QJsonValue value_album = obj_item["album"_L1];
else if (object_item.contains("album"_L1)) { // This was a tracks request or search
const QJsonValue value_album = object_item["album"_L1];
if (!value_album.isObject()) {
Error(u"Invalid Json reply, item album is not a object."_s, value_album);
continue;
}
QJsonObject obj_album = value_album.toObject();
if (!obj_album.contains("id"_L1) || !obj_album.contains("title"_L1)) {
Error(u"Invalid Json reply, item album is missing ID or title."_s, obj_album);
const QJsonObject object_album = value_album.toObject();
if (!object_album.contains("id"_L1) || !object_album.contains("title"_L1)) {
Error(u"Invalid Json reply, item album is missing ID or title."_s, object_album);
continue;
}
if (obj_album["id"_L1].isString()) {
album.album_id = obj_album["id"_L1].toString();
if (object_album["id"_L1].isString()) {
album.album_id = object_album["id"_L1].toString();
}
else {
album.album_id = QString::number(obj_album["id"_L1].toInt());
album.album_id = QString::number(object_album["id"_L1].toInt());
}
album.album = obj_album["title"_L1].toString();
if (service_->album_explicit() && obj_album.contains("explicit"_L1)) {
album.album_explicit = obj_album["explicit"_L1].toVariant().toBool();
album.album = object_album["title"_L1].toString();
if (service_->album_explicit() && object_album.contains("explicit"_L1)) {
album.album_explicit = object_album["explicit"_L1].toVariant().toBool();
if (album.album_explicit && !album.album.isEmpty()) {
album.album.append(" (Explicit)"_L1);
}
}
}
else {
Error(u"Invalid Json reply, item missing type or album."_s, obj_item);
Error(u"Invalid Json reply, item missing type or album."_s, object_item);
continue;
}
if (album_songs_requests_pending_.contains(album.album_id)) continue;
if (!obj_item.contains("artist"_L1) || !obj_item.contains("title"_L1) || !obj_item.contains("audioQuality"_L1)) {
Error(u"Invalid Json reply, item missing artist, title or audioQuality."_s, obj_item);
if (!object_item.contains("artist"_L1) || !object_item.contains("title"_L1) || !object_item.contains("audioQuality"_L1)) {
Error(u"Invalid Json reply, item missing artist, title or audioQuality."_s, object_item);
continue;
}
QJsonValue value_artist = obj_item["artist"_L1];
const QJsonValue value_artist = object_item["artist"_L1];
if (!value_artist.isObject()) {
Error(u"Invalid Json reply, item artist is not a object."_s, value_artist);
continue;
}
QJsonObject obj_artist = value_artist.toObject();
const QJsonObject obj_artist = value_artist.toObject();
if (!obj_artist.contains("id"_L1) || !obj_artist.contains("name"_L1)) {
Error(u"Invalid Json reply, item artist missing id or name."_s, obj_artist);
continue;
@@ -751,8 +723,6 @@ void TidalRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_req
Q_EMIT UpdateProgress(query_id_, GetProgress(albums_received_, albums_total_));
}
AlbumsFinishCheck(artist_requested, limit_requested, offset, albums_total, albums_received);
}
void TidalRequest::AlbumsFinishCheck(const Artist &artist, const int limit, const int offset, const int albums_total, const int albums_received) {
@@ -843,7 +813,6 @@ void TidalRequest::FlushAlbumSongsRequests() {
ParamList parameters;
if (request.offset > 0) parameters << Param(u"offset"_s, QString::number(request.offset));
QNetworkReply *reply = CreateRequest(QStringLiteral("albums/%1/tracks").arg(request.album.album_id), parameters);
replies_ << reply;
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { AlbumSongsReplyReceived(reply, request.artist, request.album, request.offset); });
++album_songs_requests_active_;
@@ -870,76 +839,75 @@ void TidalRequest::SongsReceived(QNetworkReply *reply, const Artist &artist, con
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply);
const JsonObjectResult json_object_result = ParseJsonObject(reply);
if (finished_) return;
if (data.isEmpty()) {
SongsFinishCheck(artist, album, limit_requested, offset_requested);
int songs_total = 0;
int songs_received = 0;
const QScopeGuard finish_check = qScopeGuard([this, artist, album, limit_requested, offset_requested, &songs_total, &songs_received]() { SongsFinishCheck(artist, album, limit_requested, offset_requested, songs_total, songs_received); });
if (!json_object_result.success()) {
Error(json_object_result.error_message);
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
SongsFinishCheck(artist, album, limit_requested, offset_requested);
const QJsonObject &json_object = json_object_result.json_object;
if (json_object.isEmpty()) {
return;
}
if (!json_obj.contains("limit"_L1) ||
!json_obj.contains("offset"_L1) ||
!json_obj.contains("totalNumberOfItems"_L1) ||
!json_obj.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_obj);
SongsFinishCheck(artist, album, limit_requested, offset_requested);
if (!json_object.contains("limit"_L1) ||
!json_object.contains("offset"_L1) ||
!json_object.contains("totalNumberOfItems"_L1) ||
!json_object.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_object);
return;
}
//int limit = json_obj["limit"].toInt();
int offset = json_obj["offset"_L1].toInt();
int songs_total = json_obj["totalNumberOfItems"_L1].toInt();
const int offset = json_object["offset"_L1].toInt();
songs_total = json_object["totalNumberOfItems"_L1].toInt();
if (offset != offset_requested) {
Error(QStringLiteral("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
SongsFinishCheck(artist, album, limit_requested, offset_requested, songs_total, 0);
return;
}
QJsonValue json_value = ExtractItems(json_obj);
if (!json_value.isArray()) {
SongsFinishCheck(artist, album, limit_requested, offset_requested, songs_total, 0);
const JsonArrayResult json_array_result = GetJsonArray(json_object, u"items"_s);
if (!json_array_result.success()) {
Error(json_array_result.error_message);
return;
}
const QJsonArray array_items = json_value.toArray();
const QJsonArray &array_items = json_array_result.json_array;
if (array_items.isEmpty()) {
SongsFinishCheck(artist, album, limit_requested, offset_requested, songs_total, 0);
return;
}
bool compilation = false;
bool multidisc = false;
SongList songs;
int songs_received = 0;
for (const QJsonValue &value_item : array_items) {
if (!value_item.isObject()) {
Error(u"Invalid Json reply, track is not a object."_s);
continue;
}
QJsonObject obj_item = value_item.toObject();
QJsonObject object_item = value_item.toObject();
if (obj_item.contains("item"_L1)) {
QJsonValue item = obj_item["item"_L1];
if (object_item.contains("item"_L1)) {
const QJsonValue item = object_item["item"_L1];
if (!item.isObject()) {
Error(u"Invalid Json reply, item is not a object."_s, item);
continue;
}
obj_item = item.toObject();
object_item = item.toObject();
}
++songs_received;
Song song(Song::Source::Tidal);
ParseSong(song, obj_item, artist, album);
ParseSong(song, object_item, artist, album);
if (!song.is_valid()) continue;
if (song.disc() >= 2) multidisc = true;
if (song.is_compilation()) compilation = true;
@@ -957,8 +925,6 @@ void TidalRequest::SongsReceived(QNetworkReply *reply, const Artist &artist, con
Q_EMIT UpdateProgress(query_id_, GetProgress(songs_received_, songs_total_));
}
SongsFinishCheck(artist, album, limit_requested, offset_requested, songs_total, songs_received);
}
void TidalRequest::SongsFinishCheck(const Artist &artist, const Album &album, const int limit, const int offset, const int songs_total, const int songs_received) {
@@ -1017,10 +983,10 @@ void TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti
return;
}
QJsonValue value_artist = json_obj["artist"_L1];
QJsonValue value_album = json_obj["album"_L1];
QJsonValue json_duration = json_obj["duration"_L1];
//QJsonArray array_artists = json_obj["artists"].toArray();
const QJsonValue value_artist = json_obj["artist"_L1];
const QJsonValue value_album = json_obj["album"_L1];
const QJsonValue json_duration = json_obj["duration"_L1];
//const QJsonArray array_artists = json_obj["artists"].toArray();
QString song_id;
if (json_obj["id"_L1].isString()) {
@@ -1031,52 +997,52 @@ void TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti
}
QString title = json_obj["title"_L1].toString();
//QString urlstr = json_obj["url"].toString();
int track = json_obj["trackNumber"_L1].toInt();
int disc = json_obj["volumeNumber"_L1].toInt();
bool allow_streaming = json_obj["allowStreaming"_L1].toBool();
bool stream_ready = json_obj["streamReady"_L1].toBool();
QString copyright = json_obj["copyright"_L1].toString();
//const QString urlstr = json_obj["url"].toString();
const int track = json_obj["trackNumber"_L1].toInt();
const int disc = json_obj["volumeNumber"_L1].toInt();
const bool allow_streaming = json_obj["allowStreaming"_L1].toBool();
const bool stream_ready = json_obj["streamReady"_L1].toBool();
const QString copyright = json_obj["copyright"_L1].toString();
if (!value_artist.isObject()) {
Error(u"Invalid Json reply, track artist is not a object."_s, value_artist);
return;
}
QJsonObject obj_artist = value_artist.toObject();
if (!obj_artist.contains("id"_L1) || !obj_artist.contains("name"_L1)) {
Error(u"Invalid Json reply, track artist is missing id or name."_s, obj_artist);
const QJsonObject object_artist = value_artist.toObject();
if (!object_artist.contains("id"_L1) || !object_artist.contains("name"_L1)) {
Error(u"Invalid Json reply, track artist is missing id or name."_s, object_artist);
return;
}
QString artist_id;
if (obj_artist["id"_L1].isString()) {
artist_id = obj_artist["id"_L1].toString();
if (object_artist["id"_L1].isString()) {
artist_id = object_artist["id"_L1].toString();
}
else {
artist_id = QString::number(obj_artist["id"_L1].toInt());
artist_id = QString::number(object_artist["id"_L1].toInt());
}
QString artist = obj_artist["name"_L1].toString();
QString artist = object_artist["name"_L1].toString();
if (!value_album.isObject()) {
Error(u"Invalid Json reply, track album is not a object."_s, value_album);
return;
}
QJsonObject obj_album = value_album.toObject();
if (!obj_album.contains("id"_L1) || !obj_album.contains("title"_L1)) {
Error(u"Invalid Json reply, track album is missing ID or title."_s, obj_album);
const QJsonObject object_album = value_album.toObject();
if (!object_album.contains("id"_L1) || !object_album.contains("title"_L1)) {
Error(u"Invalid Json reply, track album is missing ID or title."_s, object_album);
return;
}
QString album_id;
if (obj_album["id"_L1].isString()) {
album_id = obj_album["id"_L1].toString();
if (object_album["id"_L1].isString()) {
album_id = object_album["id"_L1].toString();
}
else {
album_id = QString::number(obj_album["id"_L1].toInt());
album_id = QString::number(object_album["id"_L1].toInt());
}
if (!album.album_id.isEmpty() && album.album_id != album_id) {
Error(u"Invalid Json reply, track album id is wrong."_s, obj_album);
Error(u"Invalid Json reply, track album id is wrong."_s, object_album);
return;
}
QString album_title = obj_album["title"_L1].toString();
QString album_title = object_album["title"_L1].toString();
if (album.album_explicit) album_title.append(" (Explicit)"_L1);
if (!allow_streaming) {
@@ -1104,8 +1070,8 @@ void TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti
}
QUrl cover_url;
if (obj_album.contains("cover"_L1)) {
const QString cover = obj_album["cover"_L1].toString().replace(u'-', u'/');
if (object_album.contains("cover"_L1)) {
const QString cover = object_album["cover"_L1].toString().replace(u'-', u'/');
if (!cover.isEmpty()) {
cover_url.setUrl(QStringLiteral("%1/images/%2/%3.jpg").arg(QLatin1String(kResourcesUrl), cover, coversize_));
}
@@ -1207,11 +1173,7 @@ void TidalRequest::FlushAlbumCoverRequests() {
while (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) {
AlbumCoverRequest request = album_cover_requests_queue_.dequeue();
QNetworkRequest req(request.url);
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
QNetworkReply *reply = network_->get(req);
album_cover_replies_ << reply;
QNetworkReply *reply = CreateGetRequest(request.url);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, request]() { AlbumCoverReceived(reply, request.album_id, request.url, request.filename); });
++album_covers_requests_active_;
@@ -1222,8 +1184,8 @@ void TidalRequest::FlushAlbumCoverRequests() {
void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url, const QString &filename) {
if (album_cover_replies_.contains(reply)) {
album_cover_replies_.removeAll(reply);
if (replies_.contains(reply)) {
replies_.removeAll(reply);
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
}
@@ -1237,24 +1199,23 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
if (finished_) return;
const QScopeGuard finish_check = qScopeGuard([this]() { AlbumCoverFinishCheck(); });
Q_EMIT UpdateProgress(query_id_, GetProgress(album_covers_requests_received_, album_covers_requests_total_));
if (!album_covers_requests_sent_.contains(album_id)) {
AlbumCoverFinishCheck();
return;
}
if (reply->error() != QNetworkReply::NoError) {
Error(QStringLiteral("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
album_covers_requests_sent_.remove(album_id);
AlbumCoverFinishCheck();
return;
}
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
Error(QStringLiteral("Received HTTP code %1 for %2.").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()).arg(url.toString()));
if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
AlbumCoverFinishCheck();
return;
}
@@ -1265,7 +1226,6 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
if (!ImageUtils::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !ImageUtils::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
Error(QStringLiteral("Unsupported mimetype for image reader %1 for %2").arg(mimetype, url.toString()));
if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
AlbumCoverFinishCheck();
return;
}
@@ -1273,7 +1233,6 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
if (data.isEmpty()) {
Error(QStringLiteral("Received empty image data for %1").arg(url.toString()));
if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
AlbumCoverFinishCheck();
return;
}
@@ -1303,8 +1262,6 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
Error(QStringLiteral("Error decoding image data from %1").arg(url.toString()));
}
AlbumCoverFinishCheck();
}
void TidalRequest::AlbumCoverFinishCheck() {
@@ -1332,13 +1289,13 @@ void TidalRequest::FinishCheck() {
artist_albums_requests_active_ <= 0 &&
album_songs_requests_active_ <= 0 &&
album_covers_requests_active_ <= 0
) {
) {
if (timer_flush_requests_->isActive()) {
timer_flush_requests_->stop();
}
finished_ = true;
if (songs_.isEmpty()) {
if (errors_.isEmpty()) {
if (error_.isEmpty()) {
if (IsSearch()) {
Q_EMIT Results(query_id_, SongMap(), tr("No match."));
}
@@ -1347,7 +1304,7 @@ void TidalRequest::FinishCheck() {
}
}
else {
Q_EMIT Results(query_id_, SongMap(), ErrorsToHTML(errors_));
Q_EMIT Results(query_id_, SongMap(), error_);
}
}
else {
@@ -1363,22 +1320,20 @@ int TidalRequest::GetProgress(const int count, const int total) {
}
void TidalRequest::Error(const QString &error, const QVariant &debug) {
void TidalRequest::Error(const QString &error_message, const QVariant &debug_output) {
if (!error.isEmpty()) {
errors_ << error;
qLog(Error) << "Tidal:" << error;
qLog(Error) << "Tidal:" << error_message;
if (debug_output.isValid()) {
qLog(Debug) << debug_output;
}
if (debug.isValid()) qLog(Debug) << debug;
FinishCheck();
error_ = error_message;
}
void TidalRequest::Warn(const QString &error, const QVariant &debug) {
void TidalRequest::Warn(const QString &error_message, const QVariant &debug) {
qLog(Error) << "Tidal:" << error;
qLog(Warning) << "Tidal:" << error_message;
if (debug.isValid()) qLog(Debug) << debug;
}