diff --git a/CMakeLists.txt b/CMakeLists.txt
index 97e6c5f21..70f9e6102 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -214,6 +214,11 @@ optional_component(SKYDRIVE ON "Skydrive support"
DEPENDS "Taglib 1.8" "TAGLIB_VERSION VERSION_GREATER 1.7.999"
)
+optional_component(BOX ON "Box 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 50b68b300..0269dd706 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -273,6 +273,7 @@
providers/amazon.png
providers/aol.png
providers/bbc.png
+ providers/box.png
providers/cdbaby.png
providers/digitallyimported-32.png
providers/digitallyimported.png
@@ -341,6 +342,7 @@
schema/schema-41.sql
schema/schema-42.sql
schema/schema-43.sql
+ schema/schema-44.sql
schema/schema-4.sql
schema/schema-5.sql
schema/schema-6.sql
diff --git a/data/providers/box.png b/data/providers/box.png
new file mode 100644
index 000000000..bff20dc30
Binary files /dev/null and b/data/providers/box.png differ
diff --git a/data/schema/schema-44.sql b/data/schema/schema-44.sql
new file mode 100644
index 000000000..89a92b8ca
--- /dev/null
+++ b/data/schema/schema-44.sql
@@ -0,0 +1,48 @@
+CREATE TABLE box_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
+);
+
+CREATE VIRTUAL TABLE box_songs_fts USING fts3 (
+ ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsgenre, ftscomment,
+ tokenize=unicode
+);
+
+UPDATE schema_version SET version=44;
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 72dc7e2a8..f1dca0ace 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1146,6 +1146,20 @@ optional_source(HAVE_SKYDRIVE
internet/skydriveurlhandler.h
)
+# Box support
+optional_source(HAVE_BOX
+ SOURCES
+ internet/boxservice.cpp
+ internet/boxsettingspage.cpp
+ internet/boxurlhandler.cpp
+ HEADERS
+ internet/boxservice.h
+ internet/boxsettingspage.h
+ internet/boxurlhandler.h
+ UI
+ internet/boxsettingspage.ui
+)
+
# Hack to add Clementine to the Unity system tray whitelist
optional_source(LINUX
SOURCES core/ubuntuunityhack.cpp
diff --git a/src/config.h.in b/src/config.h.in
index f84526c67..8088eec90 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -22,6 +22,7 @@
#cmakedefine ENABLE_VISUALISATIONS
#cmakedefine HAVE_AUDIOCD
+#cmakedefine HAVE_BOX
#cmakedefine HAVE_BREAKPAD
#cmakedefine HAVE_DBUS
#cmakedefine HAVE_DEVICEKIT
diff --git a/src/core/database.cpp b/src/core/database.cpp
index 9eef7c8b1..ccbabe394 100644
--- a/src/core/database.cpp
+++ b/src/core/database.cpp
@@ -37,7 +37,7 @@
#include
const char* Database::kDatabaseFilename = "clementine.db";
-const int Database::kSchemaVersion = 43;
+const int Database::kSchemaVersion = 44;
const char* Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1;
diff --git a/src/internet/boxservice.cpp b/src/internet/boxservice.cpp
new file mode 100644
index 000000000..f4ec9af9a
--- /dev/null
+++ b/src/internet/boxservice.cpp
@@ -0,0 +1,312 @@
+#include "boxservice.h"
+
+#include
+
+#include "core/application.h"
+#include "core/player.h"
+#include "core/waitforsignal.h"
+#include "internet/boxurlhandler.h"
+#include "internet/oauthenticator.h"
+#include "library/librarybackend.h"
+
+const char* BoxService::kServiceName = "Box";
+const char* BoxService::kSettingsGroup = "Box";
+
+namespace {
+
+static const char* kClientId = "gbswb9wp7gjyldc3qrw68h2rk68jaf4h";
+static const char* kClientSecret = "pZ6cUCQz5X0xaWoPVbCDg6GpmfTtz73s";
+
+static const char* kOAuthEndpoint =
+ "https://api.box.com/oauth2/authorize";
+static const char* kOAuthTokenEndpoint =
+ "https://api.box.com/oauth2/token";
+
+static const char* kUserInfo =
+ "https://api.box.com/2.0/users/me";
+static const char* kFolderItems =
+ "https://api.box.com/2.0/folders/%1/items";
+static const int kRootFolderId = 0;
+
+static const char* kFileContent =
+ "https://api.box.com/2.0/files/%1/content";
+
+static const char* kEvents =
+ "https://api.box.com/2.0/events";
+
+}
+
+BoxService::BoxService(Application* app, InternetModel* parent)
+ : CloudFileService(
+ app, parent,
+ kServiceName, kSettingsGroup,
+ QIcon(":/providers/box.png"),
+ SettingsDialog::Page_Box) {
+ app->player()->RegisterUrlHandler(new BoxUrlHandler(this, this));
+}
+
+bool BoxService::has_credentials() const {
+ return !refresh_token().isEmpty();
+}
+
+QString BoxService::refresh_token() const {
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+
+ return s.value("refresh_token").toString();
+}
+
+bool BoxService::is_authenticated() const {
+ return !access_token_.isEmpty() &&
+ QDateTime::currentDateTime().secsTo(expiry_time_) > 0;
+}
+
+void BoxService::EnsureConnected() {
+ if (is_authenticated()) {
+ return;
+ }
+
+ Connect();
+ WaitForSignal(this, SIGNAL(Connected()));
+}
+
+void BoxService::Connect() {
+ OAuthenticator* oauth = new OAuthenticator(
+ kClientId, kClientSecret, OAuthenticator::RedirectStyle::LOCALHOST, this);
+ if (!refresh_token().isEmpty()) {
+ oauth->RefreshAuthorisation(
+ kOAuthTokenEndpoint, refresh_token());
+ } else {
+ oauth->StartAuthorisation(
+ kOAuthEndpoint,
+ kOAuthTokenEndpoint,
+ QString::null);
+ }
+
+ NewClosure(oauth, SIGNAL(Finished()),
+ this, SLOT(ConnectFinished(OAuthenticator*)), oauth);
+}
+
+void BoxService::ConnectFinished(OAuthenticator* oauth) {
+ oauth->deleteLater();
+
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+ s.setValue("refresh_token", oauth->refresh_token());
+
+ access_token_ = oauth->access_token();
+ expiry_time_ = oauth->expiry_time();
+
+ if (s.value("name").toString().isEmpty()) {
+ QUrl url(kUserInfo);
+ QNetworkRequest request(url);
+ AddAuthorizationHeader(&request);
+
+ QNetworkReply* reply = network_->get(request);
+ NewClosure(reply, SIGNAL(finished()),
+ this, SLOT(FetchUserInfoFinished(QNetworkReply*)), reply);
+ } else {
+ emit Connected();
+ }
+ UpdateFiles();
+}
+
+void BoxService::AddAuthorizationHeader(QNetworkRequest* request) const {
+ request->setRawHeader(
+ "Authorization", QString("Bearer %1").arg(access_token_).toUtf8());
+}
+
+void BoxService::FetchUserInfoFinished(QNetworkReply* reply) {
+ reply->deleteLater();
+
+ QJson::Parser parser;
+ QVariantMap response = parser.parse(reply).toMap();
+
+ QString name = response["name"].toString();
+ if (!name.isEmpty()) {
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+ s.setValue("name", name);
+ }
+
+ emit Connected();
+}
+
+void BoxService::ForgetCredentials() {
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+
+ s.remove("refresh_token");
+ s.remove("name");
+}
+
+void BoxService::UpdateFiles() {
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+
+ if (!s.value("cursor").toString().isEmpty()) {
+ // Use events API to fetch changes.
+ UpdateFilesFromCursor(s.value("cursor").toString());
+ return;
+ }
+
+ // First run we scan as events may not cover everything.
+ FetchRecursiveFolderItems(kRootFolderId);
+ InitialiseEventsCursor();
+}
+
+void BoxService::InitialiseEventsCursor() {
+ QUrl url(kEvents);
+ url.addQueryItem("stream_position", "now");
+ QNetworkRequest request(url);
+ AddAuthorizationHeader(&request);
+ QNetworkReply* reply = network_->get(request);
+ NewClosure(reply, SIGNAL(finished()),
+ this, SLOT(InitialiseEventsFinished(QNetworkReply*)), reply);
+}
+
+void BoxService::InitialiseEventsFinished(QNetworkReply* reply) {
+ reply->deleteLater();
+ QJson::Parser parser;
+ QVariantMap response = parser.parse(reply).toMap();
+ if (response.contains("next_stream_position")) {
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+ s.setValue("cursor", response["next_stream_position"]);
+ }
+}
+
+void BoxService::FetchRecursiveFolderItems(const int folder_id) {
+ // TODO: Page through large folders.
+ QUrl url(QString(kFolderItems).arg(folder_id));
+ QStringList fields;
+ fields << "etag"
+ << "size"
+ << "created_at"
+ << "modified_at"
+ << "name";
+ QString fields_list = fields.join(",");
+ url.addQueryItem("fields", fields_list);
+ QNetworkRequest request(url);
+ AddAuthorizationHeader(&request);
+ QNetworkReply* reply = network_->get(request);
+ NewClosure(reply, SIGNAL(finished()),
+ this, SLOT(FetchFolderItemsFinished(QNetworkReply*)), reply);
+}
+
+void BoxService::FetchFolderItemsFinished(QNetworkReply* reply) {
+ reply->deleteLater();
+
+ QByteArray data = reply->readAll();
+
+ QJson::Parser parser;
+ QVariantMap response = parser.parse(data).toMap();
+
+ QVariantList entries = response["entries"].toList();
+ foreach (const QVariant& e, entries) {
+ QVariantMap entry = e.toMap();
+ if (entry["type"].toString() == "folder") {
+ FetchRecursiveFolderItems(entry["id"].toInt());
+ } else {
+ MaybeAddFileEntry(entry);
+ }
+ }
+}
+
+void BoxService::MaybeAddFileEntry(const QVariantMap& entry) {
+ QString mime_type = GuessMimeTypeForFile(entry["name"].toString());
+ QUrl url;
+ url.setScheme("box");
+ url.setPath(entry["id"].toString());
+
+ Song song;
+ song.set_url(url);
+ song.set_ctime(entry["created_at"].toDateTime().toTime_t());
+ song.set_mtime(entry["modified_at"].toDateTime().toTime_t());
+ song.set_filesize(entry["size"].toInt());
+ song.set_title(entry["name"].toString());
+
+ // This is actually a redirect. Follow it now.
+ QNetworkReply* reply = FetchContentUrlForFile(entry["id"].toString());
+ NewClosure(reply, SIGNAL(finished()),
+ this, SLOT(RedirectFollowed(QNetworkReply*, Song, QString)),
+ reply, song, mime_type);
+}
+
+QNetworkReply* BoxService::FetchContentUrlForFile(const QString& file_id) {
+ QUrl content_url(QString(kFileContent).arg(file_id));
+ QNetworkRequest request(content_url);
+ AddAuthorizationHeader(&request);
+ QNetworkReply* reply = network_->get(request);
+ return reply;
+}
+
+void BoxService::RedirectFollowed(
+ QNetworkReply* reply, const Song& song, const QString& mime_type) {
+ reply->deleteLater();
+ QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
+ if (!redirect.isValid()) {
+ return;
+ }
+
+ QUrl real_url = redirect.toUrl();
+ MaybeAddFileToDatabase(
+ song,
+ mime_type,
+ real_url,
+ QString("Bearer %1").arg(access_token_));
+}
+
+void BoxService::UpdateFilesFromCursor(const QString& cursor) {
+ QUrl url(kEvents);
+ url.addQueryItem("stream_position", cursor);
+ url.addQueryItem("limit", "5000");
+ QNetworkRequest request(url);
+ AddAuthorizationHeader(&request);
+ QNetworkReply* reply = network_->get(request);
+ NewClosure(reply, SIGNAL(finished()),
+ this, SLOT(FetchEventsFinished(QNetworkReply*)), reply);
+}
+
+void BoxService::FetchEventsFinished(QNetworkReply* reply) {
+ // TODO: Page through events.
+ reply->deleteLater();
+ QJson::Parser parser;
+ QVariantMap response = parser.parse(reply).toMap();
+
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+ s.setValue("cursor", response["next_stream_position"]);
+
+ QVariantList entries = response["entries"].toList();
+ foreach (const QVariant& e, entries) {
+ QVariantMap event = e.toMap();
+ QString type = event["event_type"].toString();
+ QVariantMap source = event["source"].toMap();
+ if (source["type"] == "file") {
+ if (type == "ITEM_UPLOAD") {
+ // Add file.
+ MaybeAddFileEntry(source);
+ } else if (type == "ITEM_TRASH") {
+ // Delete file.
+ QUrl url;
+ url.setScheme("box");
+ url.setPath(source["id"].toString());
+ Song song = library_backend_->GetSongByUrl(url);
+ if (song.is_valid()) {
+ library_backend_->DeleteSongs(SongList() << song);
+ }
+ }
+ }
+ }
+}
+
+QUrl BoxService::GetStreamingUrlFromSongId(const QString& id) {
+ EnsureConnected();
+ QNetworkReply* reply = FetchContentUrlForFile(id);
+ WaitForSignal(reply, SIGNAL(finished()));
+ reply->deleteLater();
+ QUrl real_url =
+ reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ return real_url;
+}
diff --git a/src/internet/boxservice.h b/src/internet/boxservice.h
new file mode 100644
index 000000000..97583ac30
--- /dev/null
+++ b/src/internet/boxservice.h
@@ -0,0 +1,55 @@
+#ifndef BOXSERVICE_H
+#define BOXSERVICE_H
+
+#include "cloudfileservice.h"
+
+#include
+
+class OAuthenticator;
+class QNetworkReply;
+class QNetworkRequest;
+
+class BoxService : public CloudFileService {
+ Q_OBJECT
+ public:
+ BoxService(Application* app, InternetModel* parent);
+
+ static const char* kServiceName;
+ static const char* kSettingsGroup;
+
+ virtual bool has_credentials() const;
+ QUrl GetStreamingUrlFromSongId(const QString& id);
+
+ public slots:
+ void Connect();
+ void ForgetCredentials();
+
+ signals:
+ void Connected();
+
+ private slots:
+ void ConnectFinished(OAuthenticator* oauth);
+ void FetchUserInfoFinished(QNetworkReply* reply);
+ void FetchFolderItemsFinished(QNetworkReply* reply);
+ void RedirectFollowed(
+ QNetworkReply* reply, const Song& song, const QString& mime_type);
+ void InitialiseEventsFinished(QNetworkReply* reply);
+ void FetchEventsFinished(QNetworkReply* reply);
+
+ private:
+ QString refresh_token() const;
+ bool is_authenticated() const;
+ void AddAuthorizationHeader(QNetworkRequest* request) const;
+ void UpdateFiles();
+ void FetchRecursiveFolderItems(const int folder_id);
+ void UpdateFilesFromCursor(const QString& cursor);
+ QNetworkReply* FetchContentUrlForFile(const QString& file_id);
+ void InitialiseEventsCursor();
+ void MaybeAddFileEntry(const QVariantMap& entry);
+ void EnsureConnected();
+
+ QString access_token_;
+ QDateTime expiry_time_;
+};
+
+#endif // BOXSERVICE_H
diff --git a/src/internet/boxsettingspage.cpp b/src/internet/boxsettingspage.cpp
new file mode 100644
index 000000000..3874829a5
--- /dev/null
+++ b/src/internet/boxsettingspage.cpp
@@ -0,0 +1,89 @@
+/* This file is part of Clementine.
+ Copyright 2013, John Maguire
+
+ 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 "boxsettingspage.h"
+
+#include
+
+#include "ui_boxsettingspage.h"
+#include "core/application.h"
+#include "internet/boxservice.h"
+#include "internet/internetmodel.h"
+#include "ui/settingsdialog.h"
+
+BoxSettingsPage::BoxSettingsPage(SettingsDialog* parent)
+ : SettingsPage(parent),
+ ui_(new Ui::BoxSettingsPage),
+ 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()));
+ connect(service_, SIGNAL(Connected()), SLOT(Connected()));
+
+ dialog()->installEventFilter(this);
+}
+
+BoxSettingsPage::~BoxSettingsPage() {
+ delete ui_;
+}
+
+void BoxSettingsPage::Load() {
+ QSettings s;
+ s.beginGroup(BoxService::kSettingsGroup);
+
+ const QString name = s.value("name").toString();
+
+ if (!name.isEmpty()) {
+ ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn, name);
+ }
+}
+
+void BoxSettingsPage::Save() {
+ QSettings s;
+ s.beginGroup(BoxService::kSettingsGroup);
+}
+
+void BoxSettingsPage::LoginClicked() {
+ service_->Connect();
+ ui_->login_button->setEnabled(false);
+}
+
+bool BoxSettingsPage::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 BoxSettingsPage::LogoutClicked() {
+ service_->ForgetCredentials();
+ ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
+}
+
+void BoxSettingsPage::Connected() {
+ QSettings s;
+ s.beginGroup(BoxService::kSettingsGroup);
+
+ const QString name = s.value("name").toString();
+
+ ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn, name);
+}
diff --git a/src/internet/boxsettingspage.h b/src/internet/boxsettingspage.h
new file mode 100644
index 000000000..13b46c34f
--- /dev/null
+++ b/src/internet/boxsettingspage.h
@@ -0,0 +1,53 @@
+/* This file is part of Clementine.
+ Copyright 2013, John Maguire
+
+ 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 BOXSETTINGSPAGE_H
+#define BOXSETTINGSPAGE_H
+
+#include "ui/settingspage.h"
+
+#include
+#include
+
+class BoxService;
+class Ui_BoxSettingsPage;
+
+class BoxSettingsPage : public SettingsPage {
+ Q_OBJECT
+
+public:
+ BoxSettingsPage(SettingsDialog* parent = 0);
+ ~BoxSettingsPage();
+
+ void Load();
+ void Save();
+
+ // QObject
+ bool eventFilter(QObject* object, QEvent* event);
+
+private slots:
+ void LoginClicked();
+ void LogoutClicked();
+ void Connected();
+
+private:
+ Ui_BoxSettingsPage* ui_;
+
+ BoxService* service_;
+};
+
+#endif // BOXSETTINGSPAGE_H
diff --git a/src/internet/boxsettingspage.ui b/src/internet/boxsettingspage.ui
new file mode 100644
index 000000000..45352db8c
--- /dev/null
+++ b/src/internet/boxsettingspage.ui
@@ -0,0 +1,110 @@
+
+
+ BoxSettingsPage
+
+
+
+ 0
+ 0
+ 569
+ 491
+
+
+
+ Box
+
+
+
+ :/providers/box.png:/providers/box.png
+
+
+ -
+
+
+ Clementine can play music that you have uploaded to Box
+
+
+ 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/boxurlhandler.cpp b/src/internet/boxurlhandler.cpp
new file mode 100644
index 000000000..d296e6ba6
--- /dev/null
+++ b/src/internet/boxurlhandler.cpp
@@ -0,0 +1,14 @@
+#include "boxurlhandler.h"
+
+#include "boxservice.h"
+
+BoxUrlHandler::BoxUrlHandler(BoxService* service, QObject* parent)
+ : UrlHandler(parent),
+ service_(service) {
+}
+
+UrlHandler::LoadResult BoxUrlHandler::StartLoading(const QUrl& url) {
+ QString file_id = url.path();
+ QUrl real_url = service_->GetStreamingUrlFromSongId(file_id);
+ return LoadResult(url, LoadResult::TrackAvailable, real_url);
+}
diff --git a/src/internet/boxurlhandler.h b/src/internet/boxurlhandler.h
new file mode 100644
index 000000000..c004bbbca
--- /dev/null
+++ b/src/internet/boxurlhandler.h
@@ -0,0 +1,21 @@
+#ifndef BOXURLHANDLER_H
+#define BOXURLHANDLER_H
+
+#include "core/urlhandler.h"
+
+class BoxService;
+
+class BoxUrlHandler : public UrlHandler {
+ Q_OBJECT
+ public:
+ BoxUrlHandler(BoxService* service, QObject* parent = 0);
+
+ QString scheme() const { return "box"; }
+ QIcon icon() const { return QIcon(":/providers/box.png"); }
+ LoadResult StartLoading(const QUrl& url);
+
+ private:
+ BoxService* service_;
+};
+
+#endif // BOXURLHANDLER_H
diff --git a/src/internet/googledriveservice.h b/src/internet/googledriveservice.h
index 5c6ef6d2a..a5760902b 100644
--- a/src/internet/googledriveservice.h
+++ b/src/internet/googledriveservice.h
@@ -3,8 +3,6 @@
#include "cloudfileservice.h"
-#include "core/tagreaderclient.h"
-
namespace google_drive {
class Client;
class ConnectResponse;
diff --git a/src/internet/internetmodel.cpp b/src/internet/internetmodel.cpp
index f2b418a7b..a4e10dc2d 100644
--- a/src/internet/internetmodel.cpp
+++ b/src/internet/internetmodel.cpp
@@ -56,6 +56,9 @@
#ifdef HAVE_SKYDRIVE
#include "skydriveservice.h"
#endif
+#ifdef HAVE_BOX
+ #include "boxservice.h"
+#endif
using smart_playlists::Generator;
using smart_playlists::GeneratorMimeData;
@@ -106,6 +109,9 @@ InternetModel::InternetModel(Application* app, QObject* parent)
#ifdef HAVE_SKYDRIVE
AddService(new SkydriveService(app, this));
#endif
+#ifdef HAVE_BOX
+ AddService(new BoxService(app, this));
+#endif
}
void InternetModel::AddService(InternetService *service) {
diff --git a/src/internet/oauthenticator.cpp b/src/internet/oauthenticator.cpp
index 42b161de6..b8b8129d8 100644
--- a/src/internet/oauthenticator.cpp
+++ b/src/internet/oauthenticator.cpp
@@ -139,7 +139,6 @@ void OAuthenticator::RefreshAuthorisation(
params.append(QString("%1=%2").arg(p.first, QString(QUrl::toPercentEncoding(p.second))));
}
QString post_data = params.join("&");
- qLog(Debug) << "Refresh post data:" << post_data;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader,
@@ -152,7 +151,7 @@ void OAuthenticator::RefreshAuthorisation(
void OAuthenticator::SetExpiryTime(int expires_in_seconds) {
// Set the expiry time with two minutes' grace.
expiry_time_ = QDateTime::currentDateTime().addSecs(expires_in_seconds - 120);
- qLog(Debug) << "Current Google Drive token expires at:" << expiry_time_;
+ qLog(Debug) << "Current oauth access token expires at:" << expiry_time_;
}
void OAuthenticator::RefreshAccessTokenFinished(QNetworkReply* reply) {
@@ -162,6 +161,7 @@ void OAuthenticator::RefreshAccessTokenFinished(QNetworkReply* reply) {
QVariantMap result = parser.parse(reply, &ok).toMap();
access_token_ = result["access_token"].toString();
+ refresh_token_ = result["refresh_token"].toString();
SetExpiryTime(result["expires_in"].toInt());
emit Finished();
}
diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp
index 5614e798d..cbfa9c8ed 100644
--- a/src/ui/settingsdialog.cpp
+++ b/src/ui/settingsdialog.cpp
@@ -74,6 +74,10 @@
# include "internet/dropboxsettingspage.h"
#endif
+#ifdef HAVE_BOX
+# include "internet/boxsettingspage.h"
+#endif
+
#include
#include
#include
@@ -167,6 +171,10 @@ SettingsDialog::SettingsDialog(Application* app, BackgroundStreams* streams, QWi
AddPage(Page_Dropbox, new DropboxSettingsPage(this), providers);
#endif
+#ifdef HAVE_BOX
+ AddPage(Page_Box, new BoxSettingsPage(this), providers);
+#endif
+
#ifdef HAVE_SPOTIFY
AddPage(Page_Spotify, new SpotifySettingsPage(this), providers);
#endif
diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h
index c34619e76..a21e26546 100644
--- a/src/ui/settingsdialog.h
+++ b/src/ui/settingsdialog.h
@@ -82,6 +82,7 @@ public:
Page_UbuntuOne,
Page_Dropbox,
Page_Skydrive,
+ Page_Box,
};
enum Role {