Add internet tabs view and tidal favorites (#167)
This commit is contained in:
821
src/tidal/tidalrequest.cpp
Normal file
821
src/tidal/tidalrequest.cpp
Normal file
@@ -0,0 +1,821 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "tidalservice.h"
|
||||
#include "tidalurlhandler.h"
|
||||
#include "tidalrequest.h"
|
||||
|
||||
const char *TidalRequest::kResourcesUrl = "http://resources.tidal.com";
|
||||
|
||||
TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler, NetworkAccessManager *network, QueryType type, QObject *parent)
|
||||
: TidalBaseRequest(service, network, parent),
|
||||
service_(service),
|
||||
url_handler_(url_handler),
|
||||
network_(network),
|
||||
type_(type),
|
||||
artist_query_(false),
|
||||
artist_albums_requested_(0),
|
||||
artist_albums_received_(0),
|
||||
album_songs_requested_(0),
|
||||
album_songs_received_(0),
|
||||
album_covers_requested_(0),
|
||||
album_covers_received_(0),
|
||||
need_login_(false),
|
||||
no_match_(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TidalRequest::~TidalRequest() {
|
||||
|
||||
while (!replies_.isEmpty()) {
|
||||
QNetworkReply *reply = replies_.takeFirst();
|
||||
disconnect(reply, 0, nullptr, 0);
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::LoginComplete(bool success, QString error) {
|
||||
|
||||
if (!need_login_) return;
|
||||
need_login_ = false;
|
||||
|
||||
if (!success) {
|
||||
Error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
Process();
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::Process() {
|
||||
|
||||
if (!service_->authenticated()) {
|
||||
need_login_ = true;
|
||||
service_->TryLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type_) {
|
||||
case QueryType::QueryType_Artists:
|
||||
GetArtists();
|
||||
break;
|
||||
case QueryType::QueryType_Albums:
|
||||
GetAlbums();
|
||||
break;
|
||||
case QueryType::QueryType_Songs:
|
||||
GetSongs();
|
||||
break;
|
||||
case QueryType::QueryType_SearchArtists:
|
||||
SendArtistsSearch();
|
||||
break;
|
||||
case QueryType::QueryType_SearchAlbums:
|
||||
SendAlbumsSearch();
|
||||
break;
|
||||
case QueryType::QueryType_SearchSongs:
|
||||
SendSongsSearch();
|
||||
break;
|
||||
default:
|
||||
Error("Invalid query type.");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::Search(const int search_id, const QString &search_text) {
|
||||
search_id_ = search_id;
|
||||
search_text_ = search_text;
|
||||
}
|
||||
|
||||
void TidalRequest::GetArtists() {
|
||||
|
||||
emit UpdateStatus(tr("Retrieving artists..."));
|
||||
|
||||
artist_query_ = true;
|
||||
|
||||
ParamList parameters;
|
||||
QNetworkReply *reply = CreateRequest(QString("users/%1/favorites/artists").arg(service_->user_id()), parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistsReceived(QNetworkReply*)), reply);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::GetAlbums() {
|
||||
|
||||
emit UpdateStatus(tr("Retrieving albums..."));
|
||||
|
||||
type_ = QueryType_Albums;
|
||||
|
||||
if (!service_->authenticated()) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ParamList parameters;
|
||||
QNetworkReply *reply = CreateRequest(QString("users/%1/favorites/albums").arg(service_->user_id()), parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::GetSongs() {
|
||||
|
||||
emit UpdateStatus(tr("Retrieving songs..."));
|
||||
|
||||
type_ = QueryType_Songs;
|
||||
|
||||
if (!service_->authenticated()) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ParamList parameters;
|
||||
QNetworkReply *reply = CreateRequest(QString("users/%1/favorites/tracks").arg(service_->user_id()), parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(SongsReceived(QNetworkReply*, int)), reply, 0);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::SendArtistsSearch() {
|
||||
|
||||
if (!service_->authenticated()) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
artist_query_ = true;
|
||||
|
||||
ParamList parameters;
|
||||
parameters << Param("query", search_text_);
|
||||
parameters << Param("limit", QString::number(service_->artistssearchlimit()));
|
||||
QNetworkReply *reply = CreateRequest("search/artists", parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistsReceived(QNetworkReply*)), reply);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::SendAlbumsSearch() {
|
||||
|
||||
if (!service_->authenticated()) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ParamList parameters;
|
||||
parameters << Param("query", search_text_);
|
||||
parameters << Param("limit", QString::number(service_->albumssearchlimit()));
|
||||
QNetworkReply *reply = CreateRequest("search/albums", parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::SendSongsSearch() {
|
||||
|
||||
if (!service_->authenticated()) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ParamList parameters;
|
||||
parameters << Param("query", search_text_);
|
||||
parameters << Param("limit", QString::number(service_->songssearchlimit()));
|
||||
QNetworkReply *reply = CreateRequest("search/tracks", parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::ArtistsReceived(QNetworkReply *reply) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, true);
|
||||
if (data.isEmpty()) {
|
||||
artist_query_ = false;
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(data, error);
|
||||
if (!json_value.isArray()) {
|
||||
artist_query_ = false;
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
QJsonArray json_items = json_value.toArray();
|
||||
if (json_items.isEmpty()) { // Empty array means no match
|
||||
artist_query_ = false;
|
||||
no_match_ = true;
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QJsonValue &value : json_items) {
|
||||
if (!value.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item not a object.";
|
||||
qLog(Debug) << value;
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
|
||||
if (json_obj.contains("item")) {
|
||||
QJsonValue json_item = json_obj["item"];
|
||||
if (!json_item.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item not a object.";
|
||||
qLog(Debug) << json_item;
|
||||
continue;
|
||||
}
|
||||
json_obj = json_item.toObject();
|
||||
}
|
||||
|
||||
if (!json_obj.contains("id") || !json_obj.contains("name")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item missing id or album.";
|
||||
qLog(Debug) << json_obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
int artist_id = json_obj["id"].toInt();
|
||||
if (requests_artist_albums_.contains(artist_id)) continue;
|
||||
requests_artist_albums_.append(artist_id);
|
||||
GetArtistAlbums(artist_id);
|
||||
artist_albums_requested_++;
|
||||
if (artist_albums_requested_ >= service_->artistssearchlimit()) break;
|
||||
|
||||
}
|
||||
|
||||
if (artist_albums_requested_ > 0) {
|
||||
if (artist_albums_requested_ == 1) emit UpdateStatus(tr("Retrieving albums for %1 artist...").arg(artist_albums_requested_));
|
||||
else emit UpdateStatus(tr("Retrieving albums for %1 artists...").arg(artist_albums_requested_));
|
||||
emit ProgressSetMaximum(artist_albums_requested_);
|
||||
emit UpdateProgress(0);
|
||||
}
|
||||
else {
|
||||
artist_query_ = false;
|
||||
}
|
||||
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::GetArtistAlbums(const int artist_id, const int offset) {
|
||||
|
||||
ParamList parameters;
|
||||
if (offset > 0) parameters << Param("offset", QString::number(offset));
|
||||
QNetworkReply *reply = CreateRequest(QString("artists/%1/albums").arg(artist_id), parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, artist_id, offset);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::AlbumsReceived(QNetworkReply *reply, const int artist_id, const int offset_requested) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, (artist_id == 0));
|
||||
|
||||
if (artist_query_) {
|
||||
if (!requests_artist_albums_.contains(artist_id)) return;
|
||||
artist_albums_received_++;
|
||||
emit UpdateProgress(artist_albums_received_);
|
||||
}
|
||||
|
||||
if (data.isEmpty()) {
|
||||
AlbumsFinished(artist_id, offset_requested);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
if (json_obj.isEmpty()) {
|
||||
AlbumsFinished(artist_id, offset_requested);
|
||||
return;
|
||||
}
|
||||
|
||||
int limit = 0;
|
||||
int total_albums = 0;
|
||||
if (artist_query_) { // This was a list of albums by artist
|
||||
if (!json_obj.contains("limit") ||
|
||||
!json_obj.contains("offset") ||
|
||||
!json_obj.contains("totalNumberOfItems") ||
|
||||
!json_obj.contains("items")) {
|
||||
AlbumsFinished(artist_id, offset_requested);
|
||||
Error("Json object missing values.", json_obj);
|
||||
return;
|
||||
}
|
||||
limit = json_obj["limit"].toInt();
|
||||
int offset = json_obj["offset"].toInt();
|
||||
total_albums = json_obj["totalNumberOfItems"].toInt();
|
||||
if (offset != offset_requested) {
|
||||
AlbumsFinished(artist_id, offset_requested, total_albums, limit);
|
||||
Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj, error);
|
||||
if (!json_value.isArray()) {
|
||||
AlbumsFinished(artist_id, offset_requested, total_albums, limit);
|
||||
return;
|
||||
}
|
||||
QJsonArray json_items = json_value.toArray();
|
||||
if (json_items.isEmpty()) {
|
||||
if (!artist_query_) no_match_ = true;
|
||||
AlbumsFinished(artist_id, offset_requested, total_albums, limit);
|
||||
return;
|
||||
}
|
||||
|
||||
int albums = 0;
|
||||
for (const QJsonValue &value : json_items) {
|
||||
++albums;
|
||||
if (!value.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item not a object.";
|
||||
qLog(Debug) << value;
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
|
||||
if (json_obj.contains("item")) {
|
||||
QJsonValue json_item = json_obj["item"];
|
||||
if (!json_item.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item not a object.";
|
||||
qLog(Debug) << json_item;
|
||||
continue;
|
||||
}
|
||||
json_obj = json_item.toObject();
|
||||
}
|
||||
|
||||
int album_id = 0;
|
||||
QString album;
|
||||
if (json_obj.contains("type")) { // This was a albums request or search
|
||||
if (!json_obj.contains("id") || !json_obj.contains("title")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item is missing ID or title.";
|
||||
qLog(Debug) << json_obj;
|
||||
continue;
|
||||
}
|
||||
album_id = json_obj["id"].toInt();
|
||||
album = json_obj["title"].toString();
|
||||
}
|
||||
else if (json_obj.contains("album")) { // This was a tracks request or search
|
||||
if (!service_->fetchalbums()) {
|
||||
Song song;
|
||||
ParseSong(song, 0, value);
|
||||
songs_ << song;
|
||||
continue;
|
||||
}
|
||||
QJsonValue json_value_album = json_obj["album"];
|
||||
if (!json_value_album.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item album is not a object.";
|
||||
qLog(Debug) << json_value_album;
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_album = json_value_album.toObject();
|
||||
if (!json_album.contains("id") || !json_album.contains("title")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item album is missing ID or title.";
|
||||
qLog(Debug) << json_album;
|
||||
continue;
|
||||
}
|
||||
album_id = json_album["id"].toInt();
|
||||
album = json_album["title"].toString();
|
||||
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item missing type or album.";
|
||||
qLog(Debug) << json_obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (requests_album_songs_.contains(album_id)) continue;
|
||||
|
||||
if (!json_obj.contains("artist") || !json_obj.contains("title") || !json_obj.contains("audioQuality")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item missing artist, title or audioQuality.";
|
||||
qLog(Debug) << json_obj;
|
||||
continue;
|
||||
}
|
||||
QJsonValue json_value_artist = json_obj["artist"];
|
||||
if (!json_value_artist.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item artist is not a object.";
|
||||
qLog(Debug) << json_value_artist;
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_artist = json_value_artist.toObject();
|
||||
if (!json_artist.contains("name")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, item artist missing name.";
|
||||
qLog(Debug) << json_artist;
|
||||
continue;
|
||||
}
|
||||
QString artist = json_artist["name"].toString();
|
||||
|
||||
QString quality = json_obj["audioQuality"].toString();
|
||||
QString copyright = json_obj["copyright"].toString();
|
||||
|
||||
//qLog(Debug) << "Tidal:" << artist << album << quality << copyright;
|
||||
|
||||
requests_album_songs_.insert(album_id, artist);
|
||||
album_songs_requested_++;
|
||||
if (album_songs_requested_ >= service_->albumssearchlimit()) break;
|
||||
}
|
||||
|
||||
AlbumsFinished(artist_id, offset_requested, total_albums, limit, albums);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::AlbumsFinished(const int artist_id, const int offset_requested, const int total_albums, const int limit, const int albums) {
|
||||
|
||||
if (artist_query_) { // This is a artist search.
|
||||
if (albums > limit) {
|
||||
Error("Albums returned does not match limit returned!");
|
||||
}
|
||||
int offset_next = offset_requested + albums;
|
||||
if (album_songs_requested_ < service_->albumssearchlimit() && offset_next < total_albums) {
|
||||
GetArtistAlbums(artist_id, offset_next);
|
||||
artist_albums_requested_++;
|
||||
}
|
||||
else if (artist_albums_received_ >= artist_albums_requested_) { // Artist search is finished.
|
||||
artist_query_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!artist_query_) {
|
||||
// Get songs for the albums.
|
||||
QHashIterator<int, QString> i(requests_album_songs_);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
GetAlbumSongs(i.key());
|
||||
}
|
||||
|
||||
if (album_songs_requested_ > 0) {
|
||||
if (album_songs_requested_ == 1) emit UpdateStatus(tr("Retrieving songs for %1 album...").arg(album_songs_requested_));
|
||||
else emit UpdateStatus(tr("Retrieving songs for %1 albums...").arg(album_songs_requested_));
|
||||
emit ProgressSetMaximum(album_songs_requested_);
|
||||
emit UpdateProgress(0);
|
||||
}
|
||||
}
|
||||
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::GetAlbumSongs(const int album_id) {
|
||||
|
||||
ParamList parameters;
|
||||
QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(album_id), parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(SongsReceived(QNetworkReply*, int)), reply, album_id);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::SongsReceived(QNetworkReply *reply, const int album_id) {
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error, false);
|
||||
|
||||
QString album_artist;
|
||||
if (album_id != 0) {
|
||||
if (!requests_album_songs_.contains(album_id)) return;
|
||||
album_artist = requests_album_songs_[album_id];
|
||||
}
|
||||
|
||||
album_songs_received_++;
|
||||
if (!artist_query_) {
|
||||
emit UpdateProgress(album_songs_received_);
|
||||
}
|
||||
|
||||
if (data.isEmpty()) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(data, error);
|
||||
if (!json_value.isArray()) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray json_items = json_value.toArray();
|
||||
if (json_items.isEmpty()) {
|
||||
no_match_ = true;
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
bool compilation = false;
|
||||
bool multidisc = false;
|
||||
SongList songs;
|
||||
for (const QJsonValue &value : json_items) {
|
||||
Song song;
|
||||
ParseSong(song, album_id, value, album_artist);
|
||||
if (!song.is_valid()) continue;
|
||||
if (song.disc() >= 2) multidisc = true;
|
||||
if (song.is_compilation()) compilation = true;
|
||||
songs << song;
|
||||
}
|
||||
|
||||
for (Song &song : songs) {
|
||||
if (compilation) song.set_compilation_detected(true);
|
||||
if (multidisc) {
|
||||
QString album_full(QString("%1 - (Disc %2)").arg(song.album()).arg(song.disc()));
|
||||
song.set_album(album_full);
|
||||
}
|
||||
songs_ << song;
|
||||
|
||||
}
|
||||
|
||||
if (service_->cache_album_covers() && artist_albums_requested_ <= artist_albums_received_ && album_songs_requested_ <= album_songs_received_) {
|
||||
GetAlbumCovers();
|
||||
}
|
||||
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
int TidalRequest::ParseSong(Song &song, const int album_id_requested, const QJsonValue &value, QString album_artist) {
|
||||
|
||||
if (!value.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, track is not a object.";
|
||||
qLog(Debug) << value;
|
||||
return -1;
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
|
||||
if (
|
||||
!json_obj.contains("album") ||
|
||||
!json_obj.contains("allowStreaming") ||
|
||||
!json_obj.contains("artist") ||
|
||||
!json_obj.contains("artists") ||
|
||||
!json_obj.contains("audioQuality") ||
|
||||
!json_obj.contains("duration") ||
|
||||
!json_obj.contains("id") ||
|
||||
!json_obj.contains("streamReady") ||
|
||||
!json_obj.contains("title") ||
|
||||
!json_obj.contains("trackNumber") ||
|
||||
!json_obj.contains("url") ||
|
||||
!json_obj.contains("volumeNumber") ||
|
||||
!json_obj.contains("copyright")
|
||||
) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, track is missing one or more values.";
|
||||
qLog(Debug) << json_obj;
|
||||
return -1;
|
||||
}
|
||||
|
||||
QJsonValue json_value_artist = json_obj["artist"];
|
||||
QJsonValue json_value_album = json_obj["album"];
|
||||
QJsonValue json_duration = json_obj["duration"];
|
||||
QJsonArray json_artists = json_obj["artists"].toArray();
|
||||
|
||||
int song_id = json_obj["id"].toInt();
|
||||
|
||||
QString title = json_obj["title"].toString();
|
||||
QString urlstr = json_obj["url"].toString();
|
||||
int track = json_obj["trackNumber"].toInt();
|
||||
int disc = json_obj["volumeNumber"].toInt();
|
||||
bool allow_streaming = json_obj["allowStreaming"].toBool();
|
||||
bool stream_ready = json_obj["streamReady"].toBool();
|
||||
QString copyright = json_obj["copyright"].toString();
|
||||
|
||||
if (!json_value_artist.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, track artist is not a object.";
|
||||
qLog(Debug) << json_value_artist;
|
||||
return -1;
|
||||
}
|
||||
QJsonObject json_artist = json_value_artist.toObject();
|
||||
if (!json_artist.contains("name")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, track artist is missing name.";
|
||||
qLog(Debug) << json_artist;
|
||||
return -1;
|
||||
}
|
||||
QString artist = json_artist["name"].toString();
|
||||
|
||||
if (!json_value_album.isObject()) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, track album is not a object.";
|
||||
qLog(Debug) << json_value_album;
|
||||
return -1;
|
||||
}
|
||||
QJsonObject json_album = json_value_album.toObject();
|
||||
if (!json_album.contains("id") || !json_album.contains("title") || !json_album.contains("cover")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, track album is missing id, title or cover.";
|
||||
qLog(Debug) << json_album;
|
||||
return -1;
|
||||
}
|
||||
int album_id = json_album["id"].toInt();
|
||||
if (album_id_requested != 0 && album_id_requested != album_id) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, track album id is wrong.";
|
||||
qLog(Debug) << json_album;
|
||||
return -1;
|
||||
}
|
||||
QString album = json_album["title"].toString();
|
||||
QString cover = json_album["cover"].toString();
|
||||
|
||||
if (!allow_streaming) {
|
||||
qLog(Error) << "Tidal: Song" << artist << album << title << "is not allowStreaming";
|
||||
}
|
||||
|
||||
if (!stream_ready) {
|
||||
qLog(Error) << "Tidal: Song" << artist << album << title << "is not streamReady.";
|
||||
}
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(url_handler_->scheme());
|
||||
url.setPath(QString::number(song_id));
|
||||
|
||||
QVariant q_duration = json_duration.toVariant();
|
||||
quint64 duration = 0;
|
||||
if (q_duration.isValid() && (q_duration.type() == QVariant::Int || q_duration.type() == QVariant::Double)) {
|
||||
duration = q_duration.toInt() * kNsecPerSec;
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Tidal: Invalid duration for song.";
|
||||
qLog(Debug) << json_duration;
|
||||
return -1;
|
||||
}
|
||||
|
||||
cover = cover.replace("-", "/");
|
||||
QUrl cover_url (QString("%1/images/%2/%3.jpg").arg(kResourcesUrl).arg(cover).arg(service_->coversize()));
|
||||
|
||||
title.remove(Song::kTitleRemoveMisc);
|
||||
|
||||
//qLog(Debug) << "id" << song_id << "track" << track << "disc" << disc << "title" << title << "album" << album << "album artist" << album_artist << "artist" << artist << cover << allow_streaming << url;
|
||||
|
||||
song.set_source(Song::Source_Tidal);
|
||||
song.set_album_id(album_id);
|
||||
if (album_artist != artist) song.set_albumartist(album_artist);
|
||||
song.set_album(album);
|
||||
song.set_artist(artist);
|
||||
song.set_title(title);
|
||||
song.set_track(track);
|
||||
song.set_disc(disc);
|
||||
song.set_url(url);
|
||||
song.set_length_nanosec(duration);
|
||||
song.set_art_automatic(cover_url.toEncoded());
|
||||
song.set_comment(copyright);
|
||||
song.set_directory_id(0);
|
||||
song.set_filetype(Song::FileType_Stream);
|
||||
song.set_filesize(0);
|
||||
song.set_mtime(0);
|
||||
song.set_ctime(0);
|
||||
song.set_valid(true);
|
||||
|
||||
return song_id;
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::GetAlbumCovers() {
|
||||
|
||||
for (Song &song : songs_) {
|
||||
GetAlbumCover(song);
|
||||
}
|
||||
|
||||
if (album_covers_requested_ == 1) emit UpdateStatus(tr("Retrieving album cover for %1 album...").arg(album_covers_requested_));
|
||||
else emit UpdateStatus(tr("Retrieving album covers for %1 albums...").arg(album_covers_requested_));
|
||||
emit ProgressSetMaximum(album_covers_requested_);
|
||||
emit UpdateProgress(0);
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::GetAlbumCover(Song &song) {
|
||||
|
||||
if (requests_album_covers_.contains(song.album_id())) {
|
||||
requests_album_covers_.insertMulti(song.album_id(), &song);
|
||||
return;
|
||||
}
|
||||
|
||||
album_covers_requested_++;
|
||||
requests_album_covers_.insertMulti(song.album_id(), &song);
|
||||
|
||||
QUrl url(song.art_automatic());
|
||||
QNetworkRequest req(url);
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumCoverReceived(QNetworkReply*, int, QUrl)), reply, song.album_id(), url);
|
||||
replies_ << reply;
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, int album_id, QUrl url) {
|
||||
|
||||
if (replies_.contains(reply)) {
|
||||
replies_.removeAll(reply);
|
||||
reply->deleteLater();
|
||||
}
|
||||
else {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requests_album_covers_.contains(album_id)) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
album_covers_received_++;
|
||||
emit UpdateProgress(album_covers_received_);
|
||||
|
||||
QString error;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
requests_album_covers_.remove(album_id);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
error = Error(QString("Received empty image data for %1").arg(url.toString()));
|
||||
requests_album_covers_.remove(album_id);
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image;
|
||||
if (image.loadFromData(data)) {
|
||||
|
||||
QDir dir;
|
||||
if (dir.mkpath(service_->CoverCacheDir())) {
|
||||
QString filename(service_->CoverCacheDir() + "/" + QString::number(album_id) + "-" + url.fileName());
|
||||
if (image.save(filename, "JPG")) {
|
||||
while (requests_album_covers_.contains(album_id)) {
|
||||
Song *song = requests_album_covers_.take(album_id);
|
||||
song->set_art_automatic(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
error = Error(QString("Error decoding image data from %1").arg(url.toString()));
|
||||
}
|
||||
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
void TidalRequest::CheckFinish() {
|
||||
|
||||
if (!need_login_ &&
|
||||
!artist_query_ &&
|
||||
artist_albums_requested_ <= artist_albums_received_ &&
|
||||
album_songs_requested_ <= album_songs_received_ &&
|
||||
album_covers_requested_ <= album_covers_received_
|
||||
) {
|
||||
if (songs_.isEmpty()) {
|
||||
if (IsSearch()) {
|
||||
if (no_match_) emit ErrorSignal(search_id_, tr("No match"));
|
||||
else if (errors_.isEmpty()) emit ErrorSignal(search_id_, tr("Unknown error"));
|
||||
else emit ErrorSignal(search_id_, errors_);
|
||||
}
|
||||
else {
|
||||
if (no_match_) emit Results(songs_);
|
||||
else if (errors_.isEmpty()) emit ErrorSignal(tr("Unknown error"));
|
||||
else emit ErrorSignal(errors_);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (IsSearch()) {
|
||||
emit SearchResults(search_id_, songs_);
|
||||
}
|
||||
else {
|
||||
emit Results(songs_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString TidalRequest::Error(QString error, QVariant debug) {
|
||||
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
errors_ += error;
|
||||
errors_ += "<br />";
|
||||
}
|
||||
CheckFinish();
|
||||
|
||||
return error;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user