Improve URL handler, return error for encrypted Tidal streams

This commit is contained in:
Jonas Kvinge
2021-11-08 20:25:22 +01:00
parent fd85763fb4
commit 01f8129ed0
13 changed files with 218 additions and 108 deletions

View File

@@ -178,10 +178,16 @@ QobuzService::QobuzService(Application *app, QObject *parent)
QobuzService::~QobuzService() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
QObject::disconnect(reply, nullptr, this, nullptr);
reply->abort();
reply->deleteLater();
}
while (!stream_url_requests_.isEmpty()) {
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = stream_url_requests_.take(stream_url_requests_.firstKey());
QObject::disconnect(stream_url_req.get(), nullptr, this, nullptr);
stream_url_req->deleteLater();
}
artists_collection_backend_->deleteLater();
@@ -714,31 +720,44 @@ void QobuzService::SearchResultsReceived(const int id, const SongMap &songs, con
emit SearchResults(id, songs, error);
}
void QobuzService::GetStreamURL(const QUrl &url) {
uint QobuzService::GetStreamURL(const QUrl &url, QString &error) {
if (app_id().isEmpty() || app_secret().isEmpty()) { // Don't check for login here, because we allow automatic login.
emit StreamURLFinished(url, url, Song::FileType_Stream, -1, -1, -1, tr("Missing Qobuz app ID or secret."));
return;
error = tr("Missing Qobuz app ID or secret.");
return 0;
}
const int id = ++next_stream_url_request_id_;
uint id = 0;
while (id == 0) id = ++next_stream_url_request_id_;
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = std::make_shared<QobuzStreamURLRequest>(this, network_, url, id);
stream_url_requests_.insert(id, stream_url_req);
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::TryLogin, this, &QobuzService::TryLogin);
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::StreamURLFinished, this, &QobuzService::HandleStreamURLFinished);
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::StreamURLFailure, this, &QobuzService::HandleStreamURLFailure);
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::StreamURLSuccess, this, &QobuzService::HandleStreamURLSuccess);
QObject::connect(this, &QobuzService::LoginComplete, stream_url_req.get(), &QobuzStreamURLRequest::LoginComplete);
stream_url_req->Process();
return id;
}
void QobuzService::HandleStreamURLFinished(const int id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error) {
void QobuzService::HandleStreamURLFailure(const uint id, const QUrl &original_url, const QString &error) {
if (!stream_url_requests_.contains(id)) return;
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = stream_url_requests_.take(id);
emit StreamURLFinished(original_url, stream_url, filetype, samplerate, bit_depth, duration, error);
emit StreamURLFailure(id, original_url, error);
}
void QobuzService::HandleStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration) {
if (!stream_url_requests_.contains(id)) return;
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = stream_url_requests_.take(id);
emit StreamURLSuccess(id, original_url, stream_url, filetype, samplerate, bit_depth, duration);
}

View File

@@ -93,7 +93,7 @@ class QobuzService : public InternetService {
bool login_sent() { return login_sent_; }
bool login_attempts() { return login_attempts_; }
void GetStreamURL(const QUrl &url);
uint GetStreamURL(const QUrl &url, QString &error);
CollectionBackend *artists_collection_backend() override { return artists_collection_backend_; }
CollectionBackend *albums_collection_backend() override { return albums_collection_backend_; }
@@ -138,7 +138,8 @@ class QobuzService : public InternetService {
void ArtistsUpdateProgressReceived(const int id, const int progress);
void AlbumsUpdateProgressReceived(const int id, const int progress);
void SongsUpdateProgressReceived(const int id, const int progress);
void HandleStreamURLFinished(const int id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error);
void HandleStreamURLFailure(const uint id, const QUrl &original_url, const QString &error);
void HandleStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration);
private:
typedef QPair<QString, QString> Param;
@@ -211,8 +212,8 @@ class QobuzService : public InternetService {
bool login_sent_;
int login_attempts_;
int next_stream_url_request_id_;
QMap<int, std::shared_ptr<QobuzStreamURLRequest>> stream_url_requests_;
uint next_stream_url_request_id_;
QMap<uint, std::shared_ptr<QobuzStreamURLRequest>> stream_url_requests_;
QStringList login_errors_;

View File

@@ -45,7 +45,7 @@
#include "qobuzbaserequest.h"
#include "qobuzstreamurlrequest.h"
QobuzStreamURLRequest::QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const int id, QObject *parent)
QobuzStreamURLRequest::QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const uint id, QObject *parent)
: QobuzBaseRequest(service, network, parent),
service_(service),
reply_(nullptr),
@@ -71,7 +71,7 @@ void QobuzStreamURLRequest::LoginComplete(const bool success, const QString &err
need_login_ = false;
if (!success) {
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
emit StreamURLFailure(id_, original_url_, error);
return;
}
@@ -82,7 +82,7 @@ void QobuzStreamURLRequest::LoginComplete(const bool success, const QString &err
void QobuzStreamURLRequest::Process() {
if (app_id().isEmpty() || app_secret().isEmpty()) {
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Missing Qobuz app ID or secret."));
emit StreamURLFailure(id_, original_url_, tr("Missing Qobuz app ID or secret."));
return;
}
@@ -101,7 +101,7 @@ void QobuzStreamURLRequest::Cancel() {
reply_->abort();
}
else {
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Cancelled."));
emit StreamURLFailure(id_, original_url_, tr("Cancelled."));
}
}
@@ -172,32 +172,32 @@ void QobuzStreamURLRequest::StreamURLReceived() {
need_login_ = true;
return;
}
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
emit StreamURLFailure(id_, original_url_, errors_.first());
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
emit StreamURLFailure(id_, original_url_, errors_.first());
return;
}
if (!json_obj.contains("track_id")) {
Error("Invalid Json reply, stream url is missing track_id.", json_obj);
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
emit StreamURLFailure(id_, original_url_, errors_.first());
return;
}
int track_id = json_obj["track_id"].toInt();
if (track_id != song_id_) {
Error("Incorrect track ID returned.", json_obj);
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
emit StreamURLFailure(id_, original_url_, errors_.first());
return;
}
if (!json_obj.contains("mime_type") || !json_obj.contains("url")) {
Error("Invalid Json reply, stream url is missing url or mime_type.", json_obj);
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
emit StreamURLFailure(id_, original_url_, errors_.first());
return;
}
@@ -218,7 +218,7 @@ void QobuzStreamURLRequest::StreamURLReceived() {
if (!url.isValid()) {
Error("Returned stream url is invalid.", json_obj);
emit StreamURLFinished(id_, original_url_, original_url_, filetype, -1, -1, -1, errors_.first());
emit StreamURLFailure(id_, original_url_, errors_.first());
return;
}
@@ -235,7 +235,7 @@ void QobuzStreamURLRequest::StreamURLReceived() {
bit_depth = static_cast<int>(json_obj["bit_depth"].toDouble());
}
emit StreamURLFinished(id_, original_url_, url, filetype, samplerate, bit_depth, duration);
emit StreamURLSuccess(id_, original_url_, url, filetype, samplerate, bit_depth, duration);
}

View File

@@ -40,7 +40,7 @@ class QobuzStreamURLRequest : public QobuzBaseRequest {
Q_OBJECT
public:
explicit QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const int id, QObject *parent = nullptr);
explicit QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const uint id, QObject *parent = nullptr);
~QobuzStreamURLRequest();
void GetStreamURL();
@@ -54,7 +54,8 @@ class QobuzStreamURLRequest : public QobuzBaseRequest {
signals:
void TryLogin();
void StreamURLFinished(int id, QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration, QString error = QString());
void StreamURLFailure(uint id, QUrl original_url, QString error);
void StreamURLSuccess(uint id, QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration);
private slots:
void StreamURLReceived();
@@ -68,7 +69,7 @@ class QobuzStreamURLRequest : public QobuzBaseRequest {
QobuzService *service_;
QNetworkReply *reply_;
QUrl original_url_;
int id_;
uint id_;
int song_id_;
int tries_;
bool need_login_;

View File

@@ -17,6 +17,8 @@
*
*/
#include "config.h"
#include <QObject>
#include <QString>
#include <QUrl>
@@ -30,38 +32,53 @@
QobuzUrlHandler::QobuzUrlHandler(Application *app, QobuzService *service)
: UrlHandler(service),
app_(app),
service_(service),
task_id_(-1) {
service_(service) {
QObject::connect(service, &QobuzService::StreamURLFinished, this, &QobuzUrlHandler::GetStreamURLFinished);
QObject::connect(service, &QobuzService::StreamURLFailure, this, &QobuzUrlHandler::GetStreamURLFailure);
QObject::connect(service, &QobuzService::StreamURLSuccess, this, &QobuzUrlHandler::GetStreamURLSuccess);
}
UrlHandler::LoadResult QobuzUrlHandler::StartLoading(const QUrl &url) {
Request req;
req.task_id = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
QString error;
req.id = service_->GetStreamURL(url, error);
if (req.id == 0) {
CancelTask(req.task_id);
return LoadResult(url, LoadResult::Error, error);
}
requests_.insert(req.id, req);
LoadResult ret(url);
if (task_id_ != -1) return ret;
task_id_ = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
service_->GetStreamURL(url);
ret.type_ = LoadResult::WillLoadAsynchronously;
return ret;
}
void QobuzUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error) {
void QobuzUrlHandler::GetStreamURLFailure(const uint id, const QUrl &original_url, const QString &error) {
if (task_id_ == -1) return;
CancelTask();
if (error.isEmpty()) {
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
}
else {
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, error));
}
if (!requests_.contains(id)) return;
Request req = requests_.take(id);
CancelTask(req.task_id);
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, error));
}
void QobuzUrlHandler::CancelTask() {
app_->task_manager()->SetTaskFinished(task_id_);
task_id_ = -1;
void QobuzUrlHandler::GetStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration) {
if (!requests_.contains(id)) return;
Request req = requests_.take(id);
CancelTask(req.task_id);
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
}
void QobuzUrlHandler::CancelTask(const int task_id) {
app_->task_manager()->SetTaskFinished(task_id);
}

View File

@@ -22,6 +22,7 @@
#include <QtGlobal>
#include <QObject>
#include <QMap>
#include <QString>
#include <QUrl>
@@ -40,15 +41,22 @@ class QobuzUrlHandler : public UrlHandler {
QString scheme() const { return service_->url_scheme(); }
LoadResult StartLoading(const QUrl &url);
void CancelTask();
private:
void CancelTask(const int task_id);
private slots:
void GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error = QString());
void GetStreamURLFailure(const uint id, const QUrl &original_url, const QString &error);
void GetStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration);
private:
struct Request {
Request() : id(0), task_id(-1) {}
uint id;
int task_id;
};
Application *app_;
QobuzService *service_;
int task_id_;
QMap<uint, Request> requests_;
};