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 {