From 3594af5be12d979762719010535db8f5aaec0905 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 26 Mar 2015 16:52:19 +0100 Subject: [PATCH] Initial support for Amazon Cloud Drive. --- CMakeLists.txt | 5 + data/data.qrc | 2 + data/providers/amazonclouddrive.png | Bin 0 -> 3102 bytes data/schema/schema-48.sql | 50 ++++++ ext/libclementine-tagreader/tagreader.cpp | 7 +- src/CMakeLists.txt | 14 ++ src/config.h.in | 1 + src/core/database.cpp | 2 +- src/engines/gstenginepipeline.cpp | 9 ++ src/internet/amazon/amazonclouddrive.cpp | 179 +++++++++++++++++++++ src/internet/amazon/amazonclouddrive.h | 53 ++++++ src/internet/amazon/amazonsettingspage.cpp | 75 +++++++++ src/internet/amazon/amazonsettingspage.h | 53 ++++++ src/internet/amazon/amazonsettingspage.ui | 110 +++++++++++++ src/internet/amazon/amazonurlhandler.cpp | 11 ++ src/internet/amazon/amazonurlhandler.h | 22 +++ src/internet/amazon/dropboxsettingspage.ui | 110 +++++++++++++ src/internet/core/internetmodel.cpp | 6 + src/internet/core/oauthenticator.cpp | 6 + src/internet/core/oauthenticator.h | 4 +- src/ui/mainwindow.cpp | 2 +- src/ui/mainwindow.ui | 1 + src/ui/settingsdialog.cpp | 8 + src/ui/settingsdialog.h | 3 +- 24 files changed, 728 insertions(+), 5 deletions(-) create mode 100644 data/providers/amazonclouddrive.png create mode 100644 data/schema/schema-48.sql create mode 100644 src/internet/amazon/amazonclouddrive.cpp create mode 100644 src/internet/amazon/amazonclouddrive.h create mode 100644 src/internet/amazon/amazonsettingspage.cpp create mode 100644 src/internet/amazon/amazonsettingspage.h create mode 100644 src/internet/amazon/amazonsettingspage.ui create mode 100644 src/internet/amazon/amazonurlhandler.cpp create mode 100644 src/internet/amazon/amazonurlhandler.h create mode 100644 src/internet/amazon/dropboxsettingspage.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 97d2b9b3a..94f2bf979 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,6 +215,11 @@ optional_component(SEAFILE ON "Seafile support" DEPENDS "Taglib 1.8" "TAGLIB_VERSION VERSION_GREATER 1.7.999" ) +optional_component(AMAZON_CLOUD_DRIVE ON "Amazon Cloud Drive support" + DEPENDS "Google sparsehash" SPARSEHASH_INCLUDE_DIRS + DEPENDS "Taglib 1.8" "TAGLIB_VERSION VERSION_GREATER 1.7.999" +) + optional_component(AUDIOCD ON "Devices: Audio CD support" DEPENDS "libcdio" CDIO_FOUND ) diff --git a/data/data.qrc b/data/data.qrc index 162c2f7ff..afd87697b 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -310,6 +310,7 @@ playstore/uk_generic_rgb_wo_45.png playstore/vi_generic_rgb_wo_45.png providers/amazon.png + providers/amazonclouddrive.png providers/aol.png providers/bbc.png providers/box.png @@ -385,6 +386,7 @@ schema/schema-45.sql schema/schema-46.sql schema/schema-47.sql + schema/schema-48.sql schema/schema-4.sql schema/schema-5.sql schema/schema-6.sql diff --git a/data/providers/amazonclouddrive.png b/data/providers/amazonclouddrive.png new file mode 100644 index 0000000000000000000000000000000000000000..d3707287ab61e17e4adc60a6e1e96f5cafe9ce32 GIT binary patch literal 3102 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 literal 0 HcmV?d00001 diff --git a/data/schema/schema-48.sql b/data/schema/schema-48.sql new file mode 100644 index 000000000..bdff64858 --- /dev/null +++ b/data/schema/schema-48.sql @@ -0,0 +1,50 @@ +CREATE TABLE amazon_cloud_drive_songs( + title TEXT, + album TEXT, + artist TEXT, + albumartist TEXT, + composer TEXT, + track INTEGER, + disc INTEGER, + bpm REAL, + year INTEGER, + genre TEXT, + comment TEXT, + compilation INTEGER, + + length INTEGER, + bitrate INTEGER, + samplerate INTEGER, + + directory INTEGER NOT NULL, + filename TEXT NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + filesize INTEGER NOT NULL, + sampler INTEGER NOT NULL DEFAULT 0, + art_automatic TEXT, + art_manual TEXT, + filetype INTEGER NOT NULL DEFAULT 0, + playcount INTEGER NOT NULL DEFAULT 0, + lastplayed INTEGER, + rating INTEGER, + forced_compilation_on INTEGER NOT NULL DEFAULT 0, + forced_compilation_off INTEGER NOT NULL DEFAULT 0, + effective_compilation NOT NULL DEFAULT 0, + skipcount INTEGER NOT NULL DEFAULT 0, + score INTEGER NOT NULL DEFAULT 0, + beginning INTEGER NOT NULL DEFAULT 0, + cue_path TEXT, + unavailable INTEGER DEFAULT 0, + effective_albumartist TEXT, + etag TEXT, + performer TEXT, + grouping TEXT +); + +CREATE VIRTUAL TABLE amazon_cloud_drive_songs_fts USING fts3 ( + ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment, + tokenize=unicode +); + +UPDATE schema_version SET version=48; diff --git a/ext/libclementine-tagreader/tagreader.cpp b/ext/libclementine-tagreader/tagreader.cpp index b3eb8faa1..759827dcf 100644 --- a/ext/libclementine-tagreader/tagreader.cpp +++ b/ext/libclementine-tagreader/tagreader.cpp @@ -957,7 +957,12 @@ bool TagReader::ReadCloudFile(const QUrl& download_url, const QString& title, int size, const QString& mime_type, const QString& authorisation_header, pb::tagreader::SongMetadata* song) const { - qLog(Debug) << "Loading tags from" << title; + qLog(Debug) << "Loading tags from" + << title + << download_url + << size + << mime_type + << authorisation_header; std::unique_ptr stream(new CloudStream( download_url, title, size, authorisation_header, network_)); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4a80c5994..355b605db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1178,6 +1178,20 @@ optional_source(HAVE_SEAFILE internet/seafile/seafilesettingspage.ui ) +# Amazon Cloud Drive support +optional_source(HAVE_AMAZON_CLOUD_DRIVE + SOURCES + internet/amazon/amazonclouddrive.cpp + internet/amazon/amazonsettingspage.cpp + internet/amazon/amazonurlhandler.cpp + HEADERS + internet/amazon/amazonclouddrive.h + internet/amazon/amazonsettingspage.h + internet/amazon/amazonurlhandler.h + UI + internet/amazon/amazonsettingspage.ui +) + # Pulse audio integration optional_source(HAVE_LIBPULSE diff --git a/src/config.h.in b/src/config.h.in index f9279072f..342919fbc 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -21,6 +21,7 @@ #define CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}" #cmakedefine ENABLE_VISUALISATIONS +#cmakedefine HAVE_AMAZON_CLOUD_DRIVE #cmakedefine HAVE_AUDIOCD #cmakedefine HAVE_BOX #cmakedefine HAVE_BREAKPAD diff --git a/src/core/database.cpp b/src/core/database.cpp index e351aa32e..e3bbee56a 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -47,7 +47,7 @@ #include const char* Database::kDatabaseFilename = "clementine.db"; -const int Database::kSchemaVersion = 47; +const int Database::kSchemaVersion = 48; const char* Database::kMagicAllSongsTables = "%allsongstables"; int Database::sNextConnectionId = 1; diff --git a/src/engines/gstenginepipeline.cpp b/src/engines/gstenginepipeline.cpp index 7f48f1fb2..5887a6dbd 100644 --- a/src/engines/gstenginepipeline.cpp +++ b/src/engines/gstenginepipeline.cpp @@ -903,6 +903,15 @@ void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin* bin, g_object_set(element, "extra-headers", headers, nullptr); gst_structure_free(headers); } + if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), + "extra-headers") && + instance->url().host().contains("amazonaws.com")) { + GstStructure* headers = gst_structure_new( + "extra-headers", "Authorization", G_TYPE_STRING, + instance->url().fragment().toAscii().data(), nullptr); + g_object_set(element, "extra-headers", headers, nullptr); + gst_structure_free(headers); + } if (g_object_class_find_property(G_OBJECT_GET_CLASS(element), "user-agent")) { QString user_agent = diff --git a/src/internet/amazon/amazonclouddrive.cpp b/src/internet/amazon/amazonclouddrive.cpp new file mode 100644 index 000000000..1574566ee --- /dev/null +++ b/src/internet/amazon/amazonclouddrive.cpp @@ -0,0 +1,179 @@ +#include "internet/amazon/amazonclouddrive.h" + +#include + +#include + +#include "core/application.h" +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/player.h" +#include "internet/core/oauthenticator.h" +#include "internet/amazon/amazonurlhandler.h" +#include "ui/settingsdialog.h" + +const char* AmazonCloudDrive::kServiceName = "Cloud Drive"; +const char* AmazonCloudDrive::kSettingsGroup = "AmazonCloudDrive"; + +namespace { +static const char* kServiceId = "amazon_cloud_drive"; +static const char* kClientId = + "amzn1.application-oa2-client.2b1157a7dadc45c3888567882b3a9f05"; +static const char* kClientSecret = + "acfbf95340cc4c381dd43fb75b5e111882d7fd1b02a02f3013ab124baf8d1655"; +static const char* kOAuthScope = "clouddrive:read"; +static const char* kOAuthEndpoint = "https://www.amazon.com/ap/oa"; +static const char* kOAuthTokenEndpoint = "https://api.amazon.com/auth/o2/token"; + +static const char* kEndpointEndpoint = + "https://drive.amazonaws.com/drive/v1/account/endpoint"; +static const char* kChangesEndpoint = "%1/changes"; +static const char* kDownloadEndpoint = "%1/nodes/%2/content"; +} // namespace + +AmazonCloudDrive::AmazonCloudDrive(Application* app, InternetModel* parent) + : CloudFileService(app, parent, kServiceName, kServiceId, + QIcon(":/providers/amazonclouddrive.png"), + SettingsDialog::Page_AmazonCloudDrive), + network_(new NetworkAccessManager) { + app->player()->RegisterUrlHandler(new AmazonUrlHandler(this, this)); +} + +bool AmazonCloudDrive::has_credentials() const { + QSettings s; + s.beginGroup(kSettingsGroup); + return !s.value("refresh_token").toString().isEmpty(); +} + +QUrl AmazonCloudDrive::GetStreamingUrlFromSongId(const QUrl& url) { + QUrl download_url( + QString(kDownloadEndpoint).arg(content_url_).arg(url.path())); + download_url.setFragment(QString("Bearer %1").arg(access_token_)); + return download_url; +} + +void AmazonCloudDrive::Connect() { + OAuthenticator* oauth = new OAuthenticator( + kClientId, kClientSecret, + // Amazon forbids arbitrary query parameters so REMOTE_WITH_STATE is + // required. + OAuthenticator::RedirectStyle::REMOTE_WITH_STATE, this); + oauth->StartAuthorisation(kOAuthEndpoint, kOAuthTokenEndpoint, kOAuthScope); + NewClosure(oauth, SIGNAL(Finished()), this, + SLOT(ConnectFinished(OAuthenticator*)), oauth); +} + +void AmazonCloudDrive::ForgetCredentials() { + +} + +void AmazonCloudDrive::ConnectFinished(OAuthenticator* oauth) { + oauth->deleteLater(); + + qLog(Debug) << oauth->access_token() + << oauth->expiry_time() + << oauth->refresh_token(); + + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("refresh_token", oauth->refresh_token()); + + access_token_ = oauth->access_token(); + // TODO: Amazon expiry time is only an hour so refresh this regularly. + expiry_time_ = oauth->expiry_time(); + + FetchEndpoint(); +} + +void AmazonCloudDrive::FetchEndpoint() { + QUrl url(kEndpointEndpoint); + QNetworkRequest request(url); + AddAuthorizationHeader(&request); + QNetworkReply* reply = network_->get(request); + NewClosure(reply, SIGNAL(finished()), this, + SLOT(FetchEndpointFinished(QNetworkReply*)), reply); +} + +void AmazonCloudDrive::FetchEndpointFinished(QNetworkReply* reply) { + reply->deleteLater(); + QJson::Parser parser; + QVariantMap response = parser.parse(reply).toMap(); + content_url_ = response["contentUrl"].toString(); + metadata_url_ = response["metadataUrl"].toString(); + qLog(Debug) << "content_url:" << content_url_; + qLog(Debug) << "metadata_url:" << metadata_url_; + RequestChanges(); +} + +void AmazonCloudDrive::RequestChanges() { + QUrl url(QString(kChangesEndpoint).arg(metadata_url_)); + QNetworkRequest request(url); + AddAuthorizationHeader(&request); + QNetworkReply* reply = network_->post(request, QByteArray()); + NewClosure(reply, SIGNAL(finished()), this, + SLOT(RequestChangesFinished(QNetworkReply*)), reply); +} + +void AmazonCloudDrive::RequestChangesFinished(QNetworkReply* reply) { + reply->deleteLater(); + QJson::Parser parser; + QVariantMap response = parser.parse(reply).toMap(); + + QString checkpoint = response["checkpoint"].toString(); + QSettings settings; + settings.beginGroup(kSettingsGroup); + settings.setValue("checkpoint", checkpoint); + + QVariantList nodes = response["nodes"].toList(); + qLog(Debug) << "nodes:" << nodes.length(); + for (const QVariant& n : nodes) { + QVariantMap node = n.toMap(); + + qLog(Debug) << node["kind"] << node["status"]; + + if (node["kind"].toString() == "FOLDER") { + continue; + } + QString status = node["status"].toString(); + if (node["status"].toString() != "AVAILABLE") { + continue; + } + + QVariantMap content_properties = node["contentProperties"].toMap(); + QString mime_type = content_properties["contentType"].toString(); + + QUrl url; + url.setScheme("amazonclouddrive"); + url.setPath(node["id"].toString()); + + qLog(Debug) << url << mime_type; + + if (ShouldIndexFile(url, mime_type)) { + QString node_id = node["id"].toString(); + QUrl content_url( + QString(kDownloadEndpoint).arg(content_url_).arg(node_id)); + QString md5 = content_properties["md5"].toString(); + + Song song; + song.set_url(url); + song.set_etag(md5); + song.set_mtime(node["modifiedDate"].toDateTime().toTime_t()); + song.set_ctime(node["createdDate"].toDateTime().toTime_t()); + song.set_title(node["name"].toString()); + song.set_filesize(content_properties["size"].toInt()); + + qLog(Debug) << "Adding:" + << song.title() + << mime_type + << url + << content_url; + MaybeAddFileToDatabase(song, mime_type, content_url, QString("Bearer %1").arg(access_token_)); + } + } +} + +void AmazonCloudDrive::AddAuthorizationHeader(QNetworkRequest* request) { + request->setRawHeader("Authorization", + QString("Bearer %1").arg(access_token_).toUtf8()); +} diff --git a/src/internet/amazon/amazonclouddrive.h b/src/internet/amazon/amazonclouddrive.h new file mode 100644 index 000000000..f23a46069 --- /dev/null +++ b/src/internet/amazon/amazonclouddrive.h @@ -0,0 +1,53 @@ +#ifndef INTERNET_AMAZON_AMAZON_CLOUD_DRIVE_H_ +#define INTERNET_AMAZON_AMAZON_CLOUD_DRIVE_H_ + +#include "internet/core/cloudfileservice.h" + +#include +#include +#include + +class NetworkAccessManager; +class OAuthenticator; +class QNetworkReply; +class QNetworkRequest; + +class AmazonCloudDrive : public CloudFileService { + Q_OBJECT + public: + AmazonCloudDrive(Application* app, InternetModel* parent); + + static const char* kServiceName; + static const char* kSettingsGroup; + + virtual bool has_credentials() const; + + QUrl GetStreamingUrlFromSongId(const QUrl& url); + + void ForgetCredentials(); + + signals: + void Connected(); + + public slots: + void Connect(); + + private: + void FetchEndpoint(); + void RequestChanges(); + void AddAuthorizationHeader(QNetworkRequest* request); + + private slots: + void ConnectFinished(OAuthenticator*); + void FetchEndpointFinished(QNetworkReply*); + void RequestChangesFinished(QNetworkReply*); + + private: + NetworkAccessManager* network_; + QString access_token_; + QDateTime expiry_time_; + QString content_url_; + QString metadata_url_; +}; + +#endif // INTERNET_AMAZON_AMAZON_CLOUD_DRIVE_H_ diff --git a/src/internet/amazon/amazonsettingspage.cpp b/src/internet/amazon/amazonsettingspage.cpp new file mode 100644 index 000000000..8e5a03bf1 --- /dev/null +++ b/src/internet/amazon/amazonsettingspage.cpp @@ -0,0 +1,75 @@ +/* This file is part of Clementine. + Copyright 2012, 2014, John Maguire + Copyright 2014, Krzysztof Sobiecki + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include "amazonsettingspage.h" +#include "ui_amazonsettingspage.h" + +#include "core/application.h" +#include "internet/amazon/amazonclouddrive.h" +#include "internet/core/internetmodel.h" +#include "ui/settingsdialog.h" + +AmazonSettingsPage::AmazonSettingsPage(SettingsDialog* parent) + : SettingsPage(parent), + ui_(new Ui::AmazonSettingsPage), + service_(dialog()->app()->internet_model()->Service()) { + ui_->setupUi(this); + ui_->login_state->AddCredentialGroup(ui_->login_container); + + connect(ui_->login_button, SIGNAL(clicked()), SLOT(LoginClicked())); + connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked())); + + dialog()->installEventFilter(this); +} + +AmazonSettingsPage::~AmazonSettingsPage() { delete ui_; } + +void AmazonSettingsPage::Load() { + QSettings s; + s.beginGroup(AmazonCloudDrive::kSettingsGroup); + + const QString name = s.value("name").toString(); + + if (!name.isEmpty()) { + ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn, name); + } +} + +void AmazonSettingsPage::Save() { + QSettings s; + s.beginGroup(AmazonCloudDrive::kSettingsGroup); +} + +void AmazonSettingsPage::LoginClicked() { + service_->Connect(); + ui_->login_button->setEnabled(false); +} + +bool AmazonSettingsPage::eventFilter(QObject* object, QEvent* event) { + if (object == dialog() && event->type() == QEvent::Enter) { + ui_->login_button->setEnabled(true); + return false; + } + + return SettingsPage::eventFilter(object, event); +} + +void AmazonSettingsPage::LogoutClicked() { + service_->ForgetCredentials(); + ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut); +} diff --git a/src/internet/amazon/amazonsettingspage.h b/src/internet/amazon/amazonsettingspage.h new file mode 100644 index 000000000..02350ee7d --- /dev/null +++ b/src/internet/amazon/amazonsettingspage.h @@ -0,0 +1,53 @@ +/* This file is part of Clementine. + Copyright 2012, 2014, John Maguire + Copyright 2014, Krzysztof Sobiecki + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#ifndef INTERNET_AMAZON_AMAZONSETTINGSPAGE_H_ +#define INTERNET_AMAZON_AMAZONSETTINGSPAGE_H_ + +#include "ui/settingspage.h" + +#include +#include + +class AmazonCloudDrive; +class Ui_AmazonSettingsPage; + +class AmazonSettingsPage : public SettingsPage { + Q_OBJECT + + public: + explicit AmazonSettingsPage(SettingsDialog* parent = nullptr); + ~AmazonSettingsPage(); + + void Load(); + void Save(); + + // QObject + bool eventFilter(QObject* object, QEvent* event); + + private slots: + void LoginClicked(); + void LogoutClicked(); + + private: + Ui_AmazonSettingsPage* ui_; + + AmazonCloudDrive* service_; +}; + +#endif // INTERNET_AMAZON_AMAZONSETTINGSPAGE_H_ diff --git a/src/internet/amazon/amazonsettingspage.ui b/src/internet/amazon/amazonsettingspage.ui new file mode 100644 index 000000000..d496f3424 --- /dev/null +++ b/src/internet/amazon/amazonsettingspage.ui @@ -0,0 +1,110 @@ + + + AmazonSettingsPage + + + + 0 + 0 + 569 + 491 + + + + Amazon + + + + :/providers/amazonclouddrive.png:/providers/amazonclouddrive.png + + + + + + Clementine can play music that you have uploaded to Amazon Cloud Drive + + + true + + + + + + + + + + + 28 + + + 0 + + + 0 + + + + + + + Login + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Clicking the Login button will open a web browser. You should return to Clementine after you have logged in. + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 357 + + + + + + + + + LoginStateWidget + QWidget +
widgets/loginstatewidget.h
+ 1 +
+
+ + + + +
diff --git a/src/internet/amazon/amazonurlhandler.cpp b/src/internet/amazon/amazonurlhandler.cpp new file mode 100644 index 000000000..8455679b5 --- /dev/null +++ b/src/internet/amazon/amazonurlhandler.cpp @@ -0,0 +1,11 @@ +#include "internet/amazon/amazonurlhandler.h" + +#include "internet/amazon/amazonclouddrive.h" + +AmazonUrlHandler::AmazonUrlHandler(AmazonCloudDrive* service, QObject* parent) + : UrlHandler(parent), service_(service) {} + +UrlHandler::LoadResult AmazonUrlHandler::StartLoading(const QUrl& url) { + return LoadResult(url, LoadResult::TrackAvailable, + service_->GetStreamingUrlFromSongId(url)); +} diff --git a/src/internet/amazon/amazonurlhandler.h b/src/internet/amazon/amazonurlhandler.h new file mode 100644 index 000000000..cea61d94a --- /dev/null +++ b/src/internet/amazon/amazonurlhandler.h @@ -0,0 +1,22 @@ +#ifndef INTERNET_AMAZON_AMAZONURLHANDLER_H_ +#define INTERNET_AMAZON_AMAZONURLHANDLER_H_ + +#include "core/urlhandler.h" + +class AmazonCloudDrive; + +class AmazonUrlHandler : public UrlHandler { + Q_OBJECT + public: + explicit AmazonUrlHandler( + AmazonCloudDrive* service, QObject* parent = nullptr); + + QString scheme() const { return "amazonclouddrive"; } + QIcon icon() const { return QIcon(":providers/amazonclouddrive.png"); } + LoadResult StartLoading(const QUrl& url); + + private: + AmazonCloudDrive* service_; +}; + +#endif // INTERNET_AMAZON_AMAZONURLHANDLER_H_ diff --git a/src/internet/amazon/dropboxsettingspage.ui b/src/internet/amazon/dropboxsettingspage.ui new file mode 100644 index 000000000..07b9b458b --- /dev/null +++ b/src/internet/amazon/dropboxsettingspage.ui @@ -0,0 +1,110 @@ + + + DropboxSettingsPage + + + + 0 + 0 + 569 + 491 + + + + Dropbox + + + + :/providers/dropbox.png:/providers/dropbox.png + + + + + + Clementine can play music that you have uploaded to Dropbox + + + true + + + + + + + + + + + 28 + + + 0 + + + 0 + + + + + + + Login + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Clicking the Login button will open a web browser. You should return to Clementine after you have logged in. + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 357 + + + + + + + + + LoginStateWidget + QWidget +
widgets/loginstatewidget.h
+ 1 +
+
+ + + + +
diff --git a/src/internet/core/internetmodel.cpp b/src/internet/core/internetmodel.cpp index 4783466ee..09754cd5e 100644 --- a/src/internet/core/internetmodel.cpp +++ b/src/internet/core/internetmodel.cpp @@ -64,6 +64,9 @@ #ifdef HAVE_SEAFILE #include "internet/seafile/seafileservice.h" #endif +#ifdef HAVE_AMAZON_CLOUD_DRIVE +#include "internet/amazon/amazonclouddrive.h" +#endif using smart_playlists::Generator; using smart_playlists::GeneratorMimeData; @@ -117,6 +120,9 @@ InternetModel::InternetModel(Application* app, QObject* parent) #ifdef HAVE_VK AddService(new VkService(app, this)); #endif +#ifdef HAVE_AMAZON_CLOUD_DRIVE + AddService(new AmazonCloudDrive(app, this)); +#endif invisibleRootItem()->sortChildren(0, Qt::AscendingOrder); UpdateServices(); diff --git a/src/internet/core/oauthenticator.cpp b/src/internet/core/oauthenticator.cpp index 8996da5f4..7ad11b405 100644 --- a/src/internet/core/oauthenticator.cpp +++ b/src/internet/core/oauthenticator.cpp @@ -61,10 +61,16 @@ void OAuthenticator::StartAuthorisation(const QString& oauth_endpoint, } else if (redirect_style_ == RedirectStyle::REMOTE_WITH_STATE) { redirect_url = QUrl(kRemoteURL); url.addQueryItem("state", port); + } else if (redirect_style_ == RedirectStyle::REMOTE_WITH_FRAGMENT) { + redirect_url = QUrl(kRemoteURL); + redirect_url.setUserName(port); } else { redirect_url = server->url(); } + qLog(Debug) << url + << redirect_url; + url.addQueryItem("redirect_uri", redirect_url.toString()); url.addQueryItem("scope", scope); diff --git a/src/internet/core/oauthenticator.h b/src/internet/core/oauthenticator.h index 944838a14..8b2d5b985 100644 --- a/src/internet/core/oauthenticator.h +++ b/src/internet/core/oauthenticator.h @@ -43,7 +43,9 @@ class OAuthenticator : public QObject { // 'state' parameter of the URL, for services which allow only redirect URL // without parameters (e.g. SoundCloud). "state" parameter will be added to // the redirect URL by the service itself. - REMOTE_WITH_STATE = 2 + REMOTE_WITH_STATE = 2, + + REMOTE_WITH_FRAGMENT = 3, }; OAuthenticator(const QString& client_id, const QString& client_secret, diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index fd81eaa75..3d10d577a 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -821,7 +821,7 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd, connect(ui_->action_kittens, SIGNAL(toggled(bool)), app_->network_remote(), SLOT(EnableKittens(bool))); // Hide the console - // connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole())); + connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole())); NowPlayingWidgetPositionChanged(ui_->now_playing->show_above_status_bar()); // Load theme diff --git a/src/ui/mainwindow.ui b/src/ui/mainwindow.ui index 343c24c89..622154d7e 100644 --- a/src/ui/mainwindow.ui +++ b/src/ui/mainwindow.ui @@ -461,6 +461,7 @@ + diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index b82e00a27..5cfb1bda8 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -84,6 +84,10 @@ #include "internet/seafile/seafilesettingspage.h" #endif +#ifdef HAVE_AMAZON_CLOUD_DRIVE +#include "internet/amazon/amazonsettingspage.h" +#endif + #include #include #include @@ -193,6 +197,10 @@ SettingsDialog::SettingsDialog(Application* app, BackgroundStreams* streams, AddPage(Page_Seafile, new SeafileSettingsPage(this), providers); #endif +#ifdef HAVE_AMAZON_CLOUD_DRIVE + AddPage(Page_AmazonCloudDrive, new AmazonSettingsPage(this), providers); +#endif + AddPage(Page_Magnatune, new MagnatuneSettingsPage(this), providers); AddPage(Page_DigitallyImported, new DigitallyImportedSettingsPage(this), providers); diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index 2c4a29db6..607ecc0e8 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -86,7 +86,8 @@ class SettingsDialog : public QDialog { Page_Box, Page_Vk, Page_Seafile, - Page_InternetShow + Page_InternetShow, + Page_AmazonCloudDrive, }; enum Role { Role_IsSeparator = Qt::UserRole };