Add optional oauth authentication for tidal
This commit is contained in:
parent
85a0748ad9
commit
c0c1457073
@ -135,6 +135,7 @@
|
|||||||
#include "settings/backendsettingspage.h"
|
#include "settings/backendsettingspage.h"
|
||||||
#include "settings/playlistsettingspage.h"
|
#include "settings/playlistsettingspage.h"
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
|
# include "tidal/tidalservice.h"
|
||||||
# include "settings/tidalsettingspage.h"
|
# include "settings/tidalsettingspage.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -548,6 +549,10 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||||||
connect(tidal_view_->albums_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
connect(tidal_view_->albums_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
connect(tidal_view_->songs_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
connect(tidal_view_->songs_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
connect(tidal_view_->search_view(), SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
connect(tidal_view_->search_view(), SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
TidalService *tidalservice = qobject_cast<TidalService*> (app_->internet_services()->ServiceBySource(Song::Source_Tidal));
|
||||||
|
if (tidalservice)
|
||||||
|
connect(this, SIGNAL(AuthorisationUrlReceived(const QUrl&)), tidalservice, SLOT(AuthorisationUrlReceived(const QUrl&)));
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Playlist menu
|
// Playlist menu
|
||||||
@ -1797,6 +1802,7 @@ void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByt
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||||
|
|
||||||
switch (options.player_action()) {
|
switch (options.player_action()) {
|
||||||
case CommandlineOptions::Player_Play:
|
case CommandlineOptions::Player_Play:
|
||||||
if (options.urls().empty()) {
|
if (options.urls().empty()) {
|
||||||
@ -1830,6 +1836,15 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!options.urls().empty()) {
|
if (!options.urls().empty()) {
|
||||||
|
|
||||||
|
#ifdef HAVE_TIDAL
|
||||||
|
for (const QUrl url : options.urls()) {
|
||||||
|
if (url.scheme() == "tidal" && url.host() == "login") {
|
||||||
|
emit AuthorisationUrlReceived(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
MimeData *data = new MimeData;
|
MimeData *data = new MimeData;
|
||||||
data->setUrls(options.urls());
|
data->setUrls(options.urls());
|
||||||
// Behaviour depends on command line options, so set it here
|
// Behaviour depends on command line options, so set it here
|
||||||
|
@ -131,6 +131,8 @@ signals:
|
|||||||
|
|
||||||
void IntroPointReached();
|
void IntroPointReached();
|
||||||
|
|
||||||
|
void AuthorisationUrlReceived(const QUrl &url);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void FilePathChanged(const QString& path);
|
void FilePathChanged(const QString& path);
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ class InternetService : public QObject {
|
|||||||
virtual void InitialLoadSettings() {}
|
virtual void InitialLoadSettings() {}
|
||||||
virtual void ReloadSettings() {}
|
virtual void ReloadSettings() {}
|
||||||
virtual QIcon Icon() { return Song::IconForSource(source_); }
|
virtual QIcon Icon() { return Song::IconForSource(source_); }
|
||||||
|
virtual const bool oauth() = 0;
|
||||||
|
virtual const bool authenticated() = 0;
|
||||||
virtual int Search(const QString &query, InternetSearch::SearchType type) = 0;
|
virtual int Search(const QString &query, InternetSearch::SearchType type) = 0;
|
||||||
virtual void CancelSearch() = 0;
|
virtual void CancelSearch() = 0;
|
||||||
|
|
||||||
|
@ -185,6 +185,11 @@ void InternetTabsView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
|
|
||||||
void InternetTabsView::GetArtists() {
|
void InternetTabsView::GetArtists() {
|
||||||
|
|
||||||
|
if (!service_->authenticated() && service_->oauth()) {
|
||||||
|
service_->ShowConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ui_->artists_collection->status()->clear();
|
ui_->artists_collection->status()->clear();
|
||||||
ui_->artists_collection->progressbar()->show();
|
ui_->artists_collection->progressbar()->show();
|
||||||
ui_->artists_collection->button_abort()->show();
|
ui_->artists_collection->button_abort()->show();
|
||||||
@ -224,6 +229,11 @@ void InternetTabsView::ArtistsFinished(SongList songs) {
|
|||||||
|
|
||||||
void InternetTabsView::GetAlbums() {
|
void InternetTabsView::GetAlbums() {
|
||||||
|
|
||||||
|
if (!service_->authenticated() && service_->oauth()) {
|
||||||
|
service_->ShowConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ui_->albums_collection->status()->clear();
|
ui_->albums_collection->status()->clear();
|
||||||
ui_->albums_collection->progressbar()->show();
|
ui_->albums_collection->progressbar()->show();
|
||||||
ui_->albums_collection->button_abort()->show();
|
ui_->albums_collection->button_abort()->show();
|
||||||
@ -263,6 +273,11 @@ void InternetTabsView::AlbumsFinished(SongList songs) {
|
|||||||
|
|
||||||
void InternetTabsView::GetSongs() {
|
void InternetTabsView::GetSongs() {
|
||||||
|
|
||||||
|
if (!service_->authenticated() && service_->oauth()) {
|
||||||
|
service_->ShowConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ui_->songs_collection->status()->clear();
|
ui_->songs_collection->status()->clear();
|
||||||
ui_->songs_collection->progressbar()->show();
|
ui_->songs_collection->progressbar()->show();
|
||||||
ui_->songs_collection->button_abort()->show();
|
ui_->songs_collection->button_abort()->show();
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "internet/internetservices.h"
|
#include "internet/internetservices.h"
|
||||||
#include "tidal/tidalservice.h"
|
#include "tidal/tidalservice.h"
|
||||||
|
#include "tidal/tidalstreamurlrequest.h"
|
||||||
|
|
||||||
const char *TidalSettingsPage::kSettingsGroup = "Tidal";
|
const char *TidalSettingsPage::kSettingsGroup = "Tidal";
|
||||||
|
|
||||||
@ -44,7 +45,9 @@ TidalSettingsPage::TidalSettingsPage(SettingsDialog *parent)
|
|||||||
|
|
||||||
connect(ui_->button_login, SIGNAL(clicked()), SLOT(LoginClicked()));
|
connect(ui_->button_login, SIGNAL(clicked()), SLOT(LoginClicked()));
|
||||||
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked()));
|
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked()));
|
||||||
|
connect(ui_->oauth, SIGNAL(toggled(bool)), SLOT(OAuthClicked(bool)));
|
||||||
|
|
||||||
|
connect(this, SIGNAL(Login()), service_, SLOT(StartAuthorisation()));
|
||||||
connect(this, SIGNAL(Login(QString, QString, QString)), service_, SLOT(SendLogin(QString, QString, QString)));
|
connect(this, SIGNAL(Login(QString, QString, QString)), service_, SLOT(SendLogin(QString, QString, QString)));
|
||||||
|
|
||||||
connect(service_, SIGNAL(LoginFailure(QString)), SLOT(LoginFailure(QString)));
|
connect(service_, SIGNAL(LoginFailure(QString)), SLOT(LoginFailure(QString)));
|
||||||
@ -63,6 +66,10 @@ TidalSettingsPage::TidalSettingsPage(SettingsDialog *parent)
|
|||||||
ui_->coversize->addItem("750x750", "750x750");
|
ui_->coversize->addItem("750x750", "750x750");
|
||||||
ui_->coversize->addItem("1280x1280", "1280x1280");
|
ui_->coversize->addItem("1280x1280", "1280x1280");
|
||||||
|
|
||||||
|
ui_->streamurl->addItem("streamurl", StreamUrlMethod_StreamUrl);
|
||||||
|
ui_->streamurl->addItem("urlpostpaywall", StreamUrlMethod_UrlPostPaywall);
|
||||||
|
ui_->streamurl->addItem("playbackinfopostpaywall", StreamUrlMethod_PlaybackInfoPostPaywall);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TidalSettingsPage::~TidalSettingsPage() { delete ui_; }
|
TidalSettingsPage::~TidalSettingsPage() { delete ui_; }
|
||||||
@ -72,12 +79,19 @@ void TidalSettingsPage::Load() {
|
|||||||
QSettings s;
|
QSettings s;
|
||||||
|
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
ui_->checkbox_enable->setChecked(s.value("enabled", false).toBool());
|
ui_->enable->setChecked(s.value("enabled", false).toBool());
|
||||||
|
ui_->oauth->setChecked(s.value("oauth", false).toBool());
|
||||||
|
|
||||||
|
ui_->client_id->setText(s.value("client_id").toString());
|
||||||
|
ui_->api_token->setText(s.value("api_token").toString());
|
||||||
|
ui_->user_id->setText(s.value("user_id").toString());
|
||||||
|
ui_->country_code->setText(s.value("country_code").toString());
|
||||||
|
|
||||||
ui_->username->setText(s.value("username").toString());
|
ui_->username->setText(s.value("username").toString());
|
||||||
QByteArray password = s.value("password").toByteArray();
|
QByteArray password = s.value("password").toByteArray();
|
||||||
if (password.isEmpty()) ui_->password->clear();
|
if (password.isEmpty()) ui_->password->clear();
|
||||||
else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password)));
|
else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password)));
|
||||||
ui_->token->setText(s.value("token").toString());
|
|
||||||
dialog()->ComboBoxLoadFromSettings(s, ui_->quality, "quality", "HIGH");
|
dialog()->ComboBoxLoadFromSettings(s, ui_->quality, "quality", "HIGH");
|
||||||
ui_->searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
ui_->searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
||||||
ui_->artistssearchlimit->setValue(s.value("artistssearchlimit", 5).toInt());
|
ui_->artistssearchlimit->setValue(s.value("artistssearchlimit", 5).toInt());
|
||||||
@ -86,6 +100,12 @@ void TidalSettingsPage::Load() {
|
|||||||
ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool());
|
ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool());
|
||||||
ui_->checkbox_cache_album_covers->setChecked(s.value("cachealbumcovers", true).toBool());
|
ui_->checkbox_cache_album_covers->setChecked(s.value("cachealbumcovers", true).toBool());
|
||||||
dialog()->ComboBoxLoadFromSettings(s, ui_->coversize, "coversize", "320x320");
|
dialog()->ComboBoxLoadFromSettings(s, ui_->coversize, "coversize", "320x320");
|
||||||
|
|
||||||
|
StreamUrlMethod stream_url = static_cast<StreamUrlMethod>(s.value("streamurl").toInt());
|
||||||
|
int i = ui_->streamurl->findData(stream_url);
|
||||||
|
if (i == -1) i = ui_->streamurl->findData(StreamUrlMethod_StreamUrl);
|
||||||
|
ui_->streamurl->setCurrentIndex(i);
|
||||||
|
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
if (service_->authenticated()) ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
|
if (service_->authenticated()) ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
|
||||||
@ -96,10 +116,16 @@ void TidalSettingsPage::Save() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
s.setValue("enabled", ui_->checkbox_enable->isChecked());
|
s.setValue("enabled", ui_->enable->isChecked());
|
||||||
|
s.setValue("oauth", ui_->oauth->isChecked());
|
||||||
|
s.setValue("client_id", ui_->client_id->text());
|
||||||
|
s.setValue("api_token", ui_->api_token->text());
|
||||||
|
s.setValue("user_id", ui_->user_id->text());
|
||||||
|
s.setValue("country_code", ui_->country_code->text());
|
||||||
|
|
||||||
s.setValue("username", ui_->username->text());
|
s.setValue("username", ui_->username->text());
|
||||||
s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64()));
|
s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64()));
|
||||||
s.setValue("token", ui_->token->text());
|
|
||||||
s.setValue("quality", ui_->quality->itemData(ui_->quality->currentIndex()));
|
s.setValue("quality", ui_->quality->itemData(ui_->quality->currentIndex()));
|
||||||
s.setValue("searchdelay", ui_->searchdelay->value());
|
s.setValue("searchdelay", ui_->searchdelay->value());
|
||||||
s.setValue("artistssearchlimit", ui_->artistssearchlimit->value());
|
s.setValue("artistssearchlimit", ui_->artistssearchlimit->value());
|
||||||
@ -108,6 +134,7 @@ void TidalSettingsPage::Save() {
|
|||||||
s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked());
|
s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked());
|
||||||
s.setValue("cachealbumcovers", ui_->checkbox_cache_album_covers->isChecked());
|
s.setValue("cachealbumcovers", ui_->checkbox_cache_album_covers->isChecked());
|
||||||
s.setValue("coversize", ui_->coversize->itemData(ui_->coversize->currentIndex()));
|
s.setValue("coversize", ui_->coversize->itemData(ui_->coversize->currentIndex()));
|
||||||
|
s.setValue("streamurl", ui_->streamurl->itemData(ui_->streamurl->currentIndex()));
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
service_->ReloadSettings();
|
service_->ReloadSettings();
|
||||||
@ -115,8 +142,19 @@ void TidalSettingsPage::Save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TidalSettingsPage::LoginClicked() {
|
void TidalSettingsPage::LoginClicked() {
|
||||||
emit Login(ui_->username->text(), ui_->password->text(), ui_->token->text());
|
|
||||||
|
if (ui_->oauth->isChecked()) {
|
||||||
|
emit Login();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ui_->username->text().isEmpty() || ui_->password->text().isEmpty()) {
|
||||||
|
QMessageBox::critical(this, tr("Configuration incomplete"), tr("Missing username or password."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit Login(ui_->username->text(), ui_->password->text(), ui_->api_token->text());
|
||||||
|
}
|
||||||
ui_->button_login->setEnabled(false);
|
ui_->button_login->setEnabled(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TidalSettingsPage::eventFilter(QObject *object, QEvent *event) {
|
bool TidalSettingsPage::eventFilter(QObject *object, QEvent *event) {
|
||||||
@ -127,6 +165,16 @@ bool TidalSettingsPage::eventFilter(QObject *object, QEvent *event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return SettingsPage::eventFilter(object, event);
|
return SettingsPage::eventFilter(object, event);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalSettingsPage::OAuthClicked(bool enabled) {
|
||||||
|
|
||||||
|
ui_->client_id->setEnabled(enabled);
|
||||||
|
ui_->api_token->setEnabled(!enabled);
|
||||||
|
ui_->username->setEnabled(!enabled);
|
||||||
|
ui_->password->setEnabled(!enabled);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalSettingsPage::LogoutClicked() {
|
void TidalSettingsPage::LogoutClicked() {
|
||||||
|
@ -38,15 +38,23 @@ class TidalSettingsPage : public SettingsPage {
|
|||||||
|
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
|
|
||||||
|
enum StreamUrlMethod {
|
||||||
|
StreamUrlMethod_StreamUrl,
|
||||||
|
StreamUrlMethod_UrlPostPaywall,
|
||||||
|
StreamUrlMethod_PlaybackInfoPostPaywall,
|
||||||
|
};
|
||||||
|
|
||||||
void Load();
|
void Load();
|
||||||
void Save();
|
void Save();
|
||||||
|
|
||||||
bool eventFilter(QObject *object, QEvent *event);
|
bool eventFilter(QObject *object, QEvent *event);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void Login();
|
||||||
void Login(const QString &username, const QString &password, const QString &token);
|
void Login(const QString &username, const QString &password, const QString &token);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void OAuthClicked(bool enabled);
|
||||||
void LoginClicked();
|
void LoginClicked();
|
||||||
void LogoutClicked();
|
void LogoutClicked();
|
||||||
void LoginSuccess();
|
void LoginSuccess();
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>715</width>
|
<width>715</width>
|
||||||
<height>650</height>
|
<height>794</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -15,7 +15,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="layout_tidalsettingspage">
|
<layout class="QVBoxLayout" name="layout_tidalsettingspage">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkbox_enable">
|
<widget class="QCheckBox" name="enable">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enable</string>
|
<string>Enable</string>
|
||||||
</property>
|
</property>
|
||||||
@ -37,47 +37,113 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Account details</string>
|
<string>Authentication</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="layout_credential_group">
|
<layout class="QFormLayout" name="layout_credential_group">
|
||||||
<item row="0" column="0">
|
<item row="5" column="0">
|
||||||
<widget class="QLabel" name="label_username">
|
<widget class="QLabel" name="label_username">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Tidal username</string>
|
<string>Username</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="5" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="username"/>
|
<widget class="QLineEdit" name="username"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="button_login">
|
|
||||||
<property name="text">
|
|
||||||
<string>Login</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QLabel" name="label_password">
|
<widget class="QLabel" name="label_password">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Tidal password</string>
|
<string>Password</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="QLineEdit" name="password">
|
<widget class="QLineEdit" name="password">
|
||||||
<property name="echoMode">
|
<property name="echoMode">
|
||||||
<enum>QLineEdit::Password</enum>
|
<enum>QLineEdit::Password</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_client_id">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>150</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Client ID</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="client_id"/>
|
||||||
|
</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>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QCheckBox" name="oauth">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use OAuth</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_user_id">
|
||||||
|
<property name="text">
|
||||||
|
<string>User ID</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLineEdit" name="user_id"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_country_code">
|
||||||
|
<property name="text">
|
||||||
|
<string>Country Code</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLineEdit" name="country_code"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="button_login">
|
||||||
|
<property name="text">
|
||||||
|
<string>Login</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="LoginStateWidget" name="login_state" native="true"/>
|
<widget class="LoginStateWidget" name="login_state" native="true"/>
|
||||||
</item>
|
</item>
|
||||||
@ -93,96 +159,25 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Preferences</string>
|
<string>Preferences</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="layout_preferences">
|
<layout class="QFormLayout" name="layout_preferences">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<layout class="QHBoxLayout" name="layout_token">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_token">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>150</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Token</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="token">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>200</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="spacer_token">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="layout_quality">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_quality">
|
<widget class="QLabel" name="label_quality">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>150</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Audio quality</string>
|
<string>Audio quality</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="quality"/>
|
<widget class="QComboBox" name="quality"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="0">
|
||||||
<spacer name="spacer_quality">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="layout_searchdelay">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_searchdelay">
|
<widget class="QLabel" name="label_searchdelay">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>150</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Search delay</string>
|
<string>Search delay</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="1">
|
||||||
<widget class="QSpinBox" name="searchdelay">
|
<widget class="QSpinBox" name="searchdelay">
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string>ms</string>
|
<string>ms</string>
|
||||||
@ -201,37 +196,14 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="2" column="0">
|
||||||
<spacer name="spacer_searchdelay">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="layout_artistssearchlimit">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_artistssearchlimit">
|
<widget class="QLabel" name="label_artistssearchlimit">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>150</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Artists search limit</string>
|
<string>Artists search limit</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="2" column="1">
|
||||||
<widget class="QSpinBox" name="artistssearchlimit">
|
<widget class="QSpinBox" name="artistssearchlimit">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>1</number>
|
<number>1</number>
|
||||||
@ -244,37 +216,14 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="3" column="0">
|
||||||
<spacer name="spacer_artistssearchlimit">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="layout_albumssearchlimit">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_albumssearchlimit">
|
<widget class="QLabel" name="label_albumssearchlimit">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>150</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Albums search limit</string>
|
<string>Albums search limit</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="3" column="1">
|
||||||
<widget class="QSpinBox" name="albumssearchlimit">
|
<widget class="QSpinBox" name="albumssearchlimit">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>1</number>
|
<number>1</number>
|
||||||
@ -287,37 +236,38 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="4" column="0">
|
||||||
<spacer name="spacer_albumssearchlimit">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="layout_songssearchlimit">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_songssearchlimit">
|
<widget class="QLabel" name="label_songssearchlimit">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>150</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Songs search limit</string>
|
<string>Songs search limit</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="5" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkbox_cache_album_covers">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cache album covers</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkbox_fetchalbums">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fetch entire albums when searching songs</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="QComboBox" name="coversize"/>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="label_coversize">
|
||||||
|
<property name="text">
|
||||||
|
<string>Album cover size</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
<widget class="QSpinBox" name="songssearchlimit">
|
<widget class="QSpinBox" name="songssearchlimit">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>1</number>
|
<number>1</number>
|
||||||
@ -330,67 +280,15 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="8" column="0">
|
||||||
<spacer name="spacer_songssearchlimit">
|
<widget class="QLabel" name="label_streamurl">
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkbox_fetchalbums">
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Fetch entire albums when searching songs</string>
|
<string>Stream URL method</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="8" column="1">
|
||||||
<widget class="QCheckBox" name="checkbox_cache_album_covers">
|
<widget class="QComboBox" name="streamurl"/>
|
||||||
<property name="text">
|
|
||||||
<string>Cache album covers</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="layout_coversize">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_coversize">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>150</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Album cover size</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QComboBox" name="coversize"/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="spacer_coversize">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@ -457,7 +355,6 @@
|
|||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>username</tabstop>
|
<tabstop>username</tabstop>
|
||||||
<tabstop>password</tabstop>
|
<tabstop>password</tabstop>
|
||||||
<tabstop>button_login</tabstop>
|
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../data/data.qrc"/>
|
<include location="../../data/data.qrc"/>
|
||||||
|
@ -40,7 +40,6 @@
|
|||||||
#include "tidalbaserequest.h"
|
#include "tidalbaserequest.h"
|
||||||
|
|
||||||
const char *TidalBaseRequest::kApiUrl = "https://api.tidalhifi.com/v1";
|
const char *TidalBaseRequest::kApiUrl = "https://api.tidalhifi.com/v1";
|
||||||
const char *TidalBaseRequest::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
|
|
||||||
|
|
||||||
TidalBaseRequest::TidalBaseRequest(TidalService *service, NetworkAccessManager *network, QObject *parent) :
|
TidalBaseRequest::TidalBaseRequest(TidalService *service, NetworkAccessManager *network, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
@ -53,7 +52,7 @@ TidalBaseRequest::~TidalBaseRequest() {
|
|||||||
while (!replies_.isEmpty()) {
|
while (!replies_.isEmpty()) {
|
||||||
QNetworkReply *reply = replies_.takeFirst();
|
QNetworkReply *reply = replies_.takeFirst();
|
||||||
disconnect(reply, 0, nullptr, 0);
|
disconnect(reply, 0, nullptr, 0);
|
||||||
reply->abort();
|
if (reply->isRunning()) reply->abort();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,11 +60,7 @@ TidalBaseRequest::~TidalBaseRequest() {
|
|||||||
|
|
||||||
QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_provided) {
|
QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_provided) {
|
||||||
|
|
||||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
|
||||||
typedef QList<EncodedParam> EncodedParamList;
|
|
||||||
|
|
||||||
ParamList params = ParamList() << params_provided
|
ParamList params = ParamList() << params_provided
|
||||||
<< Param("sessionId", session_id())
|
|
||||||
<< Param("countryCode", country_code());
|
<< Param("countryCode", country_code());
|
||||||
|
|
||||||
QStringList query_items;
|
QStringList query_items;
|
||||||
@ -80,7 +75,9 @@ QNetworkReply *TidalBaseRequest::CreateRequest(const QString &ressource_name, co
|
|||||||
url.setQuery(url_query);
|
url.setQuery(url_query);
|
||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
|
if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
|
||||||
|
if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
|
||||||
|
|
||||||
QNetworkReply *reply = network_->get(req);
|
QNetworkReply *reply = network_->get(req);
|
||||||
replies_ << reply;
|
replies_ << reply;
|
||||||
|
|
||||||
@ -129,7 +126,7 @@ QByteArray TidalBaseRequest::GetReplyData(QNetworkReply *reply, QString &error,
|
|||||||
}
|
}
|
||||||
if (status == 401 && sub_status == 6001) { // User does not have a valid session
|
if (status == 401 && sub_status == 6001) { // User does not have a valid session
|
||||||
emit service_->Logout();
|
emit service_->Logout();
|
||||||
if (send_login && login_attempts() < max_login_attempts() && !token().isEmpty() && !username().isEmpty() && !password().isEmpty()) {
|
if (!oauth() && send_login && login_attempts() < max_login_attempts() && !api_token().isEmpty() && !username().isEmpty() && !password().isEmpty()) {
|
||||||
qLog(Error) << "Tidal:" << failure_reason;
|
qLog(Error) << "Tidal:" << failure_reason;
|
||||||
qLog(Info) << "Tidal:" << "Attempting to login.";
|
qLog(Info) << "Tidal:" << "Attempting to login.";
|
||||||
NeedLogin();
|
NeedLogin();
|
||||||
|
@ -67,16 +67,23 @@ class TidalBaseRequest : public QObject {
|
|||||||
typedef QPair<QString, QString> Param;
|
typedef QPair<QString, QString> Param;
|
||||||
typedef QList<Param> ParamList;
|
typedef QList<Param> ParamList;
|
||||||
|
|
||||||
|
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||||
|
typedef QList<EncodedParam> EncodedParamList;
|
||||||
|
|
||||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error, const bool send_login);
|
QByteArray GetReplyData(QNetworkReply *reply, QString &error, const bool send_login);
|
||||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||||
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
||||||
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
|
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
|
||||||
|
|
||||||
QString Error(QString error, QVariant debug = QVariant());
|
virtual QString Error(QString error, QVariant debug = QVariant());
|
||||||
|
|
||||||
QString api_url() { return QString(kApiUrl); }
|
QString api_url() { return QString(kApiUrl); }
|
||||||
QString token() { return service_->token(); }
|
const bool oauth() { return service_->oauth(); }
|
||||||
|
QString client_id() { return service_->client_id(); }
|
||||||
|
QString api_token() { return service_->api_token(); }
|
||||||
|
quint64 user_id() { return service_->user_id(); }
|
||||||
|
QString country_code() { return service_->country_code(); }
|
||||||
QString username() { return service_->username(); }
|
QString username() { return service_->username(); }
|
||||||
QString password() { return service_->password(); }
|
QString password() { return service_->password(); }
|
||||||
QString quality() { return service_->quality(); }
|
QString quality() { return service_->quality(); }
|
||||||
@ -86,9 +93,8 @@ class TidalBaseRequest : public QObject {
|
|||||||
bool fetchalbums() { return service_->fetchalbums(); }
|
bool fetchalbums() { return service_->fetchalbums(); }
|
||||||
QString coversize() { return service_->coversize(); }
|
QString coversize() { return service_->coversize(); }
|
||||||
|
|
||||||
|
QString access_token() { return service_->access_token(); }
|
||||||
QString session_id() { return service_->session_id(); }
|
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 authenticated() { return service_->authenticated(); }
|
||||||
bool need_login() { return need_login(); }
|
bool need_login() { return need_login(); }
|
||||||
@ -101,7 +107,6 @@ class TidalBaseRequest : public QObject {
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
static const char *kApiUrl;
|
static const char *kApiUrl;
|
||||||
static const char *kApiTokenB64;
|
|
||||||
|
|
||||||
TidalService *service_;
|
TidalService *service_;
|
||||||
NetworkAccessManager *network_;
|
NetworkAccessManager *network_;
|
||||||
|
@ -143,7 +143,8 @@ void TidalFavoriteRequest::AddFavorites(const FavoriteType type, const SongList
|
|||||||
QUrl url(api_url() + QString("/") + "users/" + QString::number(service_->user_id()) + "/favorites/" + FavoriteText(type));
|
QUrl url(api_url() + QString("/") + "users/" + QString::number(service_->user_id()) + "/favorites/" + FavoriteText(type));
|
||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
|
if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
|
||||||
|
if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
|
||||||
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
|
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
|
||||||
QNetworkReply *reply = network_->post(req, query);
|
QNetworkReply *reply = network_->post(req, query);
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AddFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(AddFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs);
|
||||||
@ -233,9 +234,6 @@ void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongLi
|
|||||||
|
|
||||||
void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const int id, const SongList &songs) {
|
void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const int id, const SongList &songs) {
|
||||||
|
|
||||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
|
||||||
typedef QList<EncodedParam> EncodedParamList;
|
|
||||||
|
|
||||||
ParamList params = ParamList() << Param("countryCode", country_code());
|
ParamList params = ParamList() << Param("countryCode", country_code());
|
||||||
|
|
||||||
QStringList query_items;
|
QStringList query_items;
|
||||||
@ -250,7 +248,8 @@ void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const int id
|
|||||||
url.setQuery(url_query);
|
url.setQuery(url_query);
|
||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
|
if (!access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + access_token().toUtf8());
|
||||||
|
if (!session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", session_id().toUtf8());
|
||||||
QNetworkReply *reply = network_->deleteResource(req);
|
QNetworkReply *reply = network_->deleteResource(req);
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoveFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoveFavoritesReply(QNetworkReply*, const FavoriteType, const SongList&)), reply, type, songs);
|
||||||
replies_ << reply;
|
replies_ << reply;
|
||||||
|
@ -54,6 +54,7 @@ TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler,
|
|||||||
network_(network),
|
network_(network),
|
||||||
type_(type),
|
type_(type),
|
||||||
search_id_(-1),
|
search_id_(-1),
|
||||||
|
finished_(false),
|
||||||
artists_requests_active_(0),
|
artists_requests_active_(0),
|
||||||
artists_total_(0),
|
artists_total_(0),
|
||||||
artists_received_(0),
|
artists_received_(0),
|
||||||
@ -73,10 +74,10 @@ TidalRequest::TidalRequest(TidalService *service, TidalUrlHandler *url_handler,
|
|||||||
|
|
||||||
TidalRequest::~TidalRequest() {
|
TidalRequest::~TidalRequest() {
|
||||||
|
|
||||||
while (!replies_.isEmpty()) {
|
while (!album_cover_replies_.isEmpty()) {
|
||||||
QNetworkReply *reply = replies_.takeFirst();
|
QNetworkReply *reply = album_cover_replies_.takeFirst();
|
||||||
disconnect(reply, 0, nullptr, 0);
|
disconnect(reply, 0, nullptr, 0);
|
||||||
reply->abort();
|
if (reply->isRunning()) reply->abort();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,6 +311,8 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
|
|||||||
|
|
||||||
--artists_requests_active_;
|
--artists_requests_active_;
|
||||||
|
|
||||||
|
if (finished_) return;
|
||||||
|
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
ArtistsFinishCheck();
|
ArtistsFinishCheck();
|
||||||
return;
|
return;
|
||||||
@ -406,6 +409,8 @@ void TidalRequest::ArtistsReplyReceived(QNetworkReply *reply, const int limit_re
|
|||||||
|
|
||||||
void TidalRequest::ArtistsFinishCheck(const int limit, const int offset, const int artists_received) {
|
void TidalRequest::ArtistsFinishCheck(const int limit, const int offset, const int artists_received) {
|
||||||
|
|
||||||
|
if (finished_) return;
|
||||||
|
|
||||||
if ((limit == 0 || limit > artists_received) && artists_received_ < artists_total_) {
|
if ((limit == 0 || limit > artists_received) && artists_received_ < artists_total_) {
|
||||||
int offset_next = offset + artists_received;
|
int offset_next = offset + artists_received;
|
||||||
if (offset_next > 0 && offset_next < artists_total_) {
|
if (offset_next > 0 && offset_next < artists_total_) {
|
||||||
@ -441,6 +446,7 @@ void TidalRequest::ArtistsFinishCheck(const int limit, const int offset, const i
|
|||||||
void TidalRequest::AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) {
|
void TidalRequest::AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested) {
|
||||||
--albums_requests_active_;
|
--albums_requests_active_;
|
||||||
AlbumsReceived(reply, 0, limit_requested, offset_requested, (offset_requested == 0));
|
AlbumsReceived(reply, 0, limit_requested, offset_requested, (offset_requested == 0));
|
||||||
|
if (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalRequest::AddArtistAlbumsRequest(const int artist_id, const int offset) {
|
void TidalRequest::AddArtistAlbumsRequest(const int artist_id, const int offset) {
|
||||||
@ -475,6 +481,7 @@ void TidalRequest::ArtistAlbumsReplyReceived(QNetworkReply *reply, const int art
|
|||||||
++artist_albums_received_;
|
++artist_albums_received_;
|
||||||
emit UpdateProgress(artist_albums_received_);
|
emit UpdateProgress(artist_albums_received_);
|
||||||
AlbumsReceived(reply, artist_id, 0, offset_requested, false);
|
AlbumsReceived(reply, artist_id, 0, offset_requested, false);
|
||||||
|
if (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,6 +490,8 @@ void TidalRequest::AlbumsReceived(QNetworkReply *reply, const int artist_id_requ
|
|||||||
QString error;
|
QString error;
|
||||||
QByteArray data = GetReplyData(reply, error, auto_login);
|
QByteArray data = GetReplyData(reply, error, auto_login);
|
||||||
|
|
||||||
|
if (finished_) return;
|
||||||
|
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
AlbumsFinishCheck(artist_id_requested);
|
AlbumsFinishCheck(artist_id_requested);
|
||||||
return;
|
return;
|
||||||
@ -619,6 +628,8 @@ void TidalRequest::AlbumsReceived(QNetworkReply *reply, const int artist_id_requ
|
|||||||
|
|
||||||
void TidalRequest::AlbumsFinishCheck(const int artist_id, const int limit, const int offset, const int albums_total, const int albums_received) {
|
void TidalRequest::AlbumsFinishCheck(const int artist_id, const int limit, const int offset, const int albums_total, const int albums_received) {
|
||||||
|
|
||||||
|
if (finished_) return;
|
||||||
|
|
||||||
if (limit == 0 || limit > albums_received) {
|
if (limit == 0 || limit > albums_received) {
|
||||||
int offset_next = offset + albums_received;
|
int offset_next = offset + albums_received;
|
||||||
if (offset_next > 0 && offset_next < albums_total) {
|
if (offset_next > 0 && offset_next < albums_total) {
|
||||||
@ -639,9 +650,6 @@ void TidalRequest::AlbumsFinishCheck(const int artist_id, const int limit, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!albums_requests_queue_.isEmpty() && albums_requests_active_ < kMaxConcurrentAlbumsRequests) FlushAlbumsRequests();
|
|
||||||
if (!artist_albums_requests_queue_.isEmpty() && artist_albums_requests_active_ < kMaxConcurrentArtistAlbumsRequests) FlushArtistAlbumsRequests();
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
albums_requests_queue_.isEmpty() &&
|
albums_requests_queue_.isEmpty() &&
|
||||||
albums_requests_active_ <= 0 &&
|
albums_requests_active_ <= 0 &&
|
||||||
@ -726,6 +734,8 @@ void TidalRequest::SongsReceived(QNetworkReply *reply, const int artist_id, cons
|
|||||||
QString error;
|
QString error;
|
||||||
QByteArray data = GetReplyData(reply, error, auto_login);
|
QByteArray data = GetReplyData(reply, error, auto_login);
|
||||||
|
|
||||||
|
if (finished_) return;
|
||||||
|
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist);
|
SongsFinishCheck(artist_id, album_id, limit_requested, offset_requested, 0, 0, album_artist);
|
||||||
return;
|
return;
|
||||||
@ -814,6 +824,8 @@ void TidalRequest::SongsReceived(QNetworkReply *reply, const int artist_id, cons
|
|||||||
|
|
||||||
void TidalRequest::SongsFinishCheck(const int artist_id, const int album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist) {
|
void TidalRequest::SongsFinishCheck(const int artist_id, const int album_id, const int limit, const int offset, const int songs_total, const int songs_received, const QString &album_artist) {
|
||||||
|
|
||||||
|
if (finished_) return;
|
||||||
|
|
||||||
if (limit == 0 || limit > songs_received) {
|
if (limit == 0 || limit > songs_received) {
|
||||||
int offset_next = offset + songs_received;
|
int offset_next = offset + songs_received;
|
||||||
if (offset_next > 0 && offset_next < songs_total) {
|
if (offset_next > 0 && offset_next < songs_total) {
|
||||||
@ -1020,7 +1032,7 @@ void TidalRequest::FlushAlbumCoverRequests() {
|
|||||||
|
|
||||||
QNetworkRequest req(request.url);
|
QNetworkRequest req(request.url);
|
||||||
QNetworkReply *reply = network_->get(req);
|
QNetworkReply *reply = network_->get(req);
|
||||||
replies_ << reply;
|
album_cover_replies_ << reply;
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumCoverReceived(QNetworkReply*, int, QUrl)), reply, request.album_id, request.url);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumCoverReceived(QNetworkReply*, int, QUrl)), reply, request.album_id, request.url);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1029,8 +1041,8 @@ void TidalRequest::FlushAlbumCoverRequests() {
|
|||||||
|
|
||||||
void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const int album_id, const QUrl url) {
|
void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const int album_id, const QUrl url) {
|
||||||
|
|
||||||
if (replies_.contains(reply)) {
|
if (album_cover_replies_.contains(reply)) {
|
||||||
replies_.removeAll(reply);
|
album_cover_replies_.removeAll(reply);
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1040,6 +1052,9 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const int album_id,
|
|||||||
|
|
||||||
--album_covers_requests_active_;
|
--album_covers_requests_active_;
|
||||||
++album_covers_received_;
|
++album_covers_received_;
|
||||||
|
|
||||||
|
if (finished_) return;
|
||||||
|
|
||||||
emit UpdateProgress(album_covers_received_);
|
emit UpdateProgress(album_covers_received_);
|
||||||
|
|
||||||
if (!album_covers_requests_sent_.contains(album_id)) {
|
if (!album_covers_requests_sent_.contains(album_id)) {
|
||||||
@ -1099,6 +1114,7 @@ void TidalRequest::AlbumCoverFinishCheck() {
|
|||||||
void TidalRequest::FinishCheck() {
|
void TidalRequest::FinishCheck() {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
!finished_ &&
|
||||||
!need_login_ &&
|
!need_login_ &&
|
||||||
albums_requests_queue_.isEmpty() &&
|
albums_requests_queue_.isEmpty() &&
|
||||||
artists_requests_queue_.isEmpty() &&
|
artists_requests_queue_.isEmpty() &&
|
||||||
@ -1120,6 +1136,7 @@ void TidalRequest::FinishCheck() {
|
|||||||
album_covers_requests_active_ <= 0 &&
|
album_covers_requests_active_ <= 0 &&
|
||||||
album_covers_received_ >= album_covers_requested_
|
album_covers_received_ >= album_covers_requested_
|
||||||
) {
|
) {
|
||||||
|
finished_ = true;
|
||||||
if (songs_.isEmpty()) {
|
if (songs_.isEmpty()) {
|
||||||
if (IsSearch()) {
|
if (IsSearch()) {
|
||||||
if (no_results_) emit ErrorSignal(search_id_, tr("No match"));
|
if (no_results_) emit ErrorSignal(search_id_, tr("No match"));
|
||||||
|
@ -164,6 +164,8 @@ class TidalRequest : public TidalBaseRequest {
|
|||||||
int search_id_;
|
int search_id_;
|
||||||
QString search_text_;
|
QString search_text_;
|
||||||
|
|
||||||
|
bool finished_;
|
||||||
|
|
||||||
QQueue<Request> artists_requests_queue_;
|
QQueue<Request> artists_requests_queue_;
|
||||||
QQueue<Request> albums_requests_queue_;
|
QQueue<Request> albums_requests_queue_;
|
||||||
QQueue<Request> songs_requests_queue_;
|
QQueue<Request> songs_requests_queue_;
|
||||||
@ -199,7 +201,7 @@ class TidalRequest : public TidalBaseRequest {
|
|||||||
QString errors_;
|
QString errors_;
|
||||||
bool need_login_;
|
bool need_login_;
|
||||||
bool no_results_;
|
bool no_results_;
|
||||||
QList<QNetworkReply*> replies_;
|
QList<QNetworkReply*> album_cover_replies_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QDesktopServices>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
@ -55,12 +56,17 @@
|
|||||||
#include "tidalfavoriterequest.h"
|
#include "tidalfavoriterequest.h"
|
||||||
#include "tidalstreamurlrequest.h"
|
#include "tidalstreamurlrequest.h"
|
||||||
#include "settings/tidalsettingspage.h"
|
#include "settings/tidalsettingspage.h"
|
||||||
|
#include "internet/localredirectserver.h"
|
||||||
|
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
|
|
||||||
const Song::Source TidalService::kSource = Song::Source_Tidal;
|
const Song::Source TidalService::kSource = Song::Source_Tidal;
|
||||||
const char *TidalService::kAuthUrl = "https://api.tidalhifi.com/v1/login/username";
|
const char *TidalService::kClientIdB64 = "dTVxUE5OWUliRDBTMG8zNk1yQWlGWjU2SzZxTUNyQ21ZUHpadVRuVg==";
|
||||||
const char *TidalService::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
|
const char *TidalService::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
|
||||||
|
const char *TidalService::kOAuthUrl = "https://login.tidal.com/authorize";
|
||||||
|
const char *TidalService::kOAuthAccessTokenUrl = "https://login.tidal.com/oauth2/token";
|
||||||
|
const char *TidalService::kOAuthRedirectUrl = "tidal://login/auth";
|
||||||
|
const char *TidalService::kAuthUrl = "https://api.tidalhifi.com/v1/login/username";
|
||||||
const int TidalService::kLoginAttempts = 2;
|
const int TidalService::kLoginAttempts = 2;
|
||||||
const int TidalService::kTimeResetLoginAttempts = 60000;
|
const int TidalService::kTimeResetLoginAttempts = 60000;
|
||||||
|
|
||||||
@ -89,13 +95,13 @@ TidalService::TidalService(Application *app, QObject *parent)
|
|||||||
timer_search_delay_(new QTimer(this)),
|
timer_search_delay_(new QTimer(this)),
|
||||||
timer_login_attempt_(new QTimer(this)),
|
timer_login_attempt_(new QTimer(this)),
|
||||||
favorite_request_(new TidalFavoriteRequest(this, network_, this)),
|
favorite_request_(new TidalFavoriteRequest(this, network_, this)),
|
||||||
|
user_id_(0),
|
||||||
search_delay_(1500),
|
search_delay_(1500),
|
||||||
artistssearchlimit_(1),
|
artistssearchlimit_(1),
|
||||||
albumssearchlimit_(1),
|
albumssearchlimit_(1),
|
||||||
songssearchlimit_(1),
|
songssearchlimit_(1),
|
||||||
fetchalbums_(true),
|
fetchalbums_(true),
|
||||||
cache_album_covers_(true),
|
cache_album_covers_(true),
|
||||||
user_id_(0),
|
|
||||||
pending_search_id_(0),
|
pending_search_id_(0),
|
||||||
next_pending_search_id_(1),
|
next_pending_search_id_(1),
|
||||||
search_id_(0),
|
search_id_(0),
|
||||||
@ -169,7 +175,6 @@ TidalService::TidalService(Application *app, QObject *parent)
|
|||||||
connect(favorite_request_, SIGNAL(SongsRemoved(const SongList&)), songs_collection_backend_, SLOT(DeleteSongs(const SongList&)));
|
connect(favorite_request_, SIGNAL(SongsRemoved(const SongList&)), songs_collection_backend_, SLOT(DeleteSongs(const SongList&)));
|
||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
LoadSessionID();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +183,7 @@ TidalService::~TidalService() {
|
|||||||
while (!stream_url_requests_.isEmpty()) {
|
while (!stream_url_requests_.isEmpty()) {
|
||||||
TidalStreamURLRequest *stream_url_req = stream_url_requests_.takeFirst();
|
TidalStreamURLRequest *stream_url_req = stream_url_requests_.takeFirst();
|
||||||
disconnect(stream_url_req, 0, nullptr, 0);
|
disconnect(stream_url_req, 0, nullptr, 0);
|
||||||
delete stream_url_req;
|
stream_url_req->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -191,12 +196,20 @@ void TidalService::ReloadSettings() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||||
|
|
||||||
|
oauth_ = s.value("oauth", false).toBool();
|
||||||
|
client_id_ = s.value("client_id").toString();
|
||||||
|
if (client_id_.isEmpty()) client_id_ = QString::fromUtf8(QByteArray::fromBase64(kClientIdB64));
|
||||||
|
api_token_ = s.value("api_token").toString();
|
||||||
|
if (api_token_.isEmpty()) api_token_ = QString::fromUtf8(QByteArray::fromBase64(kApiTokenB64));
|
||||||
|
user_id_ = s.value("user_id", 0).toInt();
|
||||||
|
country_code_ = s.value("country_code", "US").toString();
|
||||||
|
|
||||||
username_ = s.value("username").toString();
|
username_ = s.value("username").toString();
|
||||||
QByteArray password = s.value("password").toByteArray();
|
QByteArray password = s.value("password").toByteArray();
|
||||||
if (password.isEmpty()) password_.clear();
|
if (password.isEmpty()) password_.clear();
|
||||||
else password_ = QString::fromUtf8(QByteArray::fromBase64(password));
|
else password_ = QString::fromUtf8(QByteArray::fromBase64(password));
|
||||||
token_ = s.value("token").toString();
|
|
||||||
if (token_.isEmpty()) token_ = QString::fromUtf8(QByteArray::fromBase64(kApiTokenB64));
|
|
||||||
quality_ = s.value("quality", "LOSSLESS").toString();
|
quality_ = s.value("quality", "LOSSLESS").toString();
|
||||||
search_delay_ = s.value("searchdelay", 1500).toInt();
|
search_delay_ = s.value("searchdelay", 1500).toInt();
|
||||||
artistssearchlimit_ = s.value("artistssearchlimit", 5).toInt();
|
artistssearchlimit_ = s.value("artistssearchlimit", 5).toInt();
|
||||||
@ -205,6 +218,13 @@ void TidalService::ReloadSettings() {
|
|||||||
fetchalbums_ = s.value("fetchalbums", false).toBool();
|
fetchalbums_ = s.value("fetchalbums", false).toBool();
|
||||||
coversize_ = s.value("coversize", "320x320").toString();
|
coversize_ = s.value("coversize", "320x320").toString();
|
||||||
cache_album_covers_ = s.value("cachealbumcovers", true).toBool();
|
cache_album_covers_ = s.value("cachealbumcovers", true).toBool();
|
||||||
|
stream_url_method_ = static_cast<TidalSettingsPage::StreamUrlMethod>(s.value("streamurl").toInt());
|
||||||
|
|
||||||
|
access_token_ = s.value("access_token").toString();
|
||||||
|
refresh_token_ = s.value("refresh_token").toString();
|
||||||
|
session_id_ = s.value("session_id").toString();
|
||||||
|
expiry_time_ = s.value("expiry_time").toDateTime();
|
||||||
|
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -213,20 +233,206 @@ QString TidalService::CoverCacheDir() {
|
|||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
|
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::LoadSessionID() {
|
void TidalService::StartAuthorisation() {
|
||||||
|
|
||||||
|
login_sent_ = true;
|
||||||
|
++login_attempts_;
|
||||||
|
if (timer_login_attempt_->isActive()) timer_login_attempt_->stop();
|
||||||
|
timer_login_attempt_->setInterval(kTimeResetLoginAttempts);
|
||||||
|
timer_login_attempt_->start();
|
||||||
|
|
||||||
|
const ParamList params = ParamList()
|
||||||
|
//<< Param("response_type", "token")
|
||||||
|
<< Param("response_type", "code")
|
||||||
|
<< Param("code_challenge", "T36p0vieh1pnvNNsG-0kNNpZIk4ZuP8vna5ZAtooxqo")
|
||||||
|
<< Param("code_challenge_method", "S256")
|
||||||
|
<< Param("redirect_uri", kOAuthRedirectUrl)
|
||||||
|
<< Param("client_id", client_id_)
|
||||||
|
<< Param("scope", "r_usr w_usr");
|
||||||
|
|
||||||
|
QUrlQuery url_query;
|
||||||
|
for (const Param ¶m : params) {
|
||||||
|
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
||||||
|
url_query.addQueryItem(encoded_param.first, encoded_param.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url = QUrl(kOAuthUrl);
|
||||||
|
url.setQuery(url_query);
|
||||||
|
QDesktopServices::openUrl(url);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalService::AuthorisationUrlReceived(const QUrl &url) {
|
||||||
|
|
||||||
|
qLog(Debug) << "Tidal: Authorisation URL Received" << url;
|
||||||
|
|
||||||
|
QUrlQuery url_query(url);
|
||||||
|
|
||||||
|
if (url_query.hasQueryItem("token_type") && url_query.hasQueryItem("expires_in") && url_query.hasQueryItem("access_token")) {
|
||||||
|
|
||||||
|
access_token_ = url_query.queryItemValue("access_token").toUtf8();
|
||||||
|
int expires_in = url_query.queryItemValue("expires_in").toInt();
|
||||||
|
expiry_time_ = QDateTime::currentDateTime().addSecs(expires_in - 120);
|
||||||
|
session_id_.clear();
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||||
if (!s.contains("user_id") ||!s.contains("session_id") || !s.contains("country_code")) return;
|
s.setValue("access_token", access_token_);
|
||||||
session_id_ = s.value("session_id").toString();
|
s.setValue("expiry_time", expiry_time_);
|
||||||
user_id_ = s.value("user_id").toInt();
|
s.remove("refresh_token");
|
||||||
country_code_ = s.value("country_code").toString();
|
s.remove("session_id");
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
|
login_attempts_ = 0;
|
||||||
|
if (timer_login_attempt_->isActive()) timer_login_attempt_->stop();
|
||||||
|
|
||||||
|
emit LoginComplete(true);
|
||||||
|
emit LoginSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (url_query.hasQueryItem("code") && url_query.hasQueryItem("state")) {
|
||||||
|
|
||||||
|
QString code = url_query.queryItemValue("code");
|
||||||
|
QString state = url_query.queryItemValue("state");
|
||||||
|
|
||||||
|
const ParamList params = ParamList() << Param("code", code)
|
||||||
|
<< Param("client_id", client_id_)
|
||||||
|
<< Param("grant_type", "authorization_code")
|
||||||
|
<< Param("redirect_uri", kOAuthRedirectUrl)
|
||||||
|
<< Param("scope", "r_usr w_usr")
|
||||||
|
<< Param("code_verifier", "128,113,65,59,36,187,64,14,99,32,149,202,178,5,165,106,14,184,157,42,5,198,243,245,75,115,227,169,183,199,216,67,42,202,105,33,1");
|
||||||
|
|
||||||
|
QUrlQuery url_query;
|
||||||
|
for (const Param ¶m : params) {
|
||||||
|
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
||||||
|
url_query.addQueryItem(encoded_param.first, encoded_param.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url(kOAuthAccessTokenUrl);
|
||||||
|
QNetworkRequest request = QNetworkRequest(url);
|
||||||
|
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
|
||||||
|
QNetworkReply *reply = network_->post(request, query);
|
||||||
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(AccessTokenRequestFinished(QNetworkReply*)), reply);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
|
LoginError(tr("Reply from Tidal is missing query items."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalService::AccessTokenRequestFinished(QNetworkReply *reply) {
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
login_sent_ = false;
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
if (reply->error() < 200) {
|
||||||
|
// This is a network error, there is nothing more to do.
|
||||||
|
LoginError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// See if there is Json data containing "redirectUri" then use that instead.
|
||||||
|
QByteArray data(reply->readAll());
|
||||||
|
QJsonParseError json_error;
|
||||||
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||||
|
QString failure_reason;
|
||||||
|
if (json_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("redirectUri")) {
|
||||||
|
QString redirect_uri = json_obj["redirectUri"].toString();
|
||||||
|
failure_reason = QString("Authentication failure: %1").arg(redirect_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failure_reason.isEmpty()) {
|
||||||
|
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||||
|
}
|
||||||
|
LoginError(failure_reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray data(reply->readAll());
|
||||||
|
QJsonParseError json_error;
|
||||||
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||||
|
|
||||||
|
if (json_error.error != QJsonParseError::NoError) {
|
||||||
|
LoginError("Authentication reply from server missing Json data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||||
|
LoginError("Authentication reply from server has empty Json document.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_doc.isObject()) {
|
||||||
|
LoginError("Authentication reply from server has Json document that is not an object.", json_doc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json_obj = json_doc.object();
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
LoginError("Authentication reply from server has empty Json object.", json_doc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("access_token") ||
|
||||||
|
!json_obj.contains("refresh_token") ||
|
||||||
|
!json_obj.contains("expires_in") ||
|
||||||
|
!json_obj.contains("user")
|
||||||
|
) {
|
||||||
|
LoginError("Authentication reply from server is missing access_token, refresh_token, expires_in or user", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
access_token_ = json_obj["access_token"].toString();
|
||||||
|
refresh_token_ = json_obj["refresh_token"].toString();
|
||||||
|
int expires_in = json_obj["expires_in"].toInt();
|
||||||
|
expiry_time_ = QDateTime::currentDateTime().addSecs(expires_in - 120);
|
||||||
|
|
||||||
|
QJsonValue json_user = json_obj["user"];
|
||||||
|
if (!json_user.isObject()) {
|
||||||
|
LoginError("Authentication reply from server has Json user that is not an object.", json_doc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonObject json_obj_user = json_user.toObject();
|
||||||
|
if (json_obj_user.isEmpty()) {
|
||||||
|
LoginError("Authentication reply from server has empty Json user object.", json_doc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
country_code_ = json_obj_user["countryCode"].toString();
|
||||||
|
user_id_ = json_obj_user["userId"].toInt();
|
||||||
|
session_id_.clear();
|
||||||
|
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||||
|
s.setValue("access_token", access_token_);
|
||||||
|
s.setValue("refresh_token", refresh_token_);
|
||||||
|
s.setValue("expiry_time", expiry_time_);
|
||||||
|
s.setValue("country_code", country_code_);
|
||||||
|
s.setValue("user_id", user_id_);
|
||||||
|
s.remove("session_id");
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
|
qLog(Debug) << "Tidal: Login successful" << "user id" << user_id_ << "access token" << access_token_;
|
||||||
|
|
||||||
|
login_attempts_ = 0;
|
||||||
|
if (timer_login_attempt_->isActive()) timer_login_attempt_->stop();
|
||||||
|
|
||||||
|
emit LoginComplete(true);
|
||||||
|
emit LoginSuccess();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::SendLogin() {
|
void TidalService::SendLogin() {
|
||||||
SendLogin(username_, password_, token_);
|
SendLogin(username_, password_, api_token_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::SendLogin(const QString &username, const QString &password, const QString &token) {
|
void TidalService::SendLogin(const QString &username, const QString &password, const QString &token) {
|
||||||
@ -239,10 +445,7 @@ void TidalService::SendLogin(const QString &username, const QString &password, c
|
|||||||
timer_login_attempt_->setInterval(kTimeResetLoginAttempts);
|
timer_login_attempt_->setInterval(kTimeResetLoginAttempts);
|
||||||
timer_login_attempt_->start();
|
timer_login_attempt_->start();
|
||||||
|
|
||||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
const ParamList params = ParamList() << Param("token", (token.isEmpty() ? api_token_ : token))
|
||||||
typedef QList<EncodedParam> EncodedParamList;
|
|
||||||
|
|
||||||
ParamList params = ParamList() << Param("token", token_)
|
|
||||||
<< Param("username", username)
|
<< Param("username", username)
|
||||||
<< Param("password", password)
|
<< Param("password", password)
|
||||||
<< Param("clientVersion", "2.2.1--7");
|
<< Param("clientVersion", "2.2.1--7");
|
||||||
@ -259,12 +462,13 @@ void TidalService::SendLogin(const QString &username, const QString &password, c
|
|||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
|
|
||||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
req.setRawHeader("X-Tidal-Token", token_.toUtf8());
|
req.setRawHeader("X-Tidal-Token", (token.isEmpty() ? api_token_.toUtf8() : token.toUtf8()));
|
||||||
|
|
||||||
QNetworkReply *reply = network_->post(req, url_query.toString(QUrl::FullyEncoded).toUtf8());
|
QByteArray query = url_query.toString(QUrl::FullyEncoded).toUtf8();
|
||||||
|
QNetworkReply *reply = network_->post(req, query);
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
||||||
|
|
||||||
//qLog(Debug) << "Tidal: Sending request" << url;
|
//qLog(Debug) << "Tidal: Sending request" << url << query;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,12 +540,18 @@ void TidalService::HandleAuthReply(QNetworkReply *reply) {
|
|||||||
country_code_ = json_obj["countryCode"].toString();
|
country_code_ = json_obj["countryCode"].toString();
|
||||||
session_id_ = json_obj["sessionId"].toString();
|
session_id_ = json_obj["sessionId"].toString();
|
||||||
user_id_ = json_obj["userId"].toInt();
|
user_id_ = json_obj["userId"].toInt();
|
||||||
|
access_token_.clear();
|
||||||
|
refresh_token_.clear();
|
||||||
|
expiry_time_ = QDateTime();
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||||
s.setValue("user_id", user_id_);
|
s.setValue("user_id", user_id_);
|
||||||
s.setValue("session_id", session_id_);
|
s.setValue("session_id", session_id_);
|
||||||
s.setValue("country_code", country_code_);
|
s.setValue("country_code", country_code_);
|
||||||
|
s.remove("access_token");
|
||||||
|
s.remove("refresh_token");
|
||||||
|
s.remove("expiry_time");
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
qLog(Debug) << "Tidal: Login successful" << "user id" << user_id_ << "session id" << session_id_ << "country code" << country_code_;
|
qLog(Debug) << "Tidal: Login successful" << "user id" << user_id_ << "session id" << session_id_ << "country code" << country_code_;
|
||||||
@ -356,15 +566,15 @@ void TidalService::HandleAuthReply(QNetworkReply *reply) {
|
|||||||
|
|
||||||
void TidalService::Logout() {
|
void TidalService::Logout() {
|
||||||
|
|
||||||
user_id_ = 0;
|
access_token_.clear();
|
||||||
session_id_.clear();
|
session_id_.clear();
|
||||||
country_code_.clear();
|
expiry_time_ = QDateTime();
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||||
s.remove("user_id");
|
s.remove("access_token");
|
||||||
s.remove("session_id");
|
s.remove("session_id");
|
||||||
s.remove("country_code");
|
s.remove("expiry_time");
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -381,7 +591,7 @@ void TidalService::TryLogin() {
|
|||||||
emit LoginComplete(false, "Maximum number of login attempts reached.");
|
emit LoginComplete(false, "Maximum number of login attempts reached.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (token_.isEmpty()) {
|
if (api_token_.isEmpty()) {
|
||||||
emit LoginComplete(false, "Missing Tidal API token.");
|
emit LoginComplete(false, "Missing Tidal API token.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -428,14 +638,12 @@ void TidalService::GetArtists() {
|
|||||||
void TidalService::ArtistsResultsReceived(SongList songs) {
|
void TidalService::ArtistsResultsReceived(SongList songs) {
|
||||||
|
|
||||||
emit ArtistsResults(songs);
|
emit ArtistsResults(songs);
|
||||||
ResetArtistsRequest();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::ArtistsErrorReceived(QString error) {
|
void TidalService::ArtistsErrorReceived(QString error) {
|
||||||
|
|
||||||
emit ArtistsError(error);
|
emit ArtistsError(error);
|
||||||
ResetArtistsRequest();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,14 +675,12 @@ void TidalService::GetAlbums() {
|
|||||||
void TidalService::AlbumsResultsReceived(SongList songs) {
|
void TidalService::AlbumsResultsReceived(SongList songs) {
|
||||||
|
|
||||||
emit AlbumsResults(songs);
|
emit AlbumsResults(songs);
|
||||||
ResetAlbumsRequest();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::AlbumsErrorReceived(QString error) {
|
void TidalService::AlbumsErrorReceived(QString error) {
|
||||||
|
|
||||||
emit AlbumsError(error);
|
emit AlbumsError(error);
|
||||||
ResetAlbumsRequest();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,14 +712,12 @@ void TidalService::GetSongs() {
|
|||||||
void TidalService::SongsResultsReceived(SongList songs) {
|
void TidalService::SongsResultsReceived(SongList songs) {
|
||||||
|
|
||||||
emit SongsResults(songs);
|
emit SongsResults(songs);
|
||||||
ResetSongsRequest();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::SongsErrorReceived(QString error) {
|
void TidalService::SongsErrorReceived(QString error) {
|
||||||
|
|
||||||
emit SongsError(error);
|
emit SongsError(error);
|
||||||
ResetSongsRequest();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,8 +742,8 @@ int TidalService::Search(const QString &text, InternetSearch::SearchType type) {
|
|||||||
|
|
||||||
void TidalService::StartSearch() {
|
void TidalService::StartSearch() {
|
||||||
|
|
||||||
if (token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
|
if ((oauth_ && !authenticated()) || api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
|
||||||
emit SearchError(pending_search_id_, tr("Missing token, username and/or password."));
|
emit SearchError(pending_search_id_, tr("Not authenticated."));
|
||||||
next_pending_search_id_ = 1;
|
next_pending_search_id_ = 1;
|
||||||
ShowConfig();
|
ShowConfig();
|
||||||
return;
|
return;
|
||||||
@ -605,7 +809,7 @@ void TidalService::HandleStreamURLFinished(const QUrl original_url, const QUrl s
|
|||||||
|
|
||||||
TidalStreamURLRequest *stream_url_req = qobject_cast<TidalStreamURLRequest*>(sender());
|
TidalStreamURLRequest *stream_url_req = qobject_cast<TidalStreamURLRequest*>(sender());
|
||||||
if (!stream_url_req || !stream_url_requests_.contains(stream_url_req)) return;
|
if (!stream_url_req || !stream_url_requests_.contains(stream_url_req)) return;
|
||||||
delete stream_url_req;
|
stream_url_req->deleteLater();
|
||||||
stream_url_requests_.removeAll(stream_url_req);
|
stream_url_requests_.removeAll(stream_url_req);
|
||||||
|
|
||||||
emit StreamURLFinished(original_url, stream_url, filetype, error);
|
emit StreamURLFinished(original_url, stream_url, filetype, error);
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "internet/internetservice.h"
|
#include "internet/internetservice.h"
|
||||||
#include "internet/internetsearch.h"
|
#include "internet/internetsearch.h"
|
||||||
|
#include "settings/tidalsettingspage.h"
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
class QSortFilterProxyModel;
|
||||||
class Application;
|
class Application;
|
||||||
@ -68,7 +69,11 @@ class TidalService : public InternetService {
|
|||||||
|
|
||||||
const int max_login_attempts() { return kLoginAttempts; }
|
const int max_login_attempts() { return kLoginAttempts; }
|
||||||
|
|
||||||
QString token() { return token_; }
|
const bool oauth() { return oauth_; }
|
||||||
|
QString client_id() { return client_id_; }
|
||||||
|
QString api_token() { return api_token_; }
|
||||||
|
quint64 user_id() { return user_id_; }
|
||||||
|
QString country_code() { return country_code_; }
|
||||||
QString username() { return username_; }
|
QString username() { return username_; }
|
||||||
QString password() { return password_; }
|
QString password() { return password_; }
|
||||||
QString quality() { return quality_; }
|
QString quality() { return quality_; }
|
||||||
@ -79,12 +84,12 @@ class TidalService : public InternetService {
|
|||||||
bool fetchalbums() { return fetchalbums_; }
|
bool fetchalbums() { return fetchalbums_; }
|
||||||
QString coversize() { return coversize_; }
|
QString coversize() { return coversize_; }
|
||||||
bool cache_album_covers() { return cache_album_covers_; }
|
bool cache_album_covers() { return cache_album_covers_; }
|
||||||
|
TidalSettingsPage::StreamUrlMethod stream_url_method() { return stream_url_method_; }
|
||||||
|
|
||||||
|
QString access_token() { return access_token_; }
|
||||||
QString session_id() { return session_id_; }
|
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 authenticated() { return (!access_token_.isEmpty() || !session_id_.isEmpty()); }
|
||||||
const bool login_sent() { return login_sent_; }
|
const bool login_sent() { return login_sent_; }
|
||||||
const bool login_attempts() { return login_attempts_; }
|
const bool login_attempts() { return login_attempts_; }
|
||||||
|
|
||||||
@ -125,6 +130,9 @@ class TidalService : public InternetService {
|
|||||||
void ResetSongsRequest();
|
void ResetSongsRequest();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void StartAuthorisation();
|
||||||
|
void AuthorisationUrlReceived(const QUrl &url);
|
||||||
|
void AccessTokenRequestFinished(QNetworkReply *reply);
|
||||||
void SendLogin();
|
void SendLogin();
|
||||||
void HandleAuthReply(QNetworkReply *reply);
|
void HandleAuthReply(QNetworkReply *reply);
|
||||||
void ResetLoginAttempts();
|
void ResetLoginAttempts();
|
||||||
@ -141,12 +149,18 @@ class TidalService : public InternetService {
|
|||||||
typedef QPair<QString, QString> Param;
|
typedef QPair<QString, QString> Param;
|
||||||
typedef QList<Param> ParamList;
|
typedef QList<Param> ParamList;
|
||||||
|
|
||||||
void LoadSessionID();
|
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||||
|
typedef QList<EncodedParam> EncodedParamList;
|
||||||
|
|
||||||
void SendSearch();
|
void SendSearch();
|
||||||
QString LoginError(QString error, QVariant debug = QVariant());
|
QString LoginError(QString error, QVariant debug = QVariant());
|
||||||
|
|
||||||
static const char *kAuthUrl;
|
static const char *kClientIdB64;
|
||||||
static const char *kApiTokenB64;
|
static const char *kApiTokenB64;
|
||||||
|
static const char *kOAuthUrl;
|
||||||
|
static const char *kOAuthAccessTokenUrl;
|
||||||
|
static const char *kOAuthRedirectUrl;
|
||||||
|
static const char *kAuthUrl;
|
||||||
static const int kLoginAttempts;
|
static const int kLoginAttempts;
|
||||||
static const int kTimeResetLoginAttempts;
|
static const int kTimeResetLoginAttempts;
|
||||||
|
|
||||||
@ -183,7 +197,11 @@ class TidalService : public InternetService {
|
|||||||
std::shared_ptr<TidalRequest> search_request_;
|
std::shared_ptr<TidalRequest> search_request_;
|
||||||
TidalFavoriteRequest *favorite_request_;
|
TidalFavoriteRequest *favorite_request_;
|
||||||
|
|
||||||
QString token_;
|
bool oauth_;
|
||||||
|
QString client_id_;
|
||||||
|
QString api_token_;
|
||||||
|
quint64 user_id_;
|
||||||
|
QString country_code_;
|
||||||
QString username_;
|
QString username_;
|
||||||
QString password_;
|
QString password_;
|
||||||
QString quality_;
|
QString quality_;
|
||||||
@ -194,10 +212,12 @@ class TidalService : public InternetService {
|
|||||||
bool fetchalbums_;
|
bool fetchalbums_;
|
||||||
QString coversize_;
|
QString coversize_;
|
||||||
bool cache_album_covers_;
|
bool cache_album_covers_;
|
||||||
|
TidalSettingsPage::StreamUrlMethod stream_url_method_;
|
||||||
|
|
||||||
|
QString access_token_;
|
||||||
|
QString refresh_token_;
|
||||||
QString session_id_;
|
QString session_id_;
|
||||||
quint64 user_id_;
|
QDateTime expiry_time_;
|
||||||
QString country_code_;
|
|
||||||
|
|
||||||
int pending_search_id_;
|
int pending_search_id_;
|
||||||
int next_pending_search_id_;
|
int next_pending_search_id_;
|
||||||
|
@ -20,20 +20,29 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QList>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QJsonValue>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/network.h"
|
#include "core/network.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "settings/tidalsettingspage.h"
|
||||||
#include "tidalservice.h"
|
#include "tidalservice.h"
|
||||||
#include "tidalbaserequest.h"
|
#include "tidalbaserequest.h"
|
||||||
#include "tidalstreamurlrequest.h"
|
#include "tidalstreamurlrequest.h"
|
||||||
|
|
||||||
TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent)
|
TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent)
|
||||||
: TidalBaseRequest(service, network, parent),
|
: TidalBaseRequest(service, network, parent),
|
||||||
|
service_(service),
|
||||||
reply_(nullptr),
|
reply_(nullptr),
|
||||||
original_url_(original_url),
|
original_url_(original_url),
|
||||||
song_id_(original_url.path().toInt()),
|
song_id_(original_url.path().toInt()),
|
||||||
@ -67,6 +76,10 @@ void TidalStreamURLRequest::LoginComplete(bool success, QString error) {
|
|||||||
void TidalStreamURLRequest::Process() {
|
void TidalStreamURLRequest::Process() {
|
||||||
|
|
||||||
if (!authenticated()) {
|
if (!authenticated()) {
|
||||||
|
if (oauth()) {
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, tr("Not authenticated."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
need_login_ = true;
|
need_login_ = true;
|
||||||
emit TryLogin();
|
emit TryLogin();
|
||||||
return;
|
return;
|
||||||
@ -81,7 +94,7 @@ void TidalStreamURLRequest::Cancel() {
|
|||||||
reply_->abort();
|
reply_->abort();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, "Cancelled.");
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, tr("Cancelled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -90,16 +103,36 @@ void TidalStreamURLRequest::GetStreamURL() {
|
|||||||
|
|
||||||
++tries_;
|
++tries_;
|
||||||
|
|
||||||
ParamList parameters;
|
|
||||||
parameters << Param("soundQuality", quality());
|
|
||||||
|
|
||||||
if (reply_) {
|
if (reply_) {
|
||||||
disconnect(reply_, 0, nullptr, 0);
|
disconnect(reply_, 0, nullptr, 0);
|
||||||
if (reply_->isRunning()) reply_->abort();
|
if (reply_->isRunning()) reply_->abort();
|
||||||
reply_->deleteLater();
|
reply_->deleteLater();
|
||||||
}
|
}
|
||||||
reply_ = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id_), parameters);
|
|
||||||
|
ParamList params;
|
||||||
|
|
||||||
|
switch (stream_url_method()) {
|
||||||
|
case TidalSettingsPage::StreamUrlMethod_StreamUrl:
|
||||||
|
params << Param("soundQuality", quality());
|
||||||
|
reply_ = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id_), params);
|
||||||
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
|
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
|
||||||
|
break;
|
||||||
|
case TidalSettingsPage::StreamUrlMethod_UrlPostPaywall:
|
||||||
|
params << Param("audioquality", quality());
|
||||||
|
params << Param("playbackmode", "STREAM");
|
||||||
|
params << Param("assetpresentation", "FULL");
|
||||||
|
params << Param("urlusagemode", "STREAM");
|
||||||
|
reply_ = CreateRequest(QString("tracks/%1/urlpostpaywall").arg(song_id_), params);
|
||||||
|
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
|
||||||
|
break;
|
||||||
|
case TidalSettingsPage::StreamUrlMethod_PlaybackInfoPostPaywall:
|
||||||
|
params << Param("audioquality", quality());
|
||||||
|
params << Param("playbackmode", "STREAM");
|
||||||
|
params << Param("assetpresentation", "FULL");
|
||||||
|
reply_ = CreateRequest(QString("tracks/%1/playbackinfopostpaywall").arg(song_id_), params);
|
||||||
|
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,26 +156,125 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
|||||||
}
|
}
|
||||||
reply_ = nullptr;
|
reply_ = nullptr;
|
||||||
|
|
||||||
|
qLog(Debug) << "Tidal:" << data;
|
||||||
|
|
||||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||||
if (json_obj.isEmpty()) {
|
if (json_obj.isEmpty()) {
|
||||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!json_obj.contains("url") || !json_obj.contains("codec")) {
|
if (!json_obj.contains("trackId")) {
|
||||||
error = Error("Invalid Json reply, stream missing url or codec.", json_obj);
|
error = Error("Invalid Json reply, stream missing trackId.", json_obj);
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int track_id(json_obj["trackId"].toInt());
|
||||||
|
if (track_id != song_id_) {
|
||||||
|
error = Error("Incorrect track ID returned.", json_obj);
|
||||||
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl new_url(json_obj["url"].toString());
|
Song::FileType filetype(Song::FileType_Unknown);
|
||||||
QString codec(json_obj["codec"].toString().toLower());
|
|
||||||
Song::FileType filetype(Song::FiletypeByExtension(codec));
|
if (json_obj.contains("codec") || json_obj.contains("codecs")) {
|
||||||
|
QString codec;
|
||||||
|
if (json_obj.contains("codec")) codec = json_obj["codec"].toString().toLower();
|
||||||
|
if (json_obj.contains("codecs")) codec = json_obj["codecs"].toString().toLower();
|
||||||
|
filetype = Song::FiletypeByExtension(codec);
|
||||||
if (filetype == Song::FileType_Unknown) {
|
if (filetype == Song::FileType_Unknown) {
|
||||||
qLog(Debug) << "Tidal: Unknown codec" << codec;
|
qLog(Debug) << "Tidal: Unknown codec" << codec;
|
||||||
filetype = Song::FileType_Stream;
|
filetype = Song::FileType_Stream;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emit StreamURLFinished(original_url_, new_url, filetype, QString());
|
QList<QUrl> urls;
|
||||||
|
|
||||||
|
if (json_obj.contains("manifest")) {
|
||||||
|
|
||||||
|
QString manifest(json_obj["manifest"].toString());
|
||||||
|
QByteArray data_manifest = QByteArray::fromBase64(manifest.toUtf8());
|
||||||
|
|
||||||
|
qLog(Debug) << "Tidal:" << data_manifest;
|
||||||
|
|
||||||
|
QXmlStreamReader xml_reader(data_manifest);
|
||||||
|
if (!xml_reader.hasError()) {
|
||||||
|
|
||||||
|
QString filepath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/tidalstreams";
|
||||||
|
QString filename = "tidal-" + QString::number(song_id_) + ".xml";
|
||||||
|
if (!QDir().mkpath(filepath)) {
|
||||||
|
error = Error(QString("Failed to create directory %1.").arg(filepath), json_obj);
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QUrl url("file://" + filepath + "/" + filename);
|
||||||
|
QFile file(url.toLocalFile());
|
||||||
|
if (file.exists())
|
||||||
|
file.remove();
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
error = Error(QString("Failed to open file %1 for writing.").arg(url.toLocalFile()), json_obj);
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file.write(data_manifest);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
urls << url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
|
json_obj = ExtractJsonObj(data_manifest, error);
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json_obj.contains("mimeType")) {
|
||||||
|
error = Error("Invalid Json reply, stream url reply manifest is missing mimeType.", json_obj);
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString mimetype = json_obj["mimeType"].toString();
|
||||||
|
QMimeDatabase mimedb;
|
||||||
|
for (QString suffix : mimedb.mimeTypeForName(mimetype.toUtf8()).suffixes()) {
|
||||||
|
filetype = Song::FiletypeByExtension(suffix);
|
||||||
|
if (filetype != Song::FileType_Unknown) break;
|
||||||
|
}
|
||||||
|
if (filetype == Song::FileType_Unknown) {
|
||||||
|
qLog(Debug) << "Tidal: Unknown mimetype" << mimetype;
|
||||||
|
filetype = Song::FileType_Stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_obj.contains("urls")) {
|
||||||
|
QJsonValue json_urls = json_obj["urls"];
|
||||||
|
if (!json_urls.isArray()) {
|
||||||
|
error = Error("Invalid Json reply, urls is not an array.", json_urls);
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonArray json_array_urls = json_urls.toArray();
|
||||||
|
for (const QJsonValue &value : json_array_urls) {
|
||||||
|
urls << QUrl(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (json_obj.contains("url")) {
|
||||||
|
QUrl new_url(json_obj["url"].toString());
|
||||||
|
urls << new_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urls.isEmpty()) {
|
||||||
|
error = Error("Missing stream urls.", json_obj);
|
||||||
|
emit StreamURLFinished(original_url_, original_url_, filetype);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit StreamURLFinished(original_url_, urls.first(), filetype);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "tidalbaserequest.h"
|
#include "tidalbaserequest.h"
|
||||||
|
#include "settings/tidalsettingspage.h"
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class NetworkAccessManager;
|
class NetworkAccessManager;
|
||||||
@ -44,6 +45,8 @@ class TidalStreamURLRequest : public TidalBaseRequest {
|
|||||||
void NeedLogin() { need_login_ = true; }
|
void NeedLogin() { need_login_ = true; }
|
||||||
void Cancel();
|
void Cancel();
|
||||||
|
|
||||||
|
const bool oauth() { return service_->oauth(); }
|
||||||
|
TidalSettingsPage::StreamUrlMethod stream_url_method() { return service_->stream_url_method(); }
|
||||||
QUrl original_url() { return original_url_; }
|
QUrl original_url() { return original_url_; }
|
||||||
int song_id() { return song_id_; }
|
int song_id() { return song_id_; }
|
||||||
bool need_login() { return need_login_; }
|
bool need_login() { return need_login_; }
|
||||||
@ -57,6 +60,7 @@ class TidalStreamURLRequest : public TidalBaseRequest {
|
|||||||
void StreamURLReceived();
|
void StreamURLReceived();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
TidalService *service_;
|
||||||
QNetworkReply *reply_;
|
QNetworkReply *reply_;
|
||||||
QUrl original_url_;
|
QUrl original_url_;
|
||||||
int song_id_;
|
int song_id_;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user