Add a directory model for selecting a default upload directory on Google Drive.

This commit is contained in:
David Sansome 2012-07-29 15:06:23 +01:00
parent 9653a45f66
commit fd1d70c644
12 changed files with 464 additions and 3 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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);

View File

@ -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];
}

View File

@ -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

View File

@ -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)) {

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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 &quot;Copy to device&quot;.</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>

View File

@ -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

View File

@ -76,6 +76,7 @@ public:
Page_Remote,
Page_Wiimotedev,
Page_Podcasts,
Page_GoogleDrive,
};
enum Role {