Use changes API instead of search API in Google Drive.

This commit is contained in:
John Maguire 2012-12-05 11:57:10 +00:00
parent b41a2b5308
commit 951cac2ad6
5 changed files with 149 additions and 103 deletions

View File

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

View File

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

View File

@ -1,16 +1,16 @@
/* 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/>.
*/
@ -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<google_drive::File>& 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<google_drive::File>& files);
void FilesDeleted(const QList<QUrl>& 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_;

View File

@ -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<google_drive::File>)),
this, SLOT(FilesFound(QList<google_drive::File>)));
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<google_drive::File>)),
SLOT(FilesFound(QList<google_drive::File>)));
connect(changes_response, SIGNAL(FilesDeleted(QList<QUrl>)),
SLOT(FilesDeleted(QList<QUrl>)));
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<google_drive::File>& files) {
}
}
void GoogleDriveService::ListFilesFinished(google_drive::ListFilesResponse* response) {
response->deleteLater();
void GoogleDriveService::FilesDeleted(const QList<QUrl>& 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;
}

View File

@ -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<google_drive::File>& files);
void ListFilesFinished(google_drive::ListFilesResponse* response);
void FilesDeleted(const QList<QUrl>& 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_;