Add internet tabs view and tidal favorites (#167)

This commit is contained in:
Jonas Kvinge
2019-05-27 21:10:37 +02:00
committed by GitHub
parent c4b732ff93
commit 890fba0f61
45 changed files with 3844 additions and 1081 deletions

View File

@@ -0,0 +1,212 @@
/*
* 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 <QPair>
#include <QList>
#include <QString>
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include "core/logging.h"
#include "core/network.h"
#include "tidalservice.h"
#include "tidalbaserequest.h"
const char *TidalBaseRequest::kApiUrl = "https://api.tidalhifi.com/v1";
const char *TidalBaseRequest::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
TidalBaseRequest::TidalBaseRequest(TidalService *service, NetworkAccessManager *network, QObject *parent) :
QObject(parent),
service_(service),
network_(network)
{}
TidalBaseRequest::~TidalBaseRequest() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
reply->abort();
reply->deleteLater();
}
}
QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, const QList<Param> &params_provided) {
typedef QPair<QByteArray, QByteArray> EncodedParam;
typedef QList<EncodedParam> EncodedParamList;
ParamList params = ParamList() << params_provided
<< Param("sessionId", session_id())
<< Param("countryCode", country_code());
QStringList query_items;
QUrlQuery url_query;
for (const Param& param : params) {
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
query_items << QString(encoded_param.first + "=" + encoded_param.second);
url_query.addQueryItem(encoded_param.first, encoded_param.second);
}
QUrl url(kApiUrl + QString("/") + ressource_name);
url.setQuery(url_query);
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
QNetworkReply *reply = network_->get(req);
replies_ << reply;
//qLog(Debug) << "Tidal: Sending request" << url;
return reply;
}
QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, QString &error, const bool send_login) {
if (replies_.contains(reply)) {
replies_.removeAll(reply);
reply->deleteLater();
}
else {
return QByteArray();
}
QByteArray data;
if (reply->error() == QNetworkReply::NoError) {
data = reply->readAll();
}
else {
if (reply->error() < 200) {
// This is a network error, there is nothing more to do.
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
}
else {
// See if there is Json data containing "userMessage" - then use that instead.
data = reply->readAll();
QJsonParseError parse_error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
int status = 0;
int sub_status = 0;
QString failure_reason;
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
QJsonObject json_obj = json_doc.object();
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("userMessage")) {
status = json_obj["status"].toInt();
sub_status = json_obj["subStatus"].toInt();
QString user_message = json_obj["userMessage"].toString();
failure_reason = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
}
}
if (failure_reason.isEmpty()) {
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
if (status == 401 && sub_status == 6001) { // User does not have a valid session
emit service_->Logout();
if (send_login && login_attempts() < max_login_attempts() && !token().isEmpty() && !username().isEmpty() && !password().isEmpty()) {
qLog(Error) << "Tidal:" << failure_reason;
qLog(Info) << "Tidal:" << "Attempting to login.";
NeedLogin();
emit service_->Login();
}
else {
error = Error(failure_reason);
}
}
else { // Fail
error = Error(failure_reason);
}
}
return QByteArray();
}
return data;
}
QJsonObject TidalBaseRequest::ExtractJsonObj(QByteArray &data, QString &error) {
QJsonParseError json_error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
if (json_error.error != QJsonParseError::NoError) {
error = Error("Reply from server missing Json data.", data);
return QJsonObject();
}
if (json_doc.isNull() || json_doc.isEmpty()) {
error = Error("Received empty Json document.", data);
return QJsonObject();
}
if (!json_doc.isObject()) {
error = Error("Json document is not an object.", json_doc);
return QJsonObject();
}
QJsonObject json_obj = json_doc.object();
if (json_obj.isEmpty()) {
error = Error("Received empty Json object.", json_doc);
return QJsonObject();
}
return json_obj;
}
QJsonValue TidalBaseRequest::ExtractItems(QByteArray &data, QString &error) {
QJsonObject json_obj = ExtractJsonObj(data, error);
if (json_obj.isEmpty()) return QJsonValue();
return ExtractItems(json_obj, error);
}
QJsonValue TidalBaseRequest::ExtractItems(QJsonObject &json_obj, QString &error) {
if (!json_obj.contains("items")) {
error = Error("Json reply is missing items.", json_obj);
return QJsonArray();
}
QJsonValue json_items = json_obj["items"];
return json_items;
}
QString TidalBaseRequest::Error(QString error, QVariant debug) {
qLog(Error) << "Tidal:" << error;
if (debug.isValid()) qLog(Debug) << debug;
return error;
}

View File

@@ -0,0 +1,111 @@
/*
* 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/>.
*
*/
#ifndef TIDALBASEREQUEST_H
#define TIDALBASEREQUEST_H
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QPair>
#include <QString>
#include <QUrl>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include "core/song.h"
#include "internet/internetservices.h"
#include "internet/internetservice.h"
#include "internet/internetsearch.h"
#include "tidalservice.h"
class Application;
class NetworkAccessManager;
class TidalUrlHandler;
class CollectionBackend;
class CollectionModel;
class TidalBaseRequest : public QObject {
Q_OBJECT
public:
enum QueryType {
QueryType_None,
QueryType_Artists,
QueryType_Albums,
QueryType_Songs,
QueryType_SearchArtists,
QueryType_SearchAlbums,
QueryType_SearchSongs,
QueryType_StreamURL,
};
TidalBaseRequest(TidalService *service, NetworkAccessManager *network, QObject *parent);
~TidalBaseRequest();
typedef QPair<QString, QString> Param;
typedef QList<Param> ParamList;
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> &params_provided);
QByteArray GetReplyData(QNetworkReply *reply, QString &error, const bool send_login);
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
QJsonValue ExtractItems(QByteArray &data, QString &error);
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
QString Error(QString error, QVariant debug = QVariant());
QString token() { return service_->token(); }
QString username() { return service_->username(); }
QString password() { return service_->password(); }
QString quality() { return service_->quality(); }
int artistssearchlimit() { return service_->artistssearchlimit(); }
int albumssearchlimit() { return service_->albumssearchlimit(); }
int songssearchlimit() { return service_->songssearchlimit(); }
bool fetchalbums() { return service_->fetchalbums(); }
QString coversize() { return service_->coversize(); }
QString session_id() { return service_->session_id(); }
quint64 user_id() { return service_->user_id(); }
QString country_code() { return service_->country_code(); }
bool authenticated() { return service_->authenticated(); }
bool need_login() { return need_login(); }
bool login_sent() { return service_->login_sent(); }
int max_login_attempts() { return service_->max_login_attempts(); }
int login_attempts() { return service_->login_attempts(); }
virtual void NeedLogin() = 0;
private:
static const char *kApiUrl;
static const char *kApiTokenB64;
TidalService *service_;
NetworkAccessManager *network_;
QList<QNetworkReply*> replies_;
};
#endif // TIDALBASEREQUEST_H

