Add support for Box.
This commit is contained in:
parent
91233dd8d1
commit
d21fa8cc67
@ -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
|
||||
)
|
||||
|
@ -273,6 +273,7 @@
|
||||
<file>providers/amazon.png</file>
|
||||
<file>providers/aol.png</file>
|
||||
<file>providers/bbc.png</file>
|
||||
<file>providers/box.png</file>
|
||||
<file>providers/cdbaby.png</file>
|
||||
<file>providers/digitallyimported-32.png</file>
|
||||
<file>providers/digitallyimported.png</file>
|
||||
@ -341,6 +342,7 @@
|
||||
<file>schema/schema-41.sql</file>
|
||||
<file>schema/schema-42.sql</file>
|
||||
<file>schema/schema-43.sql</file>
|
||||
<file>schema/schema-44.sql</file>
|
||||
<file>schema/schema-4.sql</file>
|
||||
<file>schema/schema-5.sql</file>
|
||||
<file>schema/schema-6.sql</file>
|
||||
|
BIN
data/providers/box.png
Normal file
BIN
data/providers/box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
48
data/schema/schema-44.sql
Normal file
48
data/schema/schema-44.sql
Normal file
@ -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;
|
@ -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
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#cmakedefine ENABLE_VISUALISATIONS
|
||||
#cmakedefine HAVE_AUDIOCD
|
||||
#cmakedefine HAVE_BOX
|
||||
#cmakedefine HAVE_BREAKPAD
|
||||
#cmakedefine HAVE_DBUS
|
||||
#cmakedefine HAVE_DEVICEKIT
|
||||
|
@ -37,7 +37,7 @@
|
||||
#include <QVariant>
|
||||
|
||||
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;
|
||||
|
312
src/internet/boxservice.cpp
Normal file
312
src/internet/boxservice.cpp
Normal file
@ -0,0 +1,312 @@
|
||||
#include "boxservice.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#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;
|
||||
}
|
55
src/internet/boxservice.h
Normal file
55
src/internet/boxservice.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef BOXSERVICE_H
|
||||
#define BOXSERVICE_H
|
||||
|
||||
#include "cloudfileservice.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
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
|
89
src/internet/boxsettingspage.cpp
Normal file
89
src/internet/boxsettingspage.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2013, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boxsettingspage.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#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<BoxService>())
|
||||
{
|
||||
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);
|
||||
}
|
53
src/internet/boxsettingspage.h
Normal file
53
src/internet/boxsettingspage.h
Normal file
@ -0,0 +1,53 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2013, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOXSETTINGSPAGE_H
|
||||
#define BOXSETTINGSPAGE_H
|
||||
|
||||
#include "ui/settingspage.h"
|
||||
|
||||
#include <QModelIndex>
|
||||
#include <QWidget>
|
||||
|
||||
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
|
110
src/internet/boxsettingspage.ui
Normal file
110
src/internet/boxsettingspage.ui
Normal file
@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BoxSettingsPage</class>
|
||||
<widget class="QWidget" name="BoxSettingsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>569</width>
|
||||
<height>491</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Box</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/providers/box.png</normaloff>:/providers/box.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Clementine can play music that you have uploaded to Box</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LoginStateWidget" name="login_state" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="login_container" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>28</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="login_button">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Clicking the Login button will open a web browser. You should return to Clementine after you have logged in.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>357</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LoginStateWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/loginstatewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
14
src/internet/boxurlhandler.cpp
Normal file
14
src/internet/boxurlhandler.cpp
Normal file
@ -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);
|
||||
}
|
21
src/internet/boxurlhandler.h
Normal file
21
src/internet/boxurlhandler.h
Normal file
@ -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
|
@ -3,8 +3,6 @@
|
||||
|
||||
#include "cloudfileservice.h"
|
||||
|
||||
#include "core/tagreaderclient.h"
|
||||
|
||||
namespace google_drive {
|
||||
class Client;
|
||||
class ConnectResponse;
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -74,6 +74,10 @@
|
||||
# include "internet/dropboxsettingspage.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_BOX
|
||||
# include "internet/boxsettingspage.h"
|
||||
#endif
|
||||
|
||||
#include <QDesktopWidget>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
@ -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
|
||||
|
@ -82,6 +82,7 @@ public:
|
||||
Page_UbuntuOne,
|
||||
Page_Dropbox,
|
||||
Page_Skydrive,
|
||||
Page_Box,
|
||||
};
|
||||
|
||||
enum Role {
|
||||
|
Loading…
x
Reference in New Issue
Block a user