diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6fe1d3e1e..291788f1f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/internet/googledriveclient.cpp b/src/internet/googledriveclient.cpp index 632f517f3..b907a2b97 100644 --- a/src/internet/googledriveclient.cpp +++ b/src/internet/googledriveclient.cpp @@ -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 { diff --git a/src/internet/googledriveclient.h b/src/internet/googledriveclient.h index ddabd2023..6944a373a 100644 --- a/src/internet/googledriveclient.h +++ b/src/internet/googledriveclient.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -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); diff --git a/src/internet/googledrivefoldermodel.cpp b/src/internet/googledrivefoldermodel.cpp new file mode 100644 index 000000000..367e6edb4 --- /dev/null +++ b/src/internet/googledrivefoldermodel.cpp @@ -0,0 +1,115 @@ +/* This file is part of Clementine. + Copyright 2012, David Sansome + + 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 "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)), + this, SLOT(FilesFound(QList))); + NewClosure(reply, SIGNAL(Finished()), + this, SLOT(FindFilesFinished(ListFilesResponse*)), + reply); +} + +void FolderModel::FindFilesFinished(ListFilesResponse* reply) { + reply->deleteLater(); +} + +void FolderModel::FilesFound(const QList& 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]; +} diff --git a/src/internet/googledrivefoldermodel.h b/src/internet/googledrivefoldermodel.h new file mode 100644 index 000000000..899274d14 --- /dev/null +++ b/src/internet/googledrivefoldermodel.h @@ -0,0 +1,66 @@ +/* This file is part of Clementine. + Copyright 2012, David Sansome + + 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 GOOGLEDRIVEFOLDERMODEL_H +#define GOOGLEDRIVEFOLDERMODEL_H + +#include "googledriveclient.h" + +#include + +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& files); + void FindFilesFinished(ListFilesResponse* reply); + +private: + Client* client_; + QIcon folder_icon_; + + QStandardItem* root_; + + QMap item_by_id_; + QMap > orphans_; +}; + +} // namespace + +#endif // GOOGLEDRIVEFOLDERMODEL_H diff --git a/src/internet/googledriveservice.cpp b/src/internet/googledriveservice.cpp index 45810191d..89453b9df 100644 --- a/src/internet/googledriveservice.cpp +++ b/src/internet/googledriveservice.cpp @@ -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)) { diff --git a/src/internet/googledriveservice.h b/src/internet/googledriveservice.h index 060e1f7fe..f71fc5918 100644 --- a/src/internet/googledriveservice.h +++ b/src/internet/googledriveservice.h @@ -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); diff --git a/src/internet/googledrivesettingspage.cpp b/src/internet/googledrivesettingspage.cpp new file mode 100644 index 000000000..cdffd0222 --- /dev/null +++ b/src/internet/googledrivesettingspage.cpp @@ -0,0 +1,90 @@ +/* This file is part of Clementine. + Copyright 2012, David Sansome + + 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 "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 + +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(); + 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; + } + } +} diff --git a/src/internet/googledrivesettingspage.h b/src/internet/googledrivesettingspage.h new file mode 100644 index 000000000..fb8ddf7b4 --- /dev/null +++ b/src/internet/googledrivesettingspage.h @@ -0,0 +1,57 @@ +/* This file is part of Clementine. + Copyright 2012, David Sansome + + 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 GOOGLEDRIVESETTINGSPAGE_H +#define GOOGLEDRIVESETTINGSPAGE_H + +#include "ui/settingspage.h" + +#include +#include + +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 diff --git a/src/internet/googledrivesettingspage.ui b/src/internet/googledrivesettingspage.ui new file mode 100644 index 000000000..57c5b5b00 --- /dev/null +++ b/src/internet/googledrivesettingspage.ui @@ -0,0 +1,72 @@ + + + GoogleDriveSettingsPage + + + + 0 + 0 + 569 + 491 + + + + Google Drive + + + + :/providers/googledrive.png:/providers/googledrive.png + + + + + + Uploads + + + + + + You can upload songs to Google Drive by right clicking and using "Copy to device". + + + true + + + + + + + Upload new songs to + + + + + + + + 16777215 + 150 + + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + + + + + + + + + + + diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index fd7671376..868d06452 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -59,6 +59,10 @@ # include "internet/spotifysettingspage.h" #endif +#ifdef HAVE_GOOGLE_DRIVE +# include "internet/googledrivesettingspage.h" +#endif + #include #include #include @@ -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 diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index b7db62d60..3d73b5691 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -76,6 +76,7 @@ public: Page_Remote, Page_Wiimotedev, Page_Podcasts, + Page_GoogleDrive, }; enum Role {