821
src/tidal/tidalrequest.cpp Normal file
View 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;
}

134
src/tidal/tidalrequest.h Normal file
View File

@@ -0,0 +1,134 @@
/*
* 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/>.
*
*/
#ifndef TIDALREQUEST_H
#define TIDALREQUEST_H
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QPair>
#include <QList>
#include <QHash>
#include <QMap>
#include <QMultiMap>
#include <QString>
#include <QUrl>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include "core/song.h"
#include "tidalbaserequest.h"
class NetworkAccessManager;
class TidalService;
class TidalUrlHandler;
class TidalRequest : public TidalBaseRequest {
Q_OBJECT
public:
TidalRequest(TidalService *service, TidalUrlHandler *url_handler, NetworkAccessManager *network, QueryType type, QObject *parent);
~TidalRequest();
void ReloadSettings();
void Process();
void NeedLogin() { need_login_ = true; }
void Search(const int search_id, const QString &search_text);
void SendArtistsSearch();
void SendAlbumsSearch();
void SendSongsSearch();
signals:
void Login();
void Login(const QString &username, const QString &password, const QString &token);
void LoginSuccess();
void LoginFailure(QString failure_reason);
void Results(SongList songs);
void SearchResults(int id, SongList songs);
void ErrorSignal(QString message);
void ErrorSignal(int id, QString message);
void UpdateStatus(QString text);
void ProgressSetMaximum(int max);
void UpdateProgress(int max);
void StreamURLFinished(const QUrl original_url, const QUrl url, const Song::FileType, QString error = QString());
public slots:
void GetArtists();
void GetAlbums();
void GetSongs();
private slots:
void LoginComplete(bool success, QString error = QString());
void ArtistsReceived(QNetworkReply *reply);
void AlbumsReceived(QNetworkReply *reply, const int artist_id, const int offset_requested = 0);
void AlbumsFinished(const int artist_id, const int offset_requested, const int total_albums = 0, const int limit = 0, const int albums = 0);
void SongsReceived(QNetworkReply *reply, int album_id);
void AlbumCoverReceived(QNetworkReply *reply, int album_id, QUrl url);
private:
typedef QPair<QString, QString> Param;
typedef QList<Param> ParamList;
const bool IsSearch() { return (type_ == QueryType_SearchArtists || type_ == QueryType_SearchAlbums || type_ == QueryType_SearchSongs); }
void SendSearch();
void GetArtistAlbums(const int artist_id, const int offset = 0);
void GetAlbumSongs(const int album_id);
void GetSongs(const int album_id);
int ParseSong(Song &song, const int album_id_requested, const QJsonValue &value, QString album_artist = QString());
void GetAlbumCovers();
void GetAlbumCover(Song &song);
void CheckFinish();
QString LoginError(QString error, QVariant debug = QVariant());
QString Error(QString error, QVariant debug = QVariant());
static const char *kResourcesUrl;
TidalService *service_;
TidalUrlHandler *url_handler_;
NetworkAccessManager *network_;
QueryType type_;
bool artist_query_;
int search_id_;
QString search_text_;
QList<int> requests_artist_albums_;
QHash<int, QString> requests_album_songs_;
QMultiMap<int, Song*> requests_album_covers_;
int artist_albums_requested_;
int artist_albums_received_;
int album_songs_requested_;
int album_songs_received_;
int album_covers_requested_;
int album_covers_received_;
SongList songs_;
QString errors_;
bool need_login_;
bool no_match_;
QList<QNetworkReply*> replies_;
};
#endif // TIDALREQUEST_H

File diff suppressed because it is too large Load Diff

View File

@@ -22,27 +22,31 @@
#include "config.h"
#include <memory>
#include <stdbool.h>
#include <QtGlobal>
#include <QObject>
#include <QPair>
#include <QList>
#include <QHash>
#include <QString>
#include <QUrl>
#include <QNetworkReply>
#include <QTimer>
#include <QDateTime>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include "core/song.h"
#include "internet/internetservices.h"
#include "internet/internetservice.h"
#include "internet/internetsearch.h"
class QSortFilterProxyModel;
class Application;
class NetworkAccessManager;
class TidalUrlHandler;
class TidalRequest;
class CollectionBackend;
class CollectionModel;
using std::shared_ptr;
class TidalService : public InternetService {
Q_OBJECT
@@ -54,81 +58,120 @@ class TidalService : public InternetService {
static const Song::Source kSource;
void ReloadSettings();
QString CoverCacheDir();
void Logout();
int Search(const QString &query, InternetSearch::SearchType type);
void CancelSearch();
const bool login_sent() { return login_sent_; }
const bool authenticated() { return (!session_id_.isEmpty() && !country_code_.isEmpty()); }
const int max_login_attempts() { return kLoginAttempts; }
QString token() { return token_; }
QString username() { return username_; }
QString password() { return password_; }
QString quality() { return quality_; }
int search_delay() { return search_delay_; }
int artistssearchlimit() { return artistssearchlimit_; }
int albumssearchlimit() { return albumssearchlimit_; }
int songssearchlimit() { return songssearchlimit_; }
bool fetchalbums() { return fetchalbums_; }
QString coversize() { return coversize_; }
bool cache_album_covers() { return cache_album_covers_; }
QString session_id() { return session_id_; }
quint64 user_id() { return user_id_; }
QString country_code() { return country_code_; }
const bool authenticated() { return (!session_id_.isEmpty() && !country_code_.isEmpty()); }
const bool login_sent() { return login_sent_; }
const bool login_attempts() { return login_attempts_; }
void GetStreamURL(const QUrl &url);
CollectionBackend *artists_collection_backend() { return artists_collection_backend_; }
CollectionBackend *albums_collection_backend() { return albums_collection_backend_; }
CollectionBackend *songs_collection_backend() { return songs_collection_backend_; }
CollectionModel *artists_collection_model() { return artists_collection_model_; }
CollectionModel *albums_collection_model() { return albums_collection_model_; }
CollectionModel *songs_collection_model() { return songs_collection_model_; }
QSortFilterProxyModel *artists_collection_sort_model() { return artists_collection_sort_model_; }
QSortFilterProxyModel *albums_collection_sort_model() { return albums_collection_sort_model_; }
QSortFilterProxyModel *songs_collection_sort_model() { return songs_collection_sort_model_; }
enum QueryType {
QueryType_Artists,
QueryType_Albums,
QueryType_Songs,
QueryType_SearchArtists,
QueryType_SearchAlbums,
QueryType_SearchSongs,
};
signals:
void Login();
void Login(const QString &username, const QString &password, const QString &token);
void LoginSuccess();
void LoginFailure(QString failure_reason);
void SearchResults(int id, SongList songs);
void SearchError(int id, QString message);
void UpdateStatus(QString text);
void ProgressSetMaximum(int max);
void UpdateProgress(int max);
void StreamURLFinished(const QUrl original_url, const QUrl url, const Song::FileType, QString error = QString());
public slots:
void ShowConfig();
void TryLogin();
void SendLogin(const QString &username, const QString &password, const QString &token);
void GetArtists();
void GetAlbums();
void GetSongs();
private slots:
void SendLogin();
void HandleAuthReply(QNetworkReply *reply);
void ResetLoginAttempts();
void StartSearch();
void ArtistsReceived(QNetworkReply *reply, int search_id);
void AlbumsReceived(QNetworkReply *reply, int search_id, int artist_id, int offset_requested = 0);
void AlbumsFinished(const int artist_id, const int offset_requested, const int total_albums = 0, const int limit = 0, const int albums = 0);
void SongsReceived(QNetworkReply *reply, int search_id, int album_id);
void StreamURLReceived(QNetworkReply *reply, const int song_id, const QUrl original_url);
void UpdateArtists(SongList songs);
void UpdateAlbums(SongList songs);
private:
typedef QPair<QString, QString> Param;
typedef QList<Param> ParamList;
void ClearSearch();
void LoadSessionID();
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<QPair<QString, QString>> &params);
QByteArray GetReplyData(QNetworkReply *reply, QString &error, const bool sendlogin = false);
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
QJsonValue ExtractItems(QByteArray &data, QString &error);
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
void SendSearch();
void SendArtistsSearch();
void SendAlbumsSearch();
void SendSongsSearch();
void GetAlbums(const int artist_id, const int offset = 0);
void GetSongs(const int album_id);
Song ParseSong(const int album_id_requested, const QJsonValue &value, QString album_artist = QString());
void CheckFinish();
QString LoginError(QString error, QVariant debug = QVariant());
QString Error(QString error, QVariant debug = QVariant());
static const char *kApiUrl;
static const char *kAuthUrl;
static const char *kResourcesUrl;
static const char *kApiTokenB64;
static const int kLoginAttempts;
static const int kTimeResetLoginAttempts;
static const char *kArtistsSongsTable;
static const char *kAlbumsSongsTable;
static const char *kSongsTable;
static const char *kArtistsSongsFtsTable;
static const char *kAlbumsSongsFtsTable;
static const char *kSongsFtsTable;
Application *app_;
NetworkAccessManager *network_;
TidalUrlHandler *url_handler_;
CollectionBackend *artists_collection_backend_;
CollectionBackend *albums_collection_backend_;
CollectionBackend *songs_collection_backend_;
CollectionModel *artists_collection_model_;
CollectionModel *albums_collection_model_;
CollectionModel *songs_collection_model_;
QSortFilterProxyModel *artists_collection_sort_model_;
QSortFilterProxyModel *albums_collection_sort_model_;
QSortFilterProxyModel *songs_collection_sort_model_;
QTimer *timer_search_delay_;
QTimer *timer_login_attempt_;
std::shared_ptr<TidalRequest> artists_request_;
std::shared_ptr<TidalRequest> albums_request_;
std::shared_ptr<TidalRequest> songs_request_;
std::shared_ptr<TidalRequest> search_request_;
QString token_;
QString username_;
QString password_;
@@ -139,6 +182,8 @@ class TidalService : public InternetService {
int songssearchlimit_;
bool fetchalbums_;
QString coversize_;
bool cache_album_covers_;
QString session_id_;
quint64 user_id_;
QString country_code_;
@@ -150,20 +195,8 @@ class TidalService : public InternetService {
int search_id_;
QString search_text_;
bool artist_search_;
QList<int> requests_artist_albums_;
QHash<int, QString> requests_album_songs_;
QHash<int, QUrl> requests_stream_url_;
QList<QUrl> queue_stream_url_;
int artist_albums_requested_;
int artist_albums_received_;
int album_songs_requested_;
int album_songs_received_;
SongList songs_;
QString search_error_;
bool login_sent_;
int login_attempts_;
QUrl stream_request_url_;
};

View File

@@ -0,0 +1,145 @@
/*
* 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 <QString>
#include <QUrl>
#include <QJsonObject>
#include "core/logging.h"
#include "core/network.h"
#include "core/song.h"
#include "tidalservice.h"
#include "tidalbaserequest.h"
#include "tidalstreamurlrequest.h"
TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent)
: TidalBaseRequest(service, network, parent),
reply_(nullptr),
original_url_(original_url),
song_id_(original_url.path().toInt()),
tries_(0),
need_login_(false) {}
TidalStreamURLRequest::~TidalStreamURLRequest() {
Cancel();
}
void TidalStreamURLRequest::LoginComplete(bool success, QString error) {
if (!need_login_) return;
need_login_ = false;
if (!success) {
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
Process();
}
void TidalStreamURLRequest::Process() {
if (!authenticated()) {
need_login_ = true;
emit TryLogin();
return;
}
GetStreamURL();
}
void TidalStreamURLRequest::Cancel() {
if (reply_) {
if (reply_->isRunning()) {
reply_->abort();
}
reply_->deleteLater();
reply_ = nullptr;
}
}
void TidalStreamURLRequest::GetStreamURL() {
++tries_;
ParamList parameters;
parameters << Param("soundQuality", quality());
if (reply_) {
Cancel();
}
reply_ = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id_), parameters);
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
}
void TidalStreamURLRequest::StreamURLReceived() {
if (!reply_) return;
disconnect(reply_, 0, nullptr, 0);
reply_->deleteLater();
QString error;
QByteArray data = GetReplyData(reply_, error, true);
if (data.isEmpty()) {
reply_ = nullptr;
if (!authenticated() && login_sent() && tries_ <= 1) {
need_login_ = true;
return;
}
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
QJsonObject json_obj = ExtractJsonObj(data, error);
if (json_obj.isEmpty()) {
reply_ = nullptr;
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
if (!json_obj.contains("url") || !json_obj.contains("codec")) {
reply_ = nullptr;
error = Error("Invalid Json reply, stream missing url or codec.", json_obj);
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
QUrl new_url(json_obj["url"].toString());
QString codec(json_obj["codec"].toString().toLower());
Song::FileType filetype(Song::FiletypeByExtension(codec));
if (filetype == Song::FileType_Unknown) {
qLog(Debug) << "Tidal: Unknown codec" << codec;
filetype = Song::FileType_Stream;
}
emit StreamURLFinished(original_url_, new_url, filetype, QString());
reply_ = nullptr;
deleteLater();
}

View File

@@ -0,0 +1,68 @@
/*
* 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/>.
*
*/
#ifndef TIDALSTREAMURLREQUEST_H
#define TIDALSTREAMURLREQUEST_H
#include "config.h"
#include <QString>
#include <QUrl>
#include "core/song.h"
#include "tidalbaserequest.h"
class QNetworkReply;
class NetworkAccessManager;
class TidalService;
class TidalStreamURLRequest : public TidalBaseRequest {
Q_OBJECT
public:
TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent);
~TidalStreamURLRequest();
void GetStreamURL();
void Process();
void NeedLogin() { need_login_ = true; }
void Cancel();
QUrl original_url() { return original_url_; }
int song_id() { return song_id_; }
bool need_login() { return need_login_; }
signals:
void TryLogin();
void StreamURLFinished(const QUrl original_url, const QUrl stream_url, const Song::FileType, QString error = QString());
private slots:
void LoginComplete(bool success, QString error = QString());
void StreamURLReceived();
private:
QNetworkReply *reply_;
QUrl original_url_;
int song_id_;
int tries_;
bool need_login_;
};
#endif // TIDALSTREAMURLREQUEST_H