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 2022-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2022-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 <QByteArray>
#include <QString>
#include <QUrl>
@@ -33,13 +32,14 @@
#include <QJsonArray>
#include <QJsonValue>
#include <QTimer>
#include <QScopeGuard>
#include "core/logging.h"
#include "core/networkaccessmanager.h"
#include "core/song.h"
#include "constants/timeconstants.h"
#include "utilities/imageutils.h"
#include "utilities/coverutils.h"
#include "core/logging.h"
#include "core/networkaccessmanager.h"
#include "core/song.h"
#include "spotifyservice.h"
#include "spotifybaserequest.h"
#include "spotifyrequest.h"
@@ -47,18 +47,17 @@
using namespace Qt::Literals::StringLiterals;
namespace {
const int kMaxConcurrentArtistsRequests = 1;
const int kMaxConcurrentAlbumsRequests = 1;
const int kMaxConcurrentSongsRequests = 1;
const int kMaxConcurrentArtistAlbumsRequests = 1;
const int kMaxConcurrentAlbumSongsRequests = 1;
const int kMaxConcurrentAlbumCoverRequests = 10;
const int kFlushRequestsDelay = 200;
}
constexpr int kMaxConcurrentArtistsRequests = 1;
constexpr int kMaxConcurrentAlbumsRequests = 1;
constexpr int kMaxConcurrentSongsRequests = 1;
constexpr int kMaxConcurrentArtistAlbumsRequests = 1;
constexpr int kMaxConcurrentAlbumSongsRequests = 1;
constexpr int kMaxConcurrentAlbumCoverRequests = 10;
constexpr int kFlushRequestsDelay = 200;
} // namespace
SpotifyRequest::SpotifyRequest(SpotifyService *service, const SharedPtr<NetworkAccessManager> network, const Type type, QObject *parent)
: SpotifyBaseRequest(service, network, parent),
service_(service),
network_(network),
timer_flush_requests_(new QTimer(this)),
type_(type),
@@ -107,20 +106,6 @@ SpotifyRequest::~SpotifyRequest() {
timer_flush_requests_->stop();
}
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 SpotifyRequest::Process() {
@@ -232,7 +217,7 @@ void SpotifyRequest::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 = ParamList() << Param(u"type"_s, u"artist"_s);
if (type_ == Type::SearchArtists) {
@@ -252,7 +237,6 @@ void SpotifyRequest::FlushArtistsRequests() {
reply = CreateRequest(u"search"_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_;
@@ -286,7 +270,7 @@ void SpotifyRequest::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 (type_ == Type::SearchAlbums) {
@@ -306,7 +290,6 @@ void SpotifyRequest::FlushAlbumsRequests() {
reply = CreateRequest(u"search"_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_;
@@ -340,7 +323,7 @@ void SpotifyRequest::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 (type_ == Type::SearchSongs) {
@@ -361,7 +344,6 @@ void SpotifyRequest::FlushSongsRequests() {
reply = CreateRequest(u"search"_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_;
@@ -419,57 +401,55 @@ void SpotifyRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_
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();
int offset = 0;
int artists_received = 0;
const QScopeGuard finish_check = qScopeGuard([this, limit_requested, &offset, &artists_received]() { ArtistsFinishCheck(limit_requested, offset, artists_received); });
if (!json_object_result.success()) {
Error(json_object_result.error_message);
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
ArtistsFinishCheck();
const QJsonObject &json_object = json_object_result.json_object;
if (json_object.isEmpty()) {
return;
}
if (!json_obj.contains("artists"_L1) || !json_obj["artists"_L1].isObject()) {
Error(u"Json object missing values."_s, json_obj);
ArtistsFinishCheck();
if (!json_object.contains("artists"_L1) || !json_object["artists"_L1].isObject()) {
Error(u"Json object missing values."_s, json_object);
return;
}
QJsonObject obj_artists = json_obj["artists"_L1].toObject();
const QJsonObject obj_artists = json_object["artists"_L1].toObject();
if (!obj_artists.contains("limit"_L1) ||
!obj_artists.contains("total"_L1) ||
!obj_artists.contains("items"_L1)) {
Error(u"Json object missing values."_s, obj_artists);
ArtistsFinishCheck();
return;
}
int offset = 0;
if (obj_artists.contains("offset"_L1)) {
offset = obj_artists["offset"_L1].toInt();
}
int artists_total = obj_artists["total"_L1].toInt();
const int artists_total = obj_artists["total"_L1].toInt();
if (offset_requested == 0) {
artists_total_ = artists_total;
}
else if (artists_total != artists_total_) {
Error(QStringLiteral("Total returned does not match previous total! %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;
}
@@ -477,20 +457,18 @@ void SpotifyRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_
Q_EMIT UpdateProgress(query_id_, GetProgress(artists_received_, artists_total_));
}
QJsonValue value_items = ExtractItems(obj_artists);
if (!value_items.isArray()) {
ArtistsFinishCheck();
const JsonArrayResult json_array_result = GetJsonArray(obj_artists, 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
if (offset_requested == 0) no_results_ = true;
ArtistsFinishCheck();
return;
}
int artists_received = 0;
for (const QJsonValue &value_item : array_items) {
++artists_received;
@@ -499,24 +477,24 @@ void SpotifyRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_
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)) {
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;
}
QString artist_id = obj_item["id"_L1].toString();
QString artist = obj_item["name"_L1].toString();
const QString artist_id = object_item["id"_L1].toString();
const QString artist = object_item["name"_L1].toString();
if (artist_albums_requests_pending_.contains(artist_id)) continue;
@@ -530,15 +508,13 @@ void SpotifyRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_
if (offset_requested != 0) Q_EMIT UpdateProgress(query_id_, GetProgress(artists_total_, artists_received_));
ArtistsFinishCheck(limit_requested, offset, artists_received);
}
void SpotifyRequest::ArtistsFinishCheck(const int limit, const int offset, const int artists_received) {
if (finished_) return;
if ((limit == 0 || limit > artists_received) && artists_received_ < artists_total_) {
if (artists_received > 0 && (limit == 0 || limit > artists_received) && artists_received_ < artists_total_) {
int offset_next = offset + artists_received;
if (offset_next > 0 && offset_next < artists_total_) {
if (type_ == Type::FavouriteArtists) AddArtistsRequest(offset_next);
@@ -592,13 +568,12 @@ void SpotifyRequest::FlushArtistAlbumsRequests() {
while (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) {
ArtistAlbumsRequest request = artist_albums_requests_queue_.dequeue();
const ArtistAlbumsRequest request = artist_albums_requests_queue_.dequeue();
ParamList parameters;
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_;
@@ -622,40 +597,43 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply);
JsonObjectResult json_object_result = ParseJsonObject(reply);
if (finished_) return;
if (data.isEmpty()) {
AlbumsFinishCheck(artist_artist);
int offset = 0;
int albums_total = 0;
int albums_received = 0;
const QScopeGuard finish_check = qScopeGuard([this, artist_artist, limit_requested, &offset, &albums_total, &albums_received]() { AlbumsFinishCheck(artist_artist, 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_artist);
QJsonObject json_object = json_object_result.json_object;
if (json_object.isEmpty()) {
return;
}
if (json_obj.contains("albums"_L1) && json_obj["albums"_L1].isObject()) {
json_obj = json_obj["albums"_L1].toObject();
if (json_object.contains("albums"_L1) && json_object["albums"_L1].isObject()) {
json_object = json_object["albums"_L1].toObject();
}
if (json_obj.contains("tracks"_L1) && json_obj["tracks"_L1].isObject()) {
json_obj = json_obj["tracks"_L1].toObject();
if (json_object.contains("tracks"_L1) && json_object["tracks"_L1].isObject()) {
json_object = json_object["tracks"_L1].toObject();
}
if (!json_obj.contains("limit"_L1) ||
!json_obj.contains("offset"_L1) ||
!json_obj.contains("total"_L1) ||
!json_obj.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_obj);
AlbumsFinishCheck(artist_artist);
if (!json_object.contains("limit"_L1) ||
!json_object.contains("offset"_L1) ||
!json_object.contains("total"_L1) ||
!json_object.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_object);
return;
}
int offset = json_obj["offset"_L1].toInt();
int albums_total = json_obj["total"_L1].toInt();
offset = json_object["offset"_L1].toInt();
albums_total = json_object["total"_L1].toInt();
if (type_ == Type::FavouriteAlbums || type_ == Type::SearchAlbums) {
albums_total_ = albums_total;
@@ -663,73 +641,70 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a
if (offset != offset_requested) {
Error(QStringLiteral("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
AlbumsFinishCheck(artist_artist);
return;
}
QJsonValue value_items = ExtractItems(json_obj);
if (!value_items.isArray()) {
AlbumsFinishCheck(artist_artist);
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()) {
if ((type_ == Type::FavouriteAlbums || type_ == Type::SearchAlbums || (type_ == Type::SearchSongs && fetchalbums_)) && offset_requested == 0) {
no_results_ = true;
}
AlbumsFinishCheck(artist_artist);
return;
}
int albums_received = 0;
for (const QJsonValue &value_item : array_items) {
albums_received = static_cast<int>(array_items.count());
++albums_received;
for (const QJsonValue &value_item : array_items) {
if (!value_item.isObject()) {
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)) {
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("album"_L1)) {
QJsonValue json_item = obj_item["album"_L1];
if (object_item.contains("album"_L1)) {
QJsonValue json_item = object_item["album"_L1];
if (!json_item.isObject()) {
Error(u"Invalid Json reply, album in array is not a object."_s, json_item);
continue;
}
obj_item = json_item.toObject();
object_item = json_item.toObject();
}
Artist artist;
Album album;
if (!obj_item.contains("id"_L1)) {
Error(u"Invalid Json reply, item is missing ID."_s, obj_item);
if (!object_item.contains("id"_L1)) {
Error(u"Invalid Json reply, item is missing ID."_s, object_item);
continue;
}
if (!obj_item.contains("name"_L1)) {
Error(u"Invalid Json reply, item is missing name."_s, obj_item);
if (!object_item.contains("name"_L1)) {
Error(u"Invalid Json reply, item is missing name."_s, object_item);
continue;
}
if (!obj_item.contains("images"_L1)) {
Error(u"Invalid Json reply, item is missing images."_s, obj_item);
if (!object_item.contains("images"_L1)) {
Error(u"Invalid Json reply, item is missing images."_s, object_item);
continue;
}
album.album_id = obj_item["id"_L1].toString();
album.album = obj_item["name"_L1].toString();
album.album_id = object_item["id"_L1].toString();
album.album = object_item["name"_L1].toString();
if (obj_item.contains("artists"_L1) && obj_item["artists"_L1].isArray()) {
const QJsonArray array_artists = obj_item["artists"_L1].toArray();
if (object_item.contains("artists"_L1) && object_item["artists"_L1].isArray()) {
const QJsonArray array_artists = object_item["artists"_L1].toArray();
bool artist_matches = false;
for (const QJsonValue &value : array_artists) {
if (!value.isObject()) {
@@ -747,7 +722,6 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a
}
}
if (!artist_matches && (type_ == Type::FavouriteArtists || type_ == Type::SearchArtists)) {
AlbumsFinishCheck(artist_artist);
return;
}
}
@@ -756,8 +730,8 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a
artist = artist_artist;
}
if (obj_item.contains("images"_L1) && obj_item["images"_L1].isArray()) {
const QJsonArray array_images = obj_item["images"_L1].toArray();
if (object_item.contains("images"_L1) && object_item["images"_L1].isArray()) {
const QJsonArray array_images = object_item["images"_L1].toArray();
for (const QJsonValue &value : array_images) {
if (!value.isObject()) {
continue;
@@ -773,8 +747,8 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a
}
}
if (obj_item.contains("tracks"_L1) && obj_item["tracks"_L1].isObject()) {
QJsonObject obj_tracks = obj_item["tracks"_L1].toObject();
if (object_item.contains("tracks"_L1) && object_item["tracks"_L1].isObject()) {
QJsonObject obj_tracks = object_item["tracks"_L1].toObject();
if (obj_tracks.contains("items"_L1) && obj_tracks["items"_L1].isArray()) {
const QJsonArray array_tracks = obj_tracks["items"_L1].toArray();
bool compilation = false;
@@ -816,15 +790,13 @@ void SpotifyRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_a
Q_EMIT UpdateProgress(query_id_, GetProgress(albums_received_, albums_total_));
}
AlbumsFinishCheck(artist_artist, limit_requested, offset, albums_total, albums_received);
}
void SpotifyRequest::AlbumsFinishCheck(const Artist &artist, const int limit, const int offset, const int albums_total, const int albums_received) {
if (finished_) return;
if (limit == 0 || limit > albums_received) {
if (albums_received > 0 && (limit == 0 || limit > albums_received)) {
int offset_next = offset + albums_received;
if (offset_next > 0 && offset_next < albums_total) {
switch (type_) {
@@ -904,15 +876,12 @@ void SpotifyRequest::AddAlbumSongsRequest(const Artist &artist, const Album &alb
void SpotifyRequest::FlushAlbumSongsRequests() {
while (!album_songs_requests_queue_.isEmpty() && album_songs_requests_active_ < kMaxConcurrentAlbumSongsRequests) {
AlbumSongsRequest request = album_songs_requests_queue_.dequeue();
const AlbumSongsRequest request = album_songs_requests_queue_.dequeue();
++album_songs_requests_active_;
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); });
}
}
@@ -935,36 +904,38 @@ void SpotifyRequest::SongsReceived(QNetworkReply *reply, const Artist &artist, c
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, 0, 0);
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, 0, 0);
QJsonObject json_object = json_object_result.json_object;
if (json_object.isEmpty()) {
return;
}
if (json_obj.contains("tracks"_L1) && json_obj["tracks"_L1].isObject()) {
json_obj = json_obj["tracks"_L1].toObject();
if (json_object.contains("tracks"_L1) && json_object["tracks"_L1].isObject()) {
json_object = json_object["tracks"_L1].toObject();
}
if (!json_obj.contains("limit"_L1) ||
!json_obj.contains("offset"_L1) ||
!json_obj.contains("total"_L1) ||
!json_obj.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_obj);
SongsFinishCheck(artist, album, limit_requested, offset_requested, 0, 0);
if (!json_object.contains("limit"_L1) ||
!json_object.contains("offset"_L1) ||
!json_object.contains("total"_L1) ||
!json_object.contains("items"_L1)) {
Error(u"Json object missing values."_s, json_object);
return;
}
int offset = json_obj["offset"_L1].toInt();
int songs_total = json_obj["total"_L1].toInt();
const int offset = json_object["offset"_L1].toInt();
songs_total = json_object["total"_L1].toInt();
if (type_ == Type::FavouriteSongs || type_ == Type::SearchSongs) {
songs_total_ = songs_total;
@@ -972,48 +943,45 @@ void SpotifyRequest::SongsReceived(QNetworkReply *reply, const Artist &artist, c
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()) {
if ((type_ == Type::FavouriteSongs || type_ == Type::SearchSongs) && offset_requested == 0) {
no_results_ = true;
}
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) && obj_item["item"_L1].isObject()) {
obj_item = obj_item["item"_L1].toObject();
if (object_item.contains("item"_L1) && object_item["item"_L1].isObject()) {
object_item = object_item["item"_L1].toObject();
}
if (obj_item.contains("track"_L1) && obj_item["track"_L1].isObject()) {
obj_item = obj_item["track"_L1].toObject();
if (object_item.contains("track"_L1) && object_item["track"_L1].isObject()) {
object_item = object_item["track"_L1].toObject();
}
++songs_received;
Song song(Song::Source::Spotify);
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;
@@ -1031,15 +999,13 @@ void SpotifyRequest::SongsReceived(QNetworkReply *reply, const Artist &artist, c
Q_EMIT UpdateProgress(query_id_, GetProgress(songs_received_, songs_total_));
}
SongsFinishCheck(artist, album, limit_requested, offset_requested, songs_total, songs_received);
}
void SpotifyRequest::SongsFinishCheck(const Artist &artist, const Album &album, const int limit, const int offset, const int songs_total, const int songs_received) {
if (finished_) return;
if (limit == 0 || limit > songs_received) {
if (songs_received > 0 && (limit == 0 || limit > songs_received)) {
int offset_next = offset + songs_received;
if (offset_next > 0 && offset_next < songs_total) {
switch (type_) {
@@ -1240,25 +1206,18 @@ void SpotifyRequest::AddAlbumCoverRequest(const Song &song) {
void SpotifyRequest::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;
const AlbumCoverRequest request = album_cover_requests_queue_.dequeue();
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_;
}
}
void SpotifyRequest::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();
}
@@ -1272,24 +1231,23 @@ void SpotifyRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &alb
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;
}
@@ -1300,7 +1258,6 @@ void SpotifyRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &alb
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;
}
@@ -1308,7 +1265,6 @@ void SpotifyRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &alb
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;
}
@@ -1338,8 +1294,6 @@ void SpotifyRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &alb
Error(QStringLiteral("Error decoding image data from %1").arg(url.toString()));
}
AlbumCoverFinishCheck();
}
void SpotifyRequest::AlbumCoverFinishCheck() {
@@ -1373,17 +1327,19 @@ void SpotifyRequest::FinishCheck() {
}
finished_ = true;
if (no_results_ && songs_.isEmpty()) {
if (IsSearch())
if (IsSearch()) {
Q_EMIT Results(query_id_, SongMap(), tr("No match."));
else
}
else {
Q_EMIT Results(query_id_, SongMap(), QString());
}
}
else {
if (songs_.isEmpty() && errors_.isEmpty()) {
if (songs_.isEmpty() && error_.isEmpty()) {
Q_EMIT Results(query_id_, songs_, tr("Data missing error"));
}
else {
Q_EMIT Results(query_id_, songs_, ErrorsToHTML(errors_));
Q_EMIT Results(query_id_, songs_, error_);
}
}
}
@@ -1396,22 +1352,20 @@ int SpotifyRequest::GetProgress(const int count, const int total) {
}
void SpotifyRequest::Error(const QString &error, const QVariant &debug) {
void SpotifyRequest::Error(const QString &error_message, const QVariant &debug_output) {
if (!error.isEmpty()) {
errors_ << error;
qLog(Error) << "Spotify:" << error;
qLog(Error) << "Spotify:" << error_message;
if (debug_output.isValid()) {
qLog(Debug) << debug_output;
}
if (debug.isValid()) qLog(Debug) << debug;
FinishCheck();
error_ = error_message;
}
void SpotifyRequest::Warn(const QString &error, const QVariant &debug) {
void SpotifyRequest::Warn(const QString &error_message, const QVariant &debug) {
qLog(Error) << "Spotify:" << error;
qLog(Error) << "Spotify:" << error_message;
if (debug.isValid()) qLog(Debug) << debug;
}