From 951cac2ad681e01322bd234bb8a4c0f311f9ab7d Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 5 Dec 2012 11:57:10 +0000 Subject: [PATCH] Use changes API instead of search API in Google Drive. --- ext/clementine-tagreader/tagreaderworker.cpp | 2 +- src/internet/googledriveclient.cpp | 138 ++++++++++--------- src/internet/googledriveclient.h | 51 +++---- src/internet/googledriveservice.cpp | 55 ++++++-- src/internet/googledriveservice.h | 6 +- 5 files changed, 149 insertions(+), 103 deletions(-) diff --git a/ext/clementine-tagreader/tagreaderworker.cpp b/ext/clementine-tagreader/tagreaderworker.cpp index 781dc6e23..dd2cddc6d 100644 --- a/ext/clementine-tagreader/tagreaderworker.cpp +++ b/ext/clementine-tagreader/tagreaderworker.cpp @@ -644,7 +644,7 @@ bool TagReaderWorker::ReadCloudFile(const QUrl& download_url, << stream->cached_bytes(); } - if (tag->tag()) { + if (tag->tag() && !tag->tag()->isEmpty()) { song->set_title(tag->tag()->title().toCString(true)); song->set_artist(tag->tag()->artist().toCString(true)); song->set_album(tag->tag()->album().toCString(true)); diff --git a/src/internet/googledriveclient.cpp b/src/internet/googledriveclient.cpp index 6f25ec8b2..f721edb70 100644 --- a/src/internet/googledriveclient.cpp +++ b/src/internet/googledriveclient.cpp @@ -29,8 +29,8 @@ 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"; + static const char* kGoogleDriveChanges = "https://www.googleapis.com/drive/v2/changes"; } QStringList File::parent_ids() const { @@ -54,18 +54,18 @@ ConnectResponse::ConnectResponse(QObject* parent) { } -ListFilesResponse::ListFilesResponse(const QString& query, QObject* parent) - : QObject(parent), - query_(query) -{ -} - GetFileResponse::GetFileResponse(const QString& file_id, QObject* parent) : QObject(parent), file_id_(file_id) { } +ListChangesResponse::ListChangesResponse(const QString& cursor, QObject* parent) + : QObject(parent), + cursor_(cursor) +{ +} + Client::Client(QObject* parent) : QObject(parent), network_(new NetworkAccessManager(this)) @@ -104,61 +104,6 @@ void Client::AddAuthorizationHeader(QNetworkRequest* request) const { "Authorization", QString("Bearer %1").arg(access_token_).toUtf8()); } -ListFilesResponse* Client::ListFiles(const QString& query) { - ListFilesResponse* ret = new ListFilesResponse(query, this); - MakeListFilesRequest(ret); - return ret; -} - -void Client::MakeListFilesRequest(ListFilesResponse* response, const QString& page_token) { - QUrl url = QUrl(kGoogleDriveFiles); - - if (!response->query_.isEmpty()) { - url.addQueryItem("q", response->query_); - } - - if (!page_token.isEmpty()) { - url.addQueryItem("pageToken", page_token); - } - - QNetworkRequest request = QNetworkRequest(url); - AddAuthorizationHeader(&request); - - QNetworkReply* reply = network_->get(request); - NewClosure(reply, SIGNAL(finished()), - this, SLOT(ListFilesFinished(ListFilesResponse*, QNetworkReply*)), - response, reply); -} - -void Client::ListFilesFinished(ListFilesResponse* response, QNetworkReply* reply) { - reply->deleteLater(); - - // Parse the response - QJson::Parser parser; - bool ok = false; - QVariantMap result = parser.parse(reply, &ok).toMap(); - if (!ok) { - qLog(Error) << "Failed to request files from Google Drive"; - emit response->Finished(); - return; - } - - // Emit the FilesFound signal for the files in the response. - FileList files; - foreach (const QVariant& v, result["items"].toList()) { - files << File(v.toMap()); - } - - emit response->FilesFound(files); - - // Get the next page of results if there is one. - if (result.contains("nextPageToken")) { - MakeListFilesRequest(response, result["nextPageToken"].toString()); - } else { - emit response->Finished(); - } -} - GetFileResponse* Client::GetFile(const QString& file_id) { GetFileResponse* ret = new GetFileResponse(file_id, this); @@ -194,6 +139,75 @@ void Client::GetFileFinished(GetFileResponse* response, QNetworkReply* reply) { emit response->Finished(); } +ListChangesResponse* Client::ListChanges(const QString& cursor) { + ListChangesResponse* ret = new ListChangesResponse(cursor, this); + MakeListChangesRequest(ret); + return ret; +} + +void Client::MakeListChangesRequest(ListChangesResponse* response, const QString& page_token) { + QUrl url(kGoogleDriveChanges); + if (!response->cursor().isEmpty()) { + url.addQueryItem("startChangeId", response->cursor()); + } + if (!page_token.isEmpty()) { + url.addQueryItem("pageToken", page_token); + } + + qLog(Debug) << "Requesting changes at:" << response->cursor() << page_token; + + QNetworkRequest request(url); + AddAuthorizationHeader(&request); + + QNetworkReply* reply = network_->get(request); + NewClosure(reply, SIGNAL(finished()), + this, SLOT(ListChangesFinished(ListChangesResponse*,QNetworkReply*)), + response, reply); +} + +void Client::ListChangesFinished(ListChangesResponse* response, QNetworkReply* reply) { + reply->deleteLater(); + + QJson::Parser parser; + bool ok = false; + // TODO: Put this on a separate thread as the response could be large. + QVariantMap result = parser.parse(reply, &ok).toMap(); + if (!ok) { + qLog(Error) << "Failed to fetch changes" << response->cursor(); + emit response->Finished(); + return; + } + + if (result.contains("largestChangeId")) { + response->next_cursor_ = result["largestChangeId"].toString(); + } + + // Emit the FilesFound signal for the files in the response. + FileList files; + QList files_deleted; + foreach (const QVariant& v, result["items"].toList()) { + QVariantMap change = v.toMap(); + if (!change["deleted"].toBool()) { + files << File(change["file"].toMap()); + } else { + QUrl url; + url.setScheme("googledrive"); + url.setPath(change["fileId"].toString()); + files_deleted << url; + } + } + + emit response->FilesFound(files); + emit response->FilesDeleted(files_deleted); + + // Get the next page of results if there is one. + if (result.contains("nextPageToken")) { + MakeListChangesRequest(response, result["nextPageToken"].toString()); + } else { + emit response->Finished(); + } +} + bool Client::is_authenticated() const { return !access_token_.isEmpty() && QDateTime::currentDateTime().secsTo(expiry_time_) > 0; diff --git a/src/internet/googledriveclient.h b/src/internet/googledriveclient.h index 991aee6d2..2da16f510 100644 --- a/src/internet/googledriveclient.h +++ b/src/internet/googledriveclient.h @@ -1,16 +1,16 @@ /* 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 . */ @@ -97,23 +97,6 @@ private: }; -class ListFilesResponse : public QObject { - Q_OBJECT - friend class Client; - -public: - const QString& query() const { return query_; } - -signals: - void FilesFound(const QList& files); - void Finished(); - -private: - ListFilesResponse(const QString& query, QObject* parent); - QString query_; -}; - - class GetFileResponse : public QObject { Q_OBJECT friend class Client; @@ -132,6 +115,25 @@ private: }; +class ListChangesResponse : public QObject { + Q_OBJECT + friend class Client; + public: + const QString& cursor() const { return cursor_; } + const QString& next_cursor() const { return next_cursor_; } + + signals: + void FilesFound(const QList& files); + void FilesDeleted(const QList& files); + void Finished(); + + private: + ListChangesResponse(const QString& cursor, QObject* parent); + QString cursor_; + QString next_cursor_; +}; + + class Client : public QObject { Q_OBJECT @@ -144,21 +146,22 @@ public: void ForgetCredentials(); ConnectResponse* Connect(const QString& refresh_token = QString()); - ListFilesResponse* ListFiles(const QString& query); GetFileResponse* GetFile(const QString& file_id); + ListChangesResponse* ListChanges(const QString& cursor); + signals: void Authenticated(); private slots: void ConnectFinished(ConnectResponse* response, OAuthenticator* oauth); - void ListFilesFinished(ListFilesResponse* response, QNetworkReply* reply); void GetFileFinished(GetFileResponse* response, QNetworkReply* reply); + void ListChangesFinished(ListChangesResponse* response, QNetworkReply* reply); private: void AddAuthorizationHeader(QNetworkRequest* request) const; - void MakeListFilesRequest(ListFilesResponse* response, - const QString& page_token = QString()); + void MakeListChangesRequest(ListChangesResponse* response, + const QString& page_token = QString()); private: QNetworkAccessManager* network_; diff --git a/src/internet/googledriveservice.cpp b/src/internet/googledriveservice.cpp index 3443486a0..4ec2abd82 100644 --- a/src/internet/googledriveservice.cpp +++ b/src/internet/googledriveservice.cpp @@ -74,14 +74,22 @@ void GoogleDriveService::ForgetCredentials() { s.remove("user_email"); } -void GoogleDriveService::ListFilesForMimeType(const QString& mime_type) { - google_drive::ListFilesResponse* list_response = client_->ListFiles( - QString("mimeType = '%1' and trashed = false").arg(mime_type)); - connect(list_response, SIGNAL(FilesFound(QList)), - this, SLOT(FilesFound(QList))); - NewClosure(list_response, SIGNAL(Finished()), - this, SLOT(ListFilesFinished(google_drive::ListFilesResponse*)), - list_response); +void GoogleDriveService::ListChanges(const QString& cursor) { + google_drive::ListChangesResponse* changes_response = client_->ListChanges(cursor); + connect(changes_response, SIGNAL(FilesFound(QList)), + SLOT(FilesFound(QList))); + connect(changes_response, SIGNAL(FilesDeleted(QList)), + SLOT(FilesDeleted(QList))); + NewClosure(changes_response, SIGNAL(Finished()), + this, SLOT(ListChangesFinished(google_drive::ListChangesResponse*)), + changes_response); +} + +void GoogleDriveService::ListChangesFinished(google_drive::ListChangesResponse* changes_response) { + changes_response->deleteLater(); + QSettings s; + s.beginGroup(kSettingsGroup); + s.setValue("cursor", changes_response->next_cursor()); } void GoogleDriveService::ConnectFinished(google_drive::ConnectResponse* response) { @@ -99,10 +107,8 @@ void GoogleDriveService::ConnectFinished(google_drive::ConnectResponse* response emit Connected(); - // Find any music files - ListFilesForMimeType("audio/mpeg"); // MP3/AAC - ListFilesForMimeType("application/ogg"); // OGG - ListFilesForMimeType("application/x-flac"); // FLAC + // Find all the changes since the last check. + ListChanges(s.value("cursor").toString()); } void GoogleDriveService::EnsureConnected() { @@ -122,16 +128,37 @@ void GoogleDriveService::FilesFound(const QList& files) { } } -void GoogleDriveService::ListFilesFinished(google_drive::ListFilesResponse* response) { - response->deleteLater(); +void GoogleDriveService::FilesDeleted(const QList& files) { + foreach (const QUrl& url, files) { + Song song = library_backend_->GetSongByUrl(url); + qLog(Debug) << "Deleting:" << url << song.title(); + if (song.is_valid()) { + library_backend_->DeleteSongs(SongList() << song); + } + } } +namespace { + +bool IsSupportedMimeType(const QString& mime_type) { + return mime_type == "audio/mpeg" || + mime_type == "application/ogg" || + mime_type == "application/x-flac"; +} + +} // namespace + void GoogleDriveService::MaybeAddFileToDatabase(const google_drive::File& file) { QString url = QString("googledrive:%1").arg(file.id()); Song song = library_backend_->GetSongByUrl(QUrl(url)); // Song already in index. // TODO: Check etag and maybe update. if (song.is_valid()) { + qLog(Debug) << "Already have:" << url; + return; + } + + if (!IsSupportedMimeType(file.mime_type())) { return; } diff --git a/src/internet/googledriveservice.h b/src/internet/googledriveservice.h index 7d2b5c60f..7b596ffbe 100644 --- a/src/internet/googledriveservice.h +++ b/src/internet/googledriveservice.h @@ -10,6 +10,7 @@ namespace google_drive { class ConnectResponse; class File; class ListFilesResponse; + class ListChangesResponse; } class GoogleDriveService : public CloudFileService { @@ -38,7 +39,8 @@ class GoogleDriveService : public CloudFileService { private slots: void ConnectFinished(google_drive::ConnectResponse* response); void FilesFound(const QList& files); - void ListFilesFinished(google_drive::ListFilesResponse* response); + void FilesDeleted(const QList& files); + void ListChangesFinished(google_drive::ListChangesResponse* response); void ReadTagsFinished(TagReaderClient::ReplyType* reply, const google_drive::File& metadata, const QString& url, @@ -50,7 +52,7 @@ class GoogleDriveService : public CloudFileService { void EnsureConnected(); void RefreshAuthorisation(const QString& refresh_token); void MaybeAddFileToDatabase(const google_drive::File& file); - void ListFilesForMimeType(const QString& mime_type); + void ListChanges(const QString& cursor); google_drive::Client* client_;