Skip to content

Commit

Permalink
Improve URL handler, return error for encrypted Tidal streams
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaski committed Nov 8, 2021
1 parent fd85763 commit 01f8129
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 107 deletions.
3 changes: 2 additions & 1 deletion src/internet/internetservice.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ class InternetService : public QObject {
void RemoveSongs(SongList songs);
void RemoveSongs(SongMap songs);

void StreamURLFinished(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);

protected:
Application *app_;
Expand Down
35 changes: 27 additions & 8 deletions src/qobuz/qobuzservice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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::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 StreamURLFailure(id, original_url, error);

}

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::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 StreamURLFinished(original_url, stream_url, filetype, samplerate, bit_depth, duration, error);
emit StreamURLSuccess(id, original_url, stream_url, filetype, samplerate, bit_depth, duration);

}

Expand Down
9 changes: 5 additions & 4 deletions src/qobuz/qobuzservice.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_; }
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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_;

Expand Down
22 changes: 11 additions & 11 deletions src/qobuz/qobuzstreamurlrequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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."));
}

}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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);

}

Expand Down
7 changes: 4 additions & 3 deletions src/qobuz/qobuzstreamurlrequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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_;
Expand Down
53 changes: 35 additions & 18 deletions src/qobuz/qobuzurlhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*
*/

#include "config.h"

#include <QObject>
#include <QString>
#include <QUrl>
Expand All @@ -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::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() {
app_->task_manager()->SetTaskFinished(task_id_);
task_id_ = -1;
void QobuzUrlHandler::CancelTask(const int task_id) {
app_->task_manager()->SetTaskFinished(task_id);
}
14 changes: 11 additions & 3 deletions src/qobuz/qobuzurlhandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <QtGlobal>
#include <QObject>
#include <QMap>
#include <QString>
#include <QUrl>

Expand All @@ -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_;

};

Expand Down
Loading

0 comments on commit 01f8129

Please sign in to comment.