Tidal: Remove deprecated username/password login

This commit is contained in:
Jonas Kvinge
2025-02-01 22:10:53 +01:00
parent eac5674891
commit ba354207d2
14 changed files with 70 additions and 545 deletions

View File

@@ -99,8 +99,7 @@ bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
req.setHeader(QNetworkRequest::ContentTypeHeader, u"application/x-www-form-urlencoded"_s);
if (service_->oauth() && !service_->access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + service_->access_token().toUtf8());
else if (!service_->session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
if (!service_->access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + service_->access_token().toUtf8());
QNetworkReply *reply = network_->get(req);
replies_ << reply;

View File

@@ -53,10 +53,8 @@ TidalSettingsPage::TidalSettingsPage(SettingsDialog *dialog, SharedPtr<TidalServ
QObject::connect(ui_->button_login, &QPushButton::clicked, this, &TidalSettingsPage::LoginClicked);
QObject::connect(ui_->login_state, &LoginStateWidget::LogoutClicked, this, &TidalSettingsPage::LogoutClicked);
QObject::connect(ui_->oauth, &QCheckBox::toggled, this, &TidalSettingsPage::OAuthClicked);
QObject::connect(this, &TidalSettingsPage::Authorize, &*service_, &TidalService::StartAuthorization);
QObject::connect(this, &TidalSettingsPage::Login, &*service_, &TidalService::SendLoginWithCredentials);
QObject::connect(&*service_, &StreamingService::LoginFailure, this, &TidalSettingsPage::LoginFailure);
QObject::connect(&*service_, &StreamingService::LoginSuccess, this, &TidalSettingsPage::LoginSuccess);
@@ -88,16 +86,7 @@ void TidalSettingsPage::Load() {
Settings s;
s.beginGroup(kSettingsGroup);
ui_->enable->setChecked(s.value(kEnabled, false).toBool());
ui_->oauth->setChecked(s.value(kOAuth, true).toBool());
ui_->client_id->setText(s.value(kClientId).toString());
ui_->api_token->setText(s.value(kApiToken).toString());
ui_->username->setText(s.value(kUsername).toString());
QByteArray password = s.value(kPassword).toByteArray();
if (password.isEmpty()) ui_->password->clear();
else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password)));
ComboBoxLoadFromSettings(s, ui_->quality, QLatin1String(kQuality), u"LOSSLESS"_s);
ui_->searchdelay->setValue(s.value(kSearchDelay, 1500).toInt());
ui_->artistssearchlimit->setValue(s.value("kArtistsSearchLimit", 4).toInt());
@@ -108,11 +97,11 @@ void TidalSettingsPage::Load() {
ComboBoxLoadFromSettings(s, ui_->coversize, QLatin1String(kCoverSize), u"640x640"_s);
ui_->streamurl->setCurrentIndex(ui_->streamurl->findData(s.value(kStreamUrl, static_cast<int>(StreamUrlMethod::StreamUrl)).toInt()));
ui_->checkbox_album_explicit->setChecked(s.value(kAlbumExplicit, false).toBool());
s.endGroup();
OAuthClicked(ui_->oauth->isChecked());
if (service_->authenticated()) ui_->login_state->SetLoggedIn(LoginStateWidget::State::LoggedIn);
if (service_->authenticated()) {
ui_->login_state->SetLoggedIn(LoginStateWidget::State::LoggedIn);
}
Init(ui_->layout_tidalsettingspage->parentWidget());
@@ -125,13 +114,19 @@ void TidalSettingsPage::Save() {
Settings s;
s.beginGroup(kSettingsGroup);
s.setValue(kEnabled, ui_->enable->isChecked());
s.setValue(kOAuth, ui_->oauth->isChecked());
if (s.contains(kOAuth)) {
s.remove(kOAuth);
}
if (s.contains(kApiToken)) {
s.remove(kApiToken);
}
if (s.contains(kUsername)) {
s.remove(kUsername);
}
if (s.contains(kPassword)) {
s.remove(kPassword);
}
s.setValue(kClientId, ui_->client_id->text());
s.setValue(kApiToken, ui_->api_token->text());
s.setValue(kUsername, ui_->username->text());
s.setValue(kPassword, QString::fromUtf8(ui_->password->text().toUtf8().toBase64()));
s.setValue(kQuality, ui_->quality->currentData().toString());
s.setValue(kSearchDelay, ui_->searchdelay->value());
s.setValue(kArtistsSearchLimit, ui_->artistssearchlimit->value());
@@ -148,28 +143,11 @@ void TidalSettingsPage::Save() {
void TidalSettingsPage::LoginClicked() {
if (ui_->oauth->isChecked()) {
if (ui_->client_id->text().isEmpty()) {
QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing Tidal client ID."));
return;
}
Q_EMIT Authorize(ui_->client_id->text());
}
else {
if (ui_->api_token->text().isEmpty()) {
QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing API token."));
return;
}
if (ui_->username->text().isEmpty()) {
QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing username."));
return;
}
if (ui_->password->text().isEmpty()) {
QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing password."));
return;
}
Q_EMIT Login(ui_->api_token->text(), ui_->username->text(), ui_->password->text());
if (ui_->client_id->text().isEmpty()) {
QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing Tidal client ID."));
return;
}
Q_EMIT Authorize(ui_->client_id->text());
ui_->button_login->setEnabled(false);
}
@@ -184,15 +162,6 @@ bool TidalSettingsPage::eventFilter(QObject *object, QEvent *event) {
}
void TidalSettingsPage::OAuthClicked(const bool enabled) {
ui_->client_id->setEnabled(enabled);
ui_->api_token->setEnabled(!enabled);
ui_->username->setEnabled(!enabled);
ui_->password->setEnabled(!enabled);
}
void TidalSettingsPage::LogoutClicked() {
service_->Logout();

View File

@@ -47,10 +47,8 @@ class TidalSettingsPage : public SettingsPage {
Q_SIGNALS:
void Authorize(const QString &client_id);
void Login(const QString &api_token, const QString &username, const QString &password);
private Q_SLOTS:
void OAuthClicked(const bool enabled);
void LoginClicked();
void LogoutClicked();
void LoginSuccess();

View File

@@ -47,13 +47,6 @@
</property>
<layout class="QFormLayout" name="layout_credential_group">
<item row="0" column="0">
<widget class="QCheckBox" name="oauth">
<property name="text">
<string>Use OAuth</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_client_id">
<property name="minimumSize">
<size>
@@ -66,74 +59,13 @@
</property>
</widget>
</item>
<item row="1" column="1">
<item row="0" column="1">
<widget class="QLineEdit" name="client_id">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_api_token">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>API Token</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="api_token">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_username">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="username">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_password">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="password">
<property name="text">
<string notr="true"/>
</property>
<property name="echoMode">
<enum>QLineEdit::EchoMode::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -308,7 +240,7 @@
<item>
<spacer name="spacer_middle">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -323,7 +255,7 @@
<item>
<spacer name="spacer_bottom">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -366,11 +298,7 @@
</customwidgets>
<tabstops>
<tabstop>enable</tabstop>
<tabstop>oauth</tabstop>
<tabstop>client_id</tabstop>
<tabstop>api_token</tabstop>
<tabstop>username</tabstop>
<tabstop>password</tabstop>
<tabstop>button_login</tabstop>
<tabstop>quality</tabstop>
<tabstop>searchdelay</tabstop>
@@ -384,6 +312,8 @@
</tabstops>
<resources>
<include location="../../data/icons.qrc"/>
<include location="../../data/icons.qrc"/>
<include location="../../data/icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -62,8 +62,7 @@ QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, co
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
req.setHeader(QNetworkRequest::ContentTypeHeader, u"application/x-www-form-urlencoded"_s);
if (oauth() && !access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
else if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
QNetworkReply *reply = network_->get(req);
QObject::connect(reply, &QNetworkReply::sslErrors, this, &TidalBaseRequest::HandleSSLErrors);
@@ -82,7 +81,7 @@ void TidalBaseRequest::HandleSSLErrors(const QList<QSslError> &ssl_errors) {
}
QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, const bool send_login) {
QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply) {
QByteArray data;
@@ -121,24 +120,8 @@ QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, const bool send_
}
if (status == 401 && sub_status == 6001) { // User does not have a valid session
service_->Logout();
if (!oauth() && send_login && login_attempts() < max_login_attempts() && !api_token().isEmpty() && !username().isEmpty() && !password().isEmpty()) {
qLog(Error) << "Tidal:" << error;
set_need_login();
if (login_sent()) {
qLog(Info) << "Tidal:" << "Waiting for login.";
}
else {
qLog(Info) << "Tidal:" << "Attempting to login.";
Q_EMIT RequestLogin();
}
}
else {
Error(error);
}
}
else {
Error(error);
}
Error(error);
}
return QByteArray();
}

View File

@@ -64,7 +64,7 @@ class TidalBaseRequest : public QObject {
using ParamList = QList<Param>;
QNetworkReply *CreateRequest(const QString &ressource_name, const ParamList &params_provided);
QByteArray GetReplyData(QNetworkReply *reply, const bool send_login);
QByteArray GetReplyData(QNetworkReply *reply);
QJsonObject ExtractJsonObj(const QByteArray &data);
QJsonValue ExtractItems(const QByteArray &data);
QJsonValue ExtractItems(const QJsonObject &json_obj);
@@ -72,30 +72,15 @@ class TidalBaseRequest : public QObject {
virtual void Error(const QString &error, const QVariant &debug = QVariant()) = 0;
static QString ErrorsToHTML(const QStringList &errors);
bool oauth() const { return service_->oauth(); }
QString client_id() const { return service_->client_id(); }
QString api_token() const { return service_->api_token(); }
quint64 user_id() const { return service_->user_id(); }
QString country_code() const { return service_->country_code(); }
QString username() const { return service_->username(); }
QString password() const { return service_->password(); }
QString quality() const { return service_->quality(); }
int artistssearchlimit() const { return service_->artistssearchlimit(); }
int albumssearchlimit() const { return service_->albumssearchlimit(); }
int songssearchlimit() const { return service_->songssearchlimit(); }
QString access_token() const { return service_->access_token(); }
QString session_id() const { return service_->session_id(); }
bool authenticated() const { return service_->authenticated(); }
bool login_sent() const { return service_->login_sent(); }
int max_login_attempts() const { return service_->max_login_attempts(); }
int login_attempts() const { return service_->login_attempts(); }
virtual void set_need_login() = 0;
Q_SIGNALS:
void RequestLogin();
private Q_SLOTS:
void HandleSSLErrors(const QList<QSslError> &ssl_errors);

View File

@@ -44,8 +44,7 @@ using namespace Qt::Literals::StringLiterals;
TidalFavoriteRequest::TidalFavoriteRequest(TidalService *service, const SharedPtr<NetworkAccessManager> network, QObject *parent)
: TidalBaseRequest(service, network, parent),
service_(service),
network_(network),
need_login_(false) {}
network_(network) {}
TidalFavoriteRequest::~TidalFavoriteRequest() {
@@ -148,8 +147,7 @@ void TidalFavoriteRequest::AddFavoritesRequest(const FavoriteType type, const QS
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
req.setHeader(QNetworkRequest::ContentTypeHeader, u"application/x-www-form-urlencoded"_s);
if (oauth() && !access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
else if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
QNetworkReply *reply = network_->post(req, query);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, type, songs]() { AddFavoritesReply(reply, type, songs); });
@@ -170,7 +168,7 @@ void TidalFavoriteRequest::AddFavoritesReply(QNetworkReply *reply, const Favorit
return;
}
GetReplyData(reply, false);
GetReplyData(reply);
if (reply->error() != QNetworkReply::NoError) {
return;
@@ -258,8 +256,7 @@ void TidalFavoriteRequest::RemoveFavoritesRequest(const FavoriteType type, const
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
req.setHeader(QNetworkRequest::ContentTypeHeader, u"application/x-www-form-urlencoded"_s);
if (oauth() && !access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
else if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
QNetworkReply *reply = network_->deleteResource(req);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, type, songs]() { RemoveFavoritesReply(reply, type, songs); });
replies_ << reply;
@@ -279,7 +276,7 @@ void TidalFavoriteRequest::RemoveFavoritesReply(QNetworkReply *reply, const Favo
return;
}
GetReplyData(reply, false);
GetReplyData(reply);
if (reply->error() != QNetworkReply::NoError) {
return;
}

View File

@@ -43,9 +43,6 @@ class TidalFavoriteRequest : public TidalBaseRequest {
explicit TidalFavoriteRequest(TidalService *service, const SharedPtr<NetworkAccessManager> network, QObject *parent = nullptr);
~TidalFavoriteRequest() override;
bool need_login() const { return need_login_; }
void set_need_login() override { need_login_ = true; }
private:
enum class FavoriteType {
Artists,
@@ -89,7 +86,6 @@ class TidalFavoriteRequest : public TidalBaseRequest {
TidalService *service_;
const SharedPtr<NetworkAccessManager> network_;
QList <QNetworkReply*> replies_;
bool need_login_;
};
#endif // TIDALFAVORITEREQUEST_H

View File

@@ -99,8 +99,7 @@ TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler,
album_songs_received_(0),
album_covers_requests_total_(0),
album_covers_requests_active_(0),
album_covers_requests_received_(0),
need_login_(false) {
album_covers_requests_received_(0) {
timer_flush_requests_->setInterval(kFlushRequestsDelay);
timer_flush_requests_->setSingleShot(false);
@@ -126,29 +125,8 @@ TidalRequest::~TidalRequest() {
}
void TidalRequest::LoginComplete(const bool success, const QString &error) {
if (!need_login_) return;
need_login_ = false;
if (!success) {
Error(error);
return;
}
Process();
}
void TidalRequest::Process() {
if (!service_->authenticated()) {
Q_EMIT UpdateStatus(query_id_, tr("Authenticating..."));
need_login_ = true;
service_->TryLogin();
return;
}
switch (query_type_) {
case Type::FavouriteArtists:
GetArtists();
@@ -417,7 +395,7 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply, (offset_requested == 0));
QByteArray data = GetReplyData(reply);
--artists_requests_active_;
++artists_requests_received_;
@@ -564,7 +542,7 @@ void TidalRequest::AlbumsReplyReceived(QNetworkReply *reply, const int limit_req
--albums_requests_active_;
++albums_requests_received_;
AlbumsReceived(reply, Artist(), limit_requested, offset_requested, offset_requested == 0);
AlbumsReceived(reply, Artist(), limit_requested, offset_requested);
}
@@ -604,18 +582,18 @@ void TidalRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const Artist
--artist_albums_requests_active_;
++artist_albums_requests_received_;
Q_EMIT UpdateProgress(query_id_, GetProgress(artist_albums_requests_received_, artist_albums_requests_total_));
AlbumsReceived(reply, artist, 0, offset_requested, false);
AlbumsReceived(reply, artist, 0, offset_requested);
}
void TidalRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_requested, const int limit_requested, const int offset_requested, const bool auto_login) {
void TidalRequest::AlbumsReceived(QNetworkReply *reply, const Artist &artist_requested, const int limit_requested, const int offset_requested) {
if (!replies_.contains(reply)) return;
replies_.removeAll(reply);
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply, auto_login);
QByteArray data = GetReplyData(reply);
if (finished_) return;
@@ -835,10 +813,10 @@ void TidalRequest::SongsReplyReceived(QNetworkReply *reply, const int limit_requ
--songs_requests_active_;
++songs_requests_received_;
if (query_type_ == Type::SearchSongs && fetchalbums_) {
AlbumsReceived(reply, Artist(), limit_requested, offset_requested, offset_requested == 0);
AlbumsReceived(reply, Artist(), limit_requested, offset_requested);
}
else {
SongsReceived(reply, Artist(), Album(), limit_requested, offset_requested, offset_requested == 0);
SongsReceived(reply, Artist(), Album(), limit_requested, offset_requested);
}
}
@@ -881,18 +859,18 @@ void TidalRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const Artist &a
if (offset_requested == 0) {
Q_EMIT UpdateProgress(query_id_, GetProgress(album_songs_requests_received_, album_songs_requests_total_));
}
SongsReceived(reply, artist, album, 0, offset_requested, false);
SongsReceived(reply, artist, album, 0, offset_requested);
}
void TidalRequest::SongsReceived(QNetworkReply *reply, const Artist &artist, const Album &album, const int limit_requested, const int offset_requested, const bool auto_login) {
void TidalRequest::SongsReceived(QNetworkReply *reply, const Artist &artist, const Album &album, const int limit_requested, const int offset_requested) {
if (!replies_.contains(reply)) return;
replies_.removeAll(reply);
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply, auto_login);
QByteArray data = GetReplyData(reply);
if (finished_) return;
@@ -1339,7 +1317,6 @@ void TidalRequest::FinishCheck() {
if (
!finished_ &&
!need_login_ &&
artists_requests_queue_.isEmpty() &&
albums_requests_queue_.isEmpty() &&
songs_requests_queue_.isEmpty() &&

View File

@@ -58,7 +58,6 @@ class TidalRequest : public TidalBaseRequest {
void ReloadSettings();
void Process();
void set_need_login() override { need_login_ = true; }
void Search(const int query_id, const QString &search_text);
private:
@@ -110,18 +109,15 @@ class TidalRequest : public TidalBaseRequest {
void ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
void AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
void AlbumsReceived(QNetworkReply *reply, const TidalRequest::Artist &artist_requested, const int limit_requested, const int offset_requested, const bool auto_login);
void AlbumsReceived(QNetworkReply *reply, const TidalRequest::Artist &artist_requested, const int limit_requested, const int offset_requested);
void SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
void SongsReceived(QNetworkReply *reply, const TidalRequest::Artist &artist, const TidalRequest::Album &album, const int limit_requested, const int offset_requested, const bool auto_login = false);
void SongsReceived(QNetworkReply *reply, const TidalRequest::Artist &artist, const TidalRequest::Album &album, const int limit_requested, const int offset_requested);
void ArtistAlbumsReplyReceived(QNetworkReply *reply, const TidalRequest::Artist &artist, const int offset_requested);
void AlbumSongsReplyReceived(QNetworkReply *reply, const TidalRequest::Artist &artist, const TidalRequest::Album &album, const int offset_requested);
void AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url, const QString &filename);
public Q_SLOTS:
void LoginComplete(const bool success, const QString &error = QString());
private:
bool IsQuery() const { return (query_type_ == Type::FavouriteArtists || query_type_ == Type::FavouriteAlbums || query_type_ == Type::FavouriteSongs); }
bool IsSearch() const { return (query_type_ == Type::SearchArtists || query_type_ == Type::SearchAlbums || query_type_ == Type::SearchSongs); }
@@ -233,7 +229,6 @@ class TidalRequest : public TidalBaseRequest {
SongMap songs_;
QStringList errors_;
bool need_login_;
QList<QNetworkReply*> replies_;
QList<QNetworkReply*> album_cover_replies_;
};

View File

@@ -75,16 +75,12 @@ const Song::Source TidalService::kSource = Song::Source::Tidal;
const char TidalService::kApiUrl[] = "https://api.tidalhifi.com/v1";
const char TidalService::kResourcesUrl[] = "https://resources.tidal.com";
const int TidalService::kLoginAttempts = 2;
namespace {
constexpr char kOAuthUrl[] = "https://login.tidal.com/authorize";
constexpr char kOAuthAccessTokenUrl[] = "https://login.tidal.com/oauth2/token";
constexpr char kOAuthRedirectUrl[] = "tidal://login/auth";
constexpr char kAuthUrl[] = "https://api.tidalhifi.com/v1/login/username";
constexpr int kTimeResetLoginAttempts = 60000;
constexpr char kArtistsSongsTable[] = "tidal_artists_songs";
constexpr char kAlbumsSongsTable[] = "tidal_albums_songs";
@@ -116,11 +112,9 @@ TidalService::TidalService(const SharedPtr<TaskManager> task_manager,
albums_collection_model_(nullptr),
songs_collection_model_(nullptr),
timer_search_delay_(new QTimer(this)),
timer_login_attempt_(new QTimer(this)),
timer_refresh_login_(new QTimer(this)),
favorite_request_(new TidalFavoriteRequest(this, network_, this)),
enabled_(false),
oauth_(false),
user_id_(0),
artistssearchlimit_(1),
albumssearchlimit_(1),
@@ -135,8 +129,6 @@ TidalService::TidalService(const SharedPtr<TaskManager> task_manager,
next_pending_search_id_(1),
pending_search_type_(StreamingSearchView::SearchType::Artists),
search_id_(0),
login_sent_(false),
login_attempts_(0),
next_stream_url_request_id_(0) {
url_handlers->Register(url_handler_);
@@ -165,16 +157,9 @@ TidalService::TidalService(const SharedPtr<TaskManager> task_manager,
timer_search_delay_->setSingleShot(true);
QObject::connect(timer_search_delay_, &QTimer::timeout, this, &TidalService::StartSearch);
timer_login_attempt_->setSingleShot(true);
timer_login_attempt_->setInterval(kTimeResetLoginAttempts);
QObject::connect(timer_login_attempt_, &QTimer::timeout, this, &TidalService::ResetLoginAttempts);
timer_refresh_login_->setSingleShot(true);
QObject::connect(timer_refresh_login_, &QTimer::timeout, this, &TidalService::RequestNewAccessToken);
QObject::connect(this, &TidalService::RequestLogin, this, &TidalService::SendLogin);
QObject::connect(this, &TidalService::LoginWithCredentials, this, &TidalService::SendLoginWithCredentials);
QObject::connect(this, &TidalService::AddArtists, favorite_request_, &TidalFavoriteRequest::AddArtists);
QObject::connect(this, &TidalService::AddAlbums, favorite_request_, &TidalFavoriteRequest::AddAlbums);
QObject::connect(this, &TidalService::AddSongs, favorite_request_, QOverload<const SongList&>::of(&TidalFavoriteRequest::AddSongs));
@@ -184,8 +169,6 @@ TidalService::TidalService(const SharedPtr<TaskManager> task_manager,
QObject::connect(this, &TidalService::RemoveSongsByList, favorite_request_, QOverload<const SongList&>::of(&TidalFavoriteRequest::RemoveSongs));
QObject::connect(this, &TidalService::RemoveSongsByMap, favorite_request_, QOverload<const SongMap&>::of(&TidalFavoriteRequest::RemoveSongs));
QObject::connect(favorite_request_, &TidalFavoriteRequest::RequestLogin, this, &TidalService::SendLogin);
QObject::connect(favorite_request_, &TidalFavoriteRequest::ArtistsAdded, &*artists_collection_backend_, &CollectionBackend::AddOrUpdateSongs);
QObject::connect(favorite_request_, &TidalFavoriteRequest::AlbumsAdded, &*albums_collection_backend_, &CollectionBackend::AddOrUpdateSongs);
QObject::connect(favorite_request_, &TidalFavoriteRequest::SongsAdded, &*songs_collection_backend_, &CollectionBackend::AddOrUpdateSongs);
@@ -247,7 +230,6 @@ void TidalService::LoadSession() {
country_code_ = s.value(kCountryCode, u"US"_s).toString();
access_token_ = s.value(kAccessToken).toString();
refresh_token_ = s.value(kRefreshToken).toString();
session_id_ = s.value(kSessionId).toString();
expires_in_ = s.value(kExpiresIn).toLongLong();
login_time_ = s.value(kLoginTime).toLongLong();
s.endGroup();
@@ -271,15 +253,7 @@ void TidalService::ReloadSettings() {
s.beginGroup(TidalSettings::kSettingsGroup);
enabled_ = s.value(TidalSettings::kEnabled, false).toBool();
oauth_ = s.value(TidalSettings::kOAuth, true).toBool();
client_id_ = s.value(TidalSettings::kClientId).toString();
api_token_ = s.value(TidalSettings::kApiToken).toString();
username_ = s.value(TidalSettings::kUsername).toString();
QByteArray password = s.value(TidalSettings::kPassword).toByteArray();
if (password.isEmpty()) password_.clear();
else password_ = QString::fromUtf8(QByteArray::fromBase64(password));
quality_ = s.value(TidalSettings::kQuality, u"LOSSLESS"_s).toString();
quint64 search_delay = s.value(TidalSettings::kSearchDelay, 1500).toInt();
artistssearchlimit_ = s.value(TidalSettings::kArtistsSearchLimit, 4).toInt();
@@ -339,7 +313,6 @@ void TidalService::AuthorizationUrlReceived(const QUrl &url) {
}
expires_in_ = url_query.queryItemValue(u"expires_in"_s).toInt();
login_time_ = QDateTime::currentSecsSinceEpoch();
session_id_.clear();
Settings s;
s.beginGroup(TidalSettings::kSettingsGroup);
@@ -382,7 +355,7 @@ void TidalService::RequestAccessToken(const QString &code) {
params << Param(u"redirect_uri"_s, QLatin1String(kOAuthRedirectUrl));
params << Param(u"scope"_s, u"r_usr w_usr"_s);
}
else if (!refresh_token_.isEmpty() && enabled_ && oauth_) {
else if (!refresh_token_.isEmpty() && enabled_) {
params << Param(u"grant_type"_s, u"refresh_token"_s);
params << Param(u"refresh_token"_s, refresh_token_);
}
@@ -501,8 +474,6 @@ void TidalService::AccessTokenRequestFinished(QNetworkReply *reply) {
}
}
session_id_.clear();
Settings s;
s.beginGroup(TidalSettings::kSettingsGroup);
s.setValue(kAccessToken, access_token_);
@@ -512,6 +483,8 @@ void TidalService::AccessTokenRequestFinished(QNetworkReply *reply) {
s.setValue(kCountryCode, country_code_);
s.setValue(kUserId, user_id_);
s.remove(kSessionId);
s.remove(kUserId);
s.remove(kCountryCode);
s.endGroup();
if (expires_in_ > 0) {
@@ -526,153 +499,12 @@ void TidalService::AccessTokenRequestFinished(QNetworkReply *reply) {
}
void TidalService::SendLogin() {
SendLoginWithCredentials(api_token_, username_, password_);
}
void TidalService::SendLoginWithCredentials(const QString &api_token, const QString &username, const QString &password) {
login_sent_ = true;
++login_attempts_;
timer_login_attempt_->start();
timer_refresh_login_->stop();
const ParamList params = ParamList() << Param(u"token"_s, (api_token.isEmpty() ? api_token_ : api_token))
<< Param(u"username"_s, username)
<< Param(u"password"_s, password)
<< Param(u"clientVersion"_s, u"2.2.1--7"_s);
QUrlQuery url_query;
for (const Param &param : params) {
url_query.addQueryItem(QString::fromLatin1(QUrl::toPercentEncoding(param.first)), QString::fromLatin1(QUrl::toPercentEncoding(param.second)));
}
QUrl url(QString::fromLatin1(kAuthUrl));
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::ContentTypeHeader, u"application/x-www-form-urlencoded"_s);
req.setRawHeader("X-Tidal-Token", (api_token.isEmpty() ? api_token_.toUtf8() : api_token.toUtf8()));
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
QNetworkReply *reply = network_->post(req, query);
QObject::connect(reply, &QNetworkReply::sslErrors, this, &TidalService::HandleLoginSSLErrors);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply]() { HandleAuthReply(reply); });
replies_ << reply;
//qLog(Debug) << "Tidal: Sending request" << url << query;
}
void TidalService::HandleAuthReply(QNetworkReply *reply) {
if (!replies_.contains(reply)) return;
replies_.removeAll(reply);
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
login_sent_ = false;
if (reply->error() != QNetworkReply::NoError || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
// This is a network error, there is nothing more to do.
LoginError(QStringLiteral("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
login_errors_.clear();
return;
}
else {
// See if there is Json data containing "status" and "userMessage" - then use that instead.
QByteArray data(reply->readAll());
QJsonParseError json_error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
if (json_error.error == QJsonParseError::NoError && !json_doc.isEmpty() && json_doc.isObject()) {
QJsonObject json_obj = json_doc.object();
if (!json_obj.isEmpty() && json_obj.contains("status"_L1) && json_obj.contains("userMessage"_L1)) {
int status = json_obj["status"_L1].toInt();
int sub_status = json_obj["subStatus"_L1].toInt();
QString user_message = json_obj["userMessage"_L1].toString();
login_errors_ << QStringLiteral("Authentication failure: %1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
}
}
if (login_errors_.isEmpty()) {
if (reply->error() != QNetworkReply::NoError) {
login_errors_ << QStringLiteral("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
else {
login_errors_ << QStringLiteral("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
}
}
LoginError();
login_errors_.clear();
return;
}
}
login_errors_.clear();
const QByteArray data = reply->readAll();
QJsonParseError json_error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
if (json_error.error != QJsonParseError::NoError) {
LoginError(u"Authentication reply from server missing Json data."_s);
return;
}
if (json_doc.isEmpty()) {
LoginError(u"Authentication reply from server has empty Json document."_s);
return;
}
if (!json_doc.isObject()) {
LoginError(u"Authentication reply from server has Json document that is not an object."_s, json_doc);
return;
}
QJsonObject json_obj = json_doc.object();
if (json_obj.isEmpty()) {
LoginError(u"Authentication reply from server has empty Json object."_s, json_doc);
return;
}
if (!json_obj.contains("userId"_L1) || !json_obj.contains("sessionId"_L1) || !json_obj.contains("countryCode"_L1)) {
LoginError(u"Authentication reply from server is missing userId, sessionId or countryCode"_s, json_obj);
return;
}
country_code_ = json_obj["countryCode"_L1].toString();
session_id_ = json_obj["sessionId"_L1].toString();
user_id_ = json_obj["userId"_L1].toInt();
access_token_.clear();
refresh_token_.clear();
Settings s;
s.beginGroup(TidalSettings::kSettingsGroup);
s.remove(kAccessToken);
s.remove(kRefreshToken);
s.remove(kExpiresIn);
s.remove(kLoginTime);
s.setValue(kUserId, user_id_);
s.setValue(kSessionId, session_id_);
s.setValue(kCountryCode, country_code_);
s.endGroup();
qLog(Debug) << "Tidal: Login successful" << "user id" << user_id_ << "session id" << session_id_ << "country code" << country_code_;
login_attempts_ = 0;
timer_login_attempt_->stop();
Q_EMIT LoginComplete(true);
Q_EMIT LoginSuccess();
}
void TidalService::Logout() {
user_id_ = 0;
country_code_.clear();
access_token_.clear();
refresh_token_.clear();
session_id_.clear();
expires_in_ = 0;
login_time_ = 0;
@@ -691,35 +523,6 @@ void TidalService::Logout() {
}
void TidalService::ResetLoginAttempts() {
login_attempts_ = 0;
}
void TidalService::TryLogin() {
if (authenticated() || login_sent_) return;
if (api_token_.isEmpty()) {
Q_EMIT LoginComplete(false, tr("Missing Tidal API token."));
return;
}
if (username_.isEmpty()) {
Q_EMIT LoginComplete(false, tr("Missing Tidal username."));
return;
}
if (password_.isEmpty()) {
Q_EMIT LoginComplete(false, tr("Missing Tidal password."));
return;
}
if (login_attempts_ >= kLoginAttempts) {
Q_EMIT LoginComplete(false, tr("Not authenticated with Tidal and reached maximum number of login attempts."));
return;
}
Q_EMIT RequestLogin();
}
void TidalService::ResetArtistsRequest() {
if (artists_request_) {
@@ -733,25 +536,16 @@ void TidalService::ResetArtistsRequest() {
void TidalService::GetArtists() {
if (!authenticated()) {
if (oauth_) {
Q_EMIT ArtistsResults(SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
Q_EMIT ArtistsResults(SongMap(), tr("Missing Tidal API token, username or password."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
Q_EMIT ArtistsResults(SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
ResetArtistsRequest();
artists_request_.reset(new TidalRequest(this, url_handler_, network_, TidalBaseRequest::Type::FavouriteArtists, this), [](TidalRequest *request) { request->deleteLater(); });
QObject::connect(&*artists_request_, &TidalRequest::RequestLogin, this, &TidalService::SendLogin);
QObject::connect(&*artists_request_, &TidalRequest::Results, this, &TidalService::ArtistsResultsReceived);
QObject::connect(&*artists_request_, &TidalRequest::UpdateStatus, this, &TidalService::ArtistsUpdateStatusReceived);
QObject::connect(&*artists_request_, &TidalRequest::UpdateProgress, this, &TidalService::ArtistsUpdateProgressReceived);
QObject::connect(this, &TidalService::LoginComplete, &*artists_request_, &TidalRequest::LoginComplete);
artists_request_->Process();
@@ -788,25 +582,16 @@ void TidalService::ResetAlbumsRequest() {
void TidalService::GetAlbums() {
if (!authenticated()) {
if (oauth_) {
Q_EMIT AlbumsResults(SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
Q_EMIT AlbumsResults(SongMap(), tr("Missing Tidal API token, username or password."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
Q_EMIT AlbumsResults(SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
ResetAlbumsRequest();
albums_request_.reset(new TidalRequest(this, url_handler_, network_, TidalBaseRequest::Type::FavouriteAlbums, this), [](TidalRequest *request) { request->deleteLater(); });
QObject::connect(&*albums_request_, &TidalRequest::RequestLogin, this, &TidalService::SendLogin);
QObject::connect(&*albums_request_, &TidalRequest::Results, this, &TidalService::AlbumsResultsReceived);
QObject::connect(&*albums_request_, &TidalRequest::UpdateStatus, this, &TidalService::AlbumsUpdateStatusReceived);
QObject::connect(&*albums_request_, &TidalRequest::UpdateProgress, this, &TidalService::AlbumsUpdateProgressReceived);
QObject::connect(this, &TidalService::LoginComplete, &*albums_request_, &TidalRequest::LoginComplete);
albums_request_->Process();
@@ -843,25 +628,16 @@ void TidalService::ResetSongsRequest() {
void TidalService::GetSongs() {
if (!authenticated()) {
if (oauth_) {
Q_EMIT SongsResults(SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
Q_EMIT SongsResults(SongMap(), tr("Missing Tidal API token, username or password."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
Q_EMIT SongsResults(SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
ResetSongsRequest();
songs_request_.reset(new TidalRequest(this, url_handler_, network_, TidalBaseRequest::Type::FavouriteSongs, this), [](TidalRequest *request) { request->deleteLater(); });
QObject::connect(&*songs_request_, &TidalRequest::RequestLogin, this, &TidalService::SendLogin);
QObject::connect(&*songs_request_, &TidalRequest::Results, this, &TidalService::SongsResultsReceived);
QObject::connect(&*songs_request_, &TidalRequest::UpdateStatus, this, &TidalService::SongsUpdateStatusReceived);
QObject::connect(&*songs_request_, &TidalRequest::UpdateProgress, this, &TidalService::SongsUpdateProgressReceived);
QObject::connect(this, &TidalService::LoginComplete, &*songs_request_, &TidalRequest::LoginComplete);
songs_request_->Process();
@@ -906,16 +682,9 @@ int TidalService::Search(const QString &text, StreamingSearchView::SearchType ty
void TidalService::StartSearch() {
if (!authenticated()) {
if (oauth_) {
Q_EMIT SearchResults(pending_search_id_, SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
Q_EMIT SearchResults(pending_search_id_, SongMap(), tr("Missing Tidal API token, username or password."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
Q_EMIT SearchResults(pending_search_id_, SongMap(), tr("Not authenticated with Tidal."));
Q_EMIT OpenSettingsDialog(kSource);
return;
}
search_id_ = pending_search_id_;
@@ -925,8 +694,7 @@ void TidalService::StartSearch() {
}
void TidalService::CancelSearch() {
}
void TidalService::CancelSearch() {}
void TidalService::SendSearch() {
@@ -949,11 +717,9 @@ void TidalService::SendSearch() {
search_request_.reset(new TidalRequest(this, url_handler_, network_, query_type, this), [](TidalRequest *request) { request->deleteLater(); });
QObject::connect(&*search_request_, &TidalRequest::RequestLogin, this, &TidalService::SendLogin);
QObject::connect(&*search_request_, &TidalRequest::Results, this, &TidalService::SearchResultsReceived);
QObject::connect(&*search_request_, &TidalRequest::UpdateStatus, this, &TidalService::SearchUpdateStatus);
QObject::connect(&*search_request_, &TidalRequest::UpdateProgress, this, &TidalService::SearchUpdateProgress);
QObject::connect(this, &TidalService::LoginComplete, &*search_request_, &TidalRequest::LoginComplete);
search_request_->Search(search_id_, search_text_);
search_request_->Process();
@@ -970,26 +736,18 @@ void TidalService::SearchResultsReceived(const int id, const SongMap &songs, con
uint TidalService::GetStreamURL(const QUrl &url, QString &error) {
if (!authenticated()) {
if (oauth_) {
error = tr("Not authenticated with Tidal.");
return 0;
}
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
error = tr("Missing Tidal API token, username or password.");
return 0;
}
error = tr("Not authenticated with Tidal.");
return 0;
}
uint id = 0;
while (id == 0) id = ++next_stream_url_request_id_;
SharedPtr<TidalStreamURLRequest> stream_url_req;
SharedPtr<TidalStreamURLRequest> stream_url_req;
stream_url_req.reset(new TidalStreamURLRequest(this, network_, url, id), [](TidalStreamURLRequest *request) { request->deleteLater(); });
stream_url_requests_.insert(id, stream_url_req);
QObject::connect(&*stream_url_req, &TidalStreamURLRequest::TryLogin, this, &TidalService::TryLogin);
QObject::connect(&*stream_url_req, &TidalStreamURLRequest::StreamURLFailure, this, &TidalService::HandleStreamURLFailure);
QObject::connect(&*stream_url_req, &TidalStreamURLRequest::StreamURLSuccess, this, &TidalService::HandleStreamURLSuccess);
QObject::connect(this, &TidalService::LoginComplete, &*stream_url_req, &TidalStreamURLRequest::LoginComplete);
stream_url_req->Process();

View File

@@ -74,7 +74,6 @@ class TidalService : public StreamingService {
static const Song::Source kSource;
static const char kApiUrl[];
static const char kResourcesUrl[];
static const int kLoginAttempts;
void Exit() override;
void ReloadSettings() override;
@@ -83,15 +82,9 @@ class TidalService : public StreamingService {
int Search(const QString &text, StreamingSearchView::SearchType type) override;
void CancelSearch() override;
int max_login_attempts() const { return kLoginAttempts; }
bool oauth() const override { return oauth_; }
QString client_id() const { return client_id_; }
QString api_token() const { return api_token_; }
quint64 user_id() const { return user_id_; }
QString country_code() const { return country_code_; }
QString username() const { return username_; }
QString password() const { return password_; }
QString quality() const { return quality_; }
int artistssearchlimit() const { return artistssearchlimit_; }
int albumssearchlimit() const { return albumssearchlimit_; }
@@ -103,11 +96,8 @@ class TidalService : public StreamingService {
bool album_explicit() const { return album_explicit_; }
QString access_token() const { return access_token_; }
QString session_id() const { return session_id_; }
bool authenticated() const override { return (!access_token_.isEmpty() || !session_id_.isEmpty()); }
bool login_sent() const { return login_sent_; }
bool login_attempts() const { return login_attempts_; }
bool authenticated() const override { return !access_token_.isEmpty(); }
uint GetStreamURL(const QUrl &url, QString &error);
@@ -125,9 +115,6 @@ class TidalService : public StreamingService {
public Q_SLOTS:
void StartAuthorization(const QString &client_id);
void TryLogin();
void SendLogin();
void SendLoginWithCredentials(const QString &api_token, const QString &username, const QString &password);
void GetArtists() override;
void GetAlbums() override;
void GetSongs() override;
@@ -141,8 +128,6 @@ class TidalService : public StreamingService {
void RequestNewAccessToken() { RequestAccessToken(); }
void HandleLoginSSLErrors(const QList<QSslError> &ssl_errors);
void AccessTokenRequestFinished(QNetworkReply *reply);
void HandleAuthReply(QNetworkReply *reply);
void ResetLoginAttempts();
void StartSearch();
void ArtistsResultsReceived(const int id, const SongMap &songs, const QString &error);
void AlbumsResultsReceived(const int id, const SongMap &songs, const QString &error);
@@ -178,7 +163,6 @@ class TidalService : public StreamingService {
CollectionModel *songs_collection_model_;
QTimer *timer_search_delay_;
QTimer *timer_login_attempt_;
QTimer *timer_refresh_login_;
SharedPtr<TidalRequest> artists_request_;
@@ -188,13 +172,9 @@ class TidalService : public StreamingService {
TidalFavoriteRequest *favorite_request_;
bool enabled_;
bool oauth_;
QString client_id_;
QString api_token_;
quint64 user_id_;
QString country_code_;
QString username_;
QString password_;
QString quality_;
int artistssearchlimit_;
int albumssearchlimit_;
@@ -207,7 +187,6 @@ class TidalService : public StreamingService {
QString access_token_;
QString refresh_token_;
QString session_id_;
quint64 expires_in_;
quint64 login_time_;
@@ -218,8 +197,6 @@ class TidalService : public StreamingService {
int search_id_;
QString search_text_;
bool login_sent_;
int login_attempts_;
QString code_verifier_;
QString code_challenge_;

View File

@@ -50,9 +50,7 @@ TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, const Shared
reply_(nullptr),
media_url_(media_url),
id_(id),
song_id_(media_url.path().toInt()),
tries_(0),
need_login_(false) {}
song_id_(media_url.path().toInt()) {}
TidalStreamURLRequest::~TidalStreamURLRequest() {
@@ -64,33 +62,10 @@ TidalStreamURLRequest::~TidalStreamURLRequest() {
}
void TidalStreamURLRequest::LoginComplete(const bool success, const QString &error) {
if (!need_login_) return;
need_login_ = false;
if (!success) {
Q_EMIT StreamURLFailure(id_, media_url_, error);
return;
}
Process();
}
void TidalStreamURLRequest::Process() {
if (!authenticated()) {
if (oauth()) {
Q_EMIT StreamURLFailure(id_, media_url_, tr("Not authenticated with Tidal."));
return;
}
else if (api_token().isEmpty() || username().isEmpty() || password().isEmpty()) {
Q_EMIT StreamURLFailure(id_, media_url_, tr("Missing Tidal API token, username or password."));
return;
}
need_login_ = true;
Q_EMIT TryLogin();
Q_EMIT StreamURLFailure(id_, media_url_, tr("Not authenticated with Tidal."));
return;
}
@@ -111,8 +86,6 @@ void TidalStreamURLRequest::Cancel() {
void TidalStreamURLRequest::GetStreamURL() {
++tries_;
if (reply_) {
QObject::disconnect(reply_, nullptr, this, nullptr);
if (reply_->isRunning()) reply_->abort();
@@ -150,17 +123,13 @@ void TidalStreamURLRequest::StreamURLReceived() {
if (!reply_) return;
QByteArray data = GetReplyData(reply_, true);
QByteArray data = GetReplyData(reply_);
QObject::disconnect(reply_, nullptr, this, nullptr);
reply_->deleteLater();
reply_ = nullptr;
if (data.isEmpty()) {
if (!authenticated() && login_sent() && tries_ <= 1) {
need_login_ = true;
return;
}
Q_EMIT StreamURLFailure(id_, media_url_, errors_.constFirst());
return;
}

View File

@@ -54,20 +54,13 @@ class TidalStreamURLRequest : public TidalBaseRequest {
QUrl media_url() const { return media_url_; }
int song_id() const { return song_id_; }
void set_need_login() override { need_login_ = true; }
bool need_login() const { return need_login_; }
Q_SIGNALS:
void TryLogin();
void StreamURLFailure(const uint id, const QUrl &media_url, const QString &error);
void StreamURLSuccess(const uint id, const QUrl &media_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate = -1, const int bit_depth = -1, const qint64 duration = -1);
private Q_SLOTS:
void StreamURLReceived();
public Q_SLOTS:
void LoginComplete(const bool success, const QString &error = QString());
private:
void Error(const QString &error, const QVariant &debug = QVariant()) override;
@@ -76,7 +69,6 @@ class TidalStreamURLRequest : public TidalBaseRequest {
QUrl media_url_;
uint id_;
int song_id_;
int tries_;
bool need_login_;
QStringList errors_;
};