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 000000000..d3707287a
Binary files /dev/null and b/data/providers/amazonclouddrive.png differ
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 @@
+