diff --git a/core/utilities/assistants/webservices/dropbox/dbtalker.cpp b/core/utilities/assistants/webservices/dropbox/dbtalker.cpp index 7f9878034b..2069dec0f4 100644 --- a/core/utilities/assistants/webservices/dropbox/dbtalker.cpp +++ b/core/utilities/assistants/webservices/dropbox/dbtalker.cpp @@ -1,471 +1,505 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export images to Dropbox web service * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2018 by Maik Qualmann * * This program 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 2, or (at your option) any later version. * * This program 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. * * ============================================================ */ #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_version.h" #include "previewloadthread.h" #include "wstoolutils.h" #include "dmetadata.h" #include "dbwindow.h" #include "dbmpform.h" #include "dbitem.h" #include "o2.h" #include "o0globals.h" #include "o0settingsstore.h" namespace Digikam { class Q_DECL_HIDDEN DBTalker::Private { public: enum State { DB_USERNAME = 0, DB_LISTFOLDERS, DB_CREATEFOLDER, DB_ADDPHOTO }; public: explicit Private(QWidget* p) { apikey = QLatin1String("mv2pk07ym9bx3r8"); secret = QLatin1String("f33sflc8jhiozqu"); authUrl = QLatin1String("https://www.dropbox.com/oauth2/authorize"); tokenUrl = QLatin1String("https://api.dropboxapi.com/oauth2/token"); state = DB_USERNAME; settings = 0; netMngr = 0; reply = 0; o2 = 0; parent = p; } public: - QString apikey; - QString secret; - QString authUrl; - QString tokenUrl; + QString apikey; + QString secret; + QString authUrl; + QString tokenUrl; + QList > folderList; - QWidget* parent; - QNetworkAccessManager* netMngr; + QWidget* parent; + QNetworkAccessManager* netMngr; - QNetworkReply* reply; + QNetworkReply* reply; - QSettings* settings; + QSettings* settings; - State state; + State state; - DMetadata meta; + DMetadata meta; - O2* o2; + O2* o2; }; DBTalker::DBTalker(QWidget* const parent) : d(new Private(parent)) { d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); d->o2 = new O2(this); d->o2->setClientId(d->apikey); d->o2->setClientSecret(d->secret); d->o2->setRefreshTokenUrl(d->tokenUrl); d->o2->setRequestUrl(d->authUrl); d->o2->setTokenUrl(d->tokenUrl); d->o2->setLocalPort(8000); d->settings = WSToolUtils::getOauthSettings(this); O0SettingsStore* const store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this); store->setGroupKey(QLatin1String("Dropbox")); d->o2->setStore(store); connect(d->o2, SIGNAL(linkingFailed()), this, SLOT(slotLinkingFailed())); connect(d->o2, SIGNAL(linkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->o2, SIGNAL(openBrowser(QUrl)), this, SLOT(slotOpenBrowser(QUrl))); } DBTalker::~DBTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir("dropbox"); delete d; } void DBTalker::link() { emit signalBusy(true); d->o2->link(); } void DBTalker::unLink() { d->o2->unlink(); d->settings->beginGroup(QLatin1String("Dropbox")); d->settings->remove(QString()); d->settings->endGroup(); } void DBTalker::reauthenticate() { d->o2->unlink(); // Wait until user account is unlinked completely while (authenticated()); d->o2->link(); } void DBTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Dropbox fail"; emit signalBusy(false); } void DBTalker::slotLinkingSucceeded() { if (!d->o2->linked()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Dropbox ok"; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Dropbox ok"; emit signalLinkingSucceeded(); } void DBTalker::slotOpenBrowser(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser..."; QDesktopServices::openUrl(url); } bool DBTalker::authenticated() { return d->o2->linked(); } /** Creates folder at specified path */ void DBTalker::createFolder(const QString& path) { //path also has name of new folder so send path parameter accordingly qCDebug(DIGIKAM_WEBSERVICES_LOG) << "createFolder:" << path; QUrl url(QLatin1String("https://api.dropboxapi.com/2/files/create_folder_v2")); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_JSON)); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); QByteArray postData = QString::fromUtf8("{\"path\": \"%1\"}").arg(path).toUtf8(); d->reply = d->netMngr->post(netRequest, postData); d->state = Private::DB_CREATEFOLDER; emit signalBusy(true); } /** Get username of dropbox user */ void DBTalker::getUserName() { QUrl url(QLatin1String("https://api.dropboxapi.com/2/users/get_current_account")); QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); d->reply = d->netMngr->post(netRequest, QByteArray()); d->state = Private::DB_USERNAME; emit signalBusy(true); } /** Get list of folders by parsing json sent by dropbox */ -void DBTalker::listFolders(const QString& path) +void DBTalker::listFolders(const QString& cursor) { - QUrl url(QLatin1String("https://api.dropboxapi.com/2/files/list_folder")); + QString apiUrl = QLatin1String("https://api.dropboxapi.com/2/files/list_folder"); - QNetworkRequest netRequest(url); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_JSON)); - netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); + if (cursor.isEmpty()) + { + d->folderList.clear(); - QByteArray postData = QString::fromUtf8("{\"path\": \"%1\",\"recursive\": true}").arg(path).toUtf8(); + QString path; + QUrl url(apiUrl); + QNetworkRequest netRequest(url); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_JSON)); + netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); - d->reply = d->netMngr->post(netRequest, postData); + QByteArray postData = QString::fromUtf8("{\"path\": \"%1\",\"recursive\": true}").arg(path).toUtf8(); + + d->reply = d->netMngr->post(netRequest, postData); + } + else + { + apiUrl.append(QLatin1String("/continue")); + + QUrl url(apiUrl); + QNetworkRequest netRequest(url); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_JSON)); + netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); + + QByteArray postData = QString::fromUtf8("{\"cursor\": \"%1\"}").arg(cursor).toUtf8(); + + d->reply = d->netMngr->post(netRequest, postData); + } d->state = Private::DB_LISTFOLDERS; emit signalBusy(true); } bool DBTalker::addPhoto(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); DBMPForm form; QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { emit signalBusy(false); return false; } QString path = WSToolUtils::makeTemporaryDir("dropbox").filePath(QFileInfo(imgPath) .baseName().trimmed() + QLatin1String(".jpg")); if (rescale && (image.width() > maxDim || image.height() > maxDim)) { image = image.scaled(maxDim,maxDim, Qt::KeepAspectRatio,Qt::SmoothTransformation); } image.save(path,"JPEG",imageQuality); if (d->meta.load(imgPath)) { d->meta.setItemDimensions(image.size()); d->meta.setItemOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); d->meta.save(path, true); } if (!form.addFile(path)) { emit signalBusy(false); return false; } QString uploadPath = uploadFolder + QUrl(QUrl::fromLocalFile(imgPath)).fileName(); QUrl url(QLatin1String("https://content.dropboxapi.com/2/files/upload")); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/octet-stream")); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); QByteArray postData = QString::fromUtf8("{\"path\": \"%1\",\"mode\": \"add\"}").arg(uploadPath).toUtf8(); netRequest.setRawHeader("Dropbox-API-Arg", postData); d->reply = d->netMngr->post(netRequest, form.formData()); d->state = Private::DB_ADDPHOTO; return true; } void DBTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } void DBTalker::slotFinished(QNetworkReply* reply) { if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state != Private::DB_CREATEFOLDER) { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); reply->deleteLater(); return; } } QByteArray buffer = reply->readAll(); switch (d->state) { case Private::DB_LISTFOLDERS: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_LISTFOLDERS"; parseResponseListFolders(buffer); break; case Private::DB_CREATEFOLDER: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_CREATEFOLDER"; parseResponseCreateFolder(buffer); break; case Private::DB_ADDPHOTO: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_ADDPHOTO"; parseResponseAddPhoto(buffer); break; case Private::DB_USERNAME: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_USERNAME"; parseResponseUserName(buffer); break; default: break; } reply->deleteLater(); } void DBTalker::parseResponseAddPhoto(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool success = jsonObject.contains(QLatin1String("size")); emit signalBusy(false); if (!success) { emit signalAddPhotoFailed(i18n("Failed to upload photo")); } else { emit signalAddPhotoSucceeded(); } } void DBTalker::parseResponseUserName(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object()[QLatin1String("name")].toObject(); QString name = jsonObject[QLatin1String("display_name")].toString(); emit signalBusy(false); emit signalSetUserName(name); } void DBTalker::parseResponseListFolders(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListAlbumsFailed(i18n("Failed to list folders")); return; } QJsonObject jsonObject = doc.object(); QJsonArray jsonArray = jsonObject[QLatin1String("entries")].toArray(); - QList > list; - list.append(qMakePair(QLatin1String(""), QLatin1String("root"))); + if (d->folderList.isEmpty()) + { + d->folderList.append(qMakePair(QLatin1String(""), QLatin1String("root"))); + } foreach (const QJsonValue& value, jsonArray) { QString path; QString folder; QJsonObject obj = value.toObject(); path = obj[QLatin1String("path_display")].toString(); folder = obj[QLatin1String(".tag")].toString(); if (folder == QLatin1String("folder")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Path is" << path; QString listName = path.section(QLatin1Char('/'), 1); - list.append(qMakePair(path, listName)); + d->folderList.append(qMakePair(path, listName)); + } + } + + if (jsonObject[QLatin1String("has_more")].toBool()) + { + QString cursor = jsonObject[QLatin1String("cursor")].toString(); + + if (!cursor.isEmpty()) + { + listFolders(cursor); + return; } } - std::sort(list.begin(), list.end()); + std::sort(d->folderList.begin(), d->folderList.end()); emit signalBusy(false); - emit signalListAlbumsDone(list); + emit signalListAlbumsDone(d->folderList); } void DBTalker::parseResponseCreateFolder(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool fail = jsonObject.contains(QLatin1String("error")); emit signalBusy(false); if (fail) { emit signalCreateFolderFailed(jsonObject[QLatin1String("error_summary")].toString()); } else { emit signalCreateFolderSucceeded(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/dropbox/dbtalker.h b/core/utilities/assistants/webservices/dropbox/dbtalker.h index 45707b41ff..6dccbb2c4d 100644 --- a/core/utilities/assistants/webservices/dropbox/dbtalker.h +++ b/core/utilities/assistants/webservices/dropbox/dbtalker.h @@ -1,92 +1,92 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export images to Dropbox web service * * Copyright (C) 2013 by Pankaj Kumar * * This program 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 2, or (at your option) any later version. * * This program 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. * * ============================================================ */ #ifndef DIGIKAM_DB_TALKER_H #define DIGIKAM_DB_TALKER_H // Qt includes #include #include #include #include namespace Digikam { class DBTalker : public QObject { Q_OBJECT public: explicit DBTalker(QWidget* const parent); ~DBTalker(); public: void link(); void unLink(); bool authenticated(); void reauthenticate(); void getUserName(); void cancel(); - void listFolders(const QString& path = QString()); + void listFolders(const QString& cursor = QString()); bool addPhoto(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality); void createFolder(const QString& path); Q_SIGNALS: void signalBusy(bool val); void signalLinkingSucceeded(); void signalLinkingFailed(); void signalSetUserName(const QString& msg); void signalListAlbumsFailed(const QString& msg); void signalListAlbumsDone(const QList >& list); void signalCreateFolderFailed(const QString& msg); void signalCreateFolderSucceeded(); void signalAddPhotoFailed(const QString& msg); void signalAddPhotoSucceeded(); private Q_SLOTS: void slotLinkingFailed(); void slotLinkingSucceeded(); void slotOpenBrowser(const QUrl& url); void slotFinished(QNetworkReply* reply); private: void parseResponseUserName(const QByteArray& data); void parseResponseListFolders(const QByteArray& data); void parseResponseCreateFolder(const QByteArray& data); void parseResponseAddPhoto(const QByteArray& data); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_DB_TALKER_H