Add a directory model for selecting a default upload directory on Google Drive.
This commit is contained in:
parent
9653a45f66
commit
fd1d70c644
|
@ -999,12 +999,18 @@ optional_source(HAVE_MOODBAR
|
|||
optional_source(HAVE_GOOGLE_DRIVE
|
||||
SOURCES
|
||||
internet/googledriveclient.cpp
|
||||
internet/googledrivefoldermodel.cpp
|
||||
internet/googledriveservice.cpp
|
||||
internet/googledrivesettingspage.cpp
|
||||
internet/googledriveurlhandler.cpp
|
||||
HEADERS
|
||||
internet/googledriveclient.h
|
||||
internet/googledrivefoldermodel.h
|
||||
internet/googledriveservice.h
|
||||
internet/googledrivesettingspage.h
|
||||
internet/googledriveurlhandler.h
|
||||
UI
|
||||
internet/googledrivesettingspage.ui
|
||||
)
|
||||
|
||||
# Hack to add Clementine to the Unity system tray whitelist
|
||||
|
|
|
@ -24,11 +24,29 @@
|
|||
|
||||
using namespace google_drive;
|
||||
|
||||
const char* File::kFolderMimeType = "application/vnd.google-apps.folder";
|
||||
|
||||
namespace {
|
||||
static const char* kGoogleDriveFiles = "https://www.googleapis.com/drive/v2/files";
|
||||
static const char* kGoogleDriveFile = "https://www.googleapis.com/drive/v2/files/%1";
|
||||
}
|
||||
|
||||
QStringList File::parent_ids() const {
|
||||
QStringList ret;
|
||||
|
||||
foreach (const QVariant& var, data_["parents"].toList()) {
|
||||
QVariantMap map(var.toMap());
|
||||
|
||||
if (map["isRoot"].toBool()) {
|
||||
ret << QString();
|
||||
} else {
|
||||
ret << map["id"].toString();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConnectResponse::ConnectResponse(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
|
@ -73,6 +91,8 @@ void Client::ConnectFinished(ConnectResponse* response, OAuthenticator* oauth) {
|
|||
access_token_ = oauth->access_token();
|
||||
response->refresh_token_ = oauth->refresh_token();
|
||||
emit response->Finished();
|
||||
|
||||
emit Authenticated();
|
||||
}
|
||||
|
||||
void Client::AddAuthorizationHeader(QNetworkRequest* request) const {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QVariantMap>
|
||||
|
||||
|
@ -39,9 +40,12 @@ class File {
|
|||
public:
|
||||
File(const QVariantMap& data = QVariantMap()) : data_(data) {}
|
||||
|
||||
static const char* kFolderMimeType;
|
||||
|
||||
QString id() const { return data_["id"].toString(); }
|
||||
QString etag() const { return data_["etag"].toString(); }
|
||||
QString title() const { return data_["title"].toString(); }
|
||||
QString mime_type() const { return data_["mimeType"].toString(); }
|
||||
QString description() const { return data_["description"].toString(); }
|
||||
long size() const { return data_["fileSize"].toUInt(); }
|
||||
QUrl download_url() const { return data_["downloadUrl"].toUrl(); }
|
||||
|
@ -54,6 +58,19 @@ public:
|
|||
return QDateTime::fromString(data_["createdDate"].toString(), Qt::ISODate);
|
||||
}
|
||||
|
||||
bool is_folder() const { return mime_type() == kFolderMimeType; }
|
||||
QStringList parent_ids() const;
|
||||
|
||||
bool has_label(const QString& name) const {
|
||||
return data_["labels"].toMap()[name].toBool();
|
||||
}
|
||||
|
||||
bool is_starred() const { return has_label("starred"); }
|
||||
bool is_hidden() const { return has_label("hidden"); }
|
||||
bool is_trashed() const { return has_label("trashed"); }
|
||||
bool is_restricted() const { return has_label("restricted"); }
|
||||
bool is_viewed() const { return has_label("viewed"); }
|
||||
|
||||
private:
|
||||
QVariantMap data_;
|
||||
};
|
||||
|
@ -125,6 +142,9 @@ public:
|
|||
ListFilesResponse* ListFiles(const QString& query);
|
||||
GetFileResponse* GetFile(const QString& file_id);
|
||||
|
||||
signals:
|
||||
void Authenticated();
|
||||
|
||||
private slots:
|
||||
void ConnectFinished(ConnectResponse* response, OAuthenticator* oauth);
|
||||
void ListFilesFinished(ListFilesResponse* response, QNetworkReply* reply);
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.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 "googledriveclient.h"
|
||||
#include "googledrivefoldermodel.h"
|
||||
#include "core/closure.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
using namespace google_drive;
|
||||
|
||||
FolderModel::FolderModel(Client* client, QObject* parent)
|
||||
: QStandardItemModel(parent),
|
||||
client_(client)
|
||||
{
|
||||
folder_icon_ = IconLoader::Load("folder");
|
||||
|
||||
root_ = new QStandardItem(tr("My Drive"));
|
||||
item_by_id_[QString()] = root_;
|
||||
invisibleRootItem()->appendRow(root_);
|
||||
|
||||
connect(client, SIGNAL(Authenticated()), SLOT(Refresh()));
|
||||
if (client->is_authenticated()) {
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void FolderModel::Refresh() {
|
||||
ListFilesResponse* reply =
|
||||
client_->ListFiles(QString("mimeType = '%1'").arg(File::kFolderMimeType));
|
||||
connect(reply, SIGNAL(FilesFound(QList<google_drive::File>)),
|
||||
this, SLOT(FilesFound(QList<google_drive::File>)));
|
||||
NewClosure(reply, SIGNAL(Finished()),
|
||||
this, SLOT(FindFilesFinished(ListFilesResponse*)),
|
||||
reply);
|
||||
}
|
||||
|
||||
void FolderModel::FindFilesFinished(ListFilesResponse* reply) {
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void FolderModel::FilesFound(const QList<google_drive::File>& files) {
|
||||
foreach (const File& file, files) {
|
||||
if (file.is_hidden() || file.is_trashed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString id(file.id());
|
||||
|
||||
// Does this file exist in the model already?
|
||||
if (item_by_id_.contains(id)) {
|
||||
// If it has the same etag ignore it, otherwise remove and recreate it.
|
||||
QStandardItem* old_item = item_by_id_[id];
|
||||
if (old_item->data(Role_Etag).toString() == file.etag()) {
|
||||
continue;
|
||||
} else {
|
||||
item_by_id_.remove(id);
|
||||
old_item->parent()->removeRow(old_item->row());
|
||||
}
|
||||
}
|
||||
|
||||
// Get the first parent's ID
|
||||
const QStringList parent_ids = file.parent_ids();
|
||||
if (parent_ids.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
const QString parent_id = parent_ids.first();
|
||||
|
||||
// If the parent doesn't exist yet, remember this file for later.
|
||||
if (!item_by_id_.contains(parent_id)) {
|
||||
orphans_[parent_id] << file;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the item for the parent
|
||||
QStandardItem* parent = item_by_id_[parent_id];
|
||||
|
||||
// Create the item
|
||||
QStandardItem* item = new QStandardItem(file.title());
|
||||
item->setData(file.etag(), Role_Etag);
|
||||
item->setData(id, Role_Id);
|
||||
item_by_id_[id] = item;
|
||||
parent->appendRow(item);
|
||||
|
||||
// Add any children for this item that we saw before.
|
||||
if (orphans_.contains(id)) {
|
||||
FilesFound(orphans_.take(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant FolderModel::data(const QModelIndex& index, int role) const {
|
||||
if (role == Qt::DecorationRole) {
|
||||
return folder_icon_;
|
||||
}
|
||||
|
||||
return QStandardItemModel::data(index, role);
|
||||
}
|
||||
|
||||
QStandardItem* FolderModel::ItemById(const QString& id) const {
|
||||
return item_by_id_[id];
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.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 GOOGLEDRIVEFOLDERMODEL_H
|
||||
#define GOOGLEDRIVEFOLDERMODEL_H
|
||||
|
||||
#include "googledriveclient.h"
|
||||
|
||||
#include <QStandardItemModel>
|
||||
|
||||
namespace google_drive {
|
||||
|
||||
class Client;
|
||||
|
||||
class FolderModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FolderModel(Client* client, QObject* parent = 0);
|
||||
|
||||
enum Role {
|
||||
Role_Etag = Qt::UserRole,
|
||||
Role_Id
|
||||
};
|
||||
|
||||
QIcon folder_icon() const { return folder_icon_; }
|
||||
void set_folder_icon(const QIcon& icon) { folder_icon_ = icon; }
|
||||
|
||||
QVariant data(const QModelIndex& index, int role) const;
|
||||
|
||||
QStandardItem* ItemById(const QString& id) const;
|
||||
|
||||
public slots:
|
||||
void Refresh();
|
||||
|
||||
private slots:
|
||||
void FilesFound(const QList<google_drive::File>& files);
|
||||
void FindFilesFinished(ListFilesResponse* reply);
|
||||
|
||||
private:
|
||||
Client* client_;
|
||||
QIcon folder_icon_;
|
||||
|
||||
QStandardItem* root_;
|
||||
|
||||
QMap<QString, QStandardItem*> item_by_id_;
|
||||
QMap<QString, QList<google_drive::File> > orphans_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // GOOGLEDRIVEFOLDERMODEL_H
|
|
@ -18,9 +18,10 @@
|
|||
#include "googledriveurlhandler.h"
|
||||
#include "internetmodel.h"
|
||||
|
||||
namespace {
|
||||
const char* GoogleDriveService::kServiceName = "Google Drive";
|
||||
const char* GoogleDriveService::kSettingsGroup = "GoogleDrive";
|
||||
|
||||
static const char* kSettingsGroup = "GoogleDrive";
|
||||
namespace {
|
||||
|
||||
static const char* kSongsTable = "google_drive_songs";
|
||||
static const char* kFtsTable = "google_drive_songs_fts";
|
||||
|
@ -29,7 +30,7 @@ static const char* kFtsTable = "google_drive_songs_fts";
|
|||
|
||||
|
||||
GoogleDriveService::GoogleDriveService(Application* app, InternetModel* parent)
|
||||
: InternetService("Google Drive", app, parent, parent),
|
||||
: InternetService(kServiceName, app, parent, parent),
|
||||
root_(NULL),
|
||||
client_(new google_drive::Client(this)),
|
||||
library_sort_model_(new QSortFilterProxyModel(this)) {
|
||||
|
|
|
@ -24,6 +24,11 @@ class GoogleDriveService : public InternetService {
|
|||
public:
|
||||
GoogleDriveService(Application* app, InternetModel* parent);
|
||||
|
||||
static const char* kServiceName;
|
||||
static const char* kSettingsGroup;
|
||||
|
||||
google_drive::Client* client() const { return client_; }
|
||||
|
||||
QStandardItem* CreateRootItem();
|
||||
void LazyPopulate(QStandardItem* item);
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.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 "googledriveclient.h"
|
||||
#include "googledrivefoldermodel.h"
|
||||
#include "googledriveservice.h"
|
||||
#include "googledrivesettingspage.h"
|
||||
#include "ui_googledrivesettingspage.h"
|
||||
#include "core/application.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "ui/settingsdialog.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
GoogleDriveSettingsPage::GoogleDriveSettingsPage(SettingsDialog* parent)
|
||||
: SettingsPage(parent),
|
||||
ui_(new Ui::GoogleDriveSettingsPage),
|
||||
model_(NULL),
|
||||
proxy_model_(NULL),
|
||||
item_needs_selecting_(false)
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
}
|
||||
|
||||
GoogleDriveSettingsPage::~GoogleDriveSettingsPage() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void GoogleDriveSettingsPage::Load() {
|
||||
QSettings s;
|
||||
s.beginGroup(GoogleDriveService::kSettingsGroup);
|
||||
|
||||
destination_folder_id_ = s.value("destination_folder_id").toString();
|
||||
item_needs_selecting_ = !destination_folder_id_.isEmpty();
|
||||
|
||||
if (!model_) {
|
||||
GoogleDriveService* service =
|
||||
dialog()->app()->internet_model()->Service<GoogleDriveService>();
|
||||
google_drive::Client* client = service->client();
|
||||
|
||||
model_ = new google_drive::FolderModel(client, this);
|
||||
proxy_model_ = new QSortFilterProxyModel(this);
|
||||
proxy_model_->setSourceModel(model_);
|
||||
proxy_model_->setDynamicSortFilter(true);
|
||||
proxy_model_->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
proxy_model_->sort(0);
|
||||
|
||||
ui_->upload_destination->setModel(proxy_model_);
|
||||
|
||||
connect(model_, SIGNAL(rowsInserted(QModelIndex,int,int)),
|
||||
this, SLOT(DirectoryRowsInserted(QModelIndex)));
|
||||
}
|
||||
}
|
||||
|
||||
void GoogleDriveSettingsPage::Save() {
|
||||
QSettings s;
|
||||
s.beginGroup(GoogleDriveService::kSettingsGroup);
|
||||
|
||||
s.setValue("destination_folder_id",
|
||||
ui_->upload_destination->currentIndex().data(
|
||||
google_drive::FolderModel::Role_Id).toString());
|
||||
}
|
||||
|
||||
void GoogleDriveSettingsPage::DirectoryRowsInserted(const QModelIndex& parent) {
|
||||
ui_->upload_destination->expand(proxy_model_->mapFromSource(parent));
|
||||
|
||||
if (item_needs_selecting_) {
|
||||
QStandardItem* item = model_->ItemById(destination_folder_id_);
|
||||
if (item) {
|
||||
ui_->upload_destination->selectionModel()->select(
|
||||
proxy_model_->mapFromSource(item->index()),
|
||||
QItemSelectionModel::ClearAndSelect);
|
||||
item_needs_selecting_ = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.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 GOOGLEDRIVESETTINGSPAGE_H
|
||||
#define GOOGLEDRIVESETTINGSPAGE_H
|
||||
|
||||
#include "ui/settingspage.h"
|
||||
|
||||
#include <QModelIndex>
|
||||
#include <QWidget>
|
||||
|
||||
class Ui_GoogleDriveSettingsPage;
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
namespace google_drive {
|
||||
class FolderModel;
|
||||
}
|
||||
|
||||
class GoogleDriveSettingsPage : public SettingsPage {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GoogleDriveSettingsPage(SettingsDialog* parent = 0);
|
||||
~GoogleDriveSettingsPage();
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
private slots:
|
||||
void DirectoryRowsInserted(const QModelIndex& parent);
|
||||
|
||||
private:
|
||||
Ui_GoogleDriveSettingsPage* ui_;
|
||||
|
||||
google_drive::FolderModel* model_;
|
||||
QSortFilterProxyModel* proxy_model_;
|
||||
|
||||
QString destination_folder_id_;
|
||||
bool item_needs_selecting_;
|
||||
};
|
||||
|
||||
#endif // GOOGLEDRIVESETTINGSPAGE_H
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GoogleDriveSettingsPage</class>
|
||||
<widget class="QWidget" name="GoogleDriveSettingsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>569</width>
|
||||
<height>491</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Google Drive</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/providers/googledrive.png</normaloff>:/providers/googledrive.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Uploads</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>You can upload songs to Google Drive by right clicking and using "Copy to device".</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Upload new songs to</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTreeView" name="upload_destination">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -59,6 +59,10 @@
|
|||
# include "internet/spotifysettingspage.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GOOGLE_DRIVE
|
||||
# include "internet/googledrivesettingspage.h"
|
||||
#endif
|
||||
|
||||
#include <QDesktopWidget>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
|
@ -139,6 +143,10 @@ SettingsDialog::SettingsDialog(Application* app, BackgroundStreams* streams, QWi
|
|||
|
||||
AddPage(Page_Grooveshark, new GroovesharkSettingsPage(this), providers);
|
||||
|
||||
#ifdef HAVE_GOOGLE_DRIVE
|
||||
AddPage(Page_GoogleDrive, new GoogleDriveSettingsPage(this), providers);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SPOTIFY
|
||||
AddPage(Page_Spotify, new SpotifySettingsPage(this), providers);
|
||||
#endif
|
||||
|
|
|
@ -76,6 +76,7 @@ public:
|
|||
Page_Remote,
|
||||
Page_Wiimotedev,
|
||||
Page_Podcasts,
|
||||
Page_GoogleDrive,
|
||||
};
|
||||
|
||||
enum Role {
|
||||
|
|
Loading…
Reference in New Issue