diff --git a/core/utilities/assistants/webservices/box/boxtalker.cpp b/core/utilities/assistants/webservices/box/boxtalker.cpp index 18df567546..999b4f6ba9 100644 --- a/core/utilities/assistants/webservices/box/boxtalker.cpp +++ b/core/utilities/assistants/webservices/box/boxtalker.cpp @@ -1,503 +1,505 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-05-20 * Description : a tool to export images to Box web service * * Copyright (C) 2018 by Tarek Talaat * * 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 "boxtalker.h" // Qt includes #include #include #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 "wstoolutils.h" #include "boxwindow.h" #include "boxitem.h" #include "previewloadthread.h" #include "o0settingsstore.h" namespace Digikam { class Q_DECL_HIDDEN BOXTalker::Private { public: enum State { BOX_USERNAME = 0, BOX_LISTFOLDERS, BOX_CREATEFOLDER, BOX_ADDPHOTO }; public: explicit Private() { clientId = QLatin1String("w2gevz5rargld3aun22fwmssnmswptrn"); clientSecret = QLatin1String("iIL49xl04DJH7on8NYLrcYTQEgNKVmbs"); authUrl = QLatin1String("https://account.box.com/api/oauth2/authorize"); tokenUrl = QLatin1String("https://api.box.com/oauth2/token"); redirectUrl = QLatin1String("https://app.box.com"); state = BOX_USERNAME; parent = 0; netMngr = 0; reply = 0; settings = 0; o2 = 0; } public: QString clientId; QString clientSecret; QString authUrl; QString tokenUrl; QString redirectUrl; QWidget* parent; QNetworkAccessManager* netMngr; QNetworkReply* reply; QSettings* settings; O2* o2; State state; QByteArray buffer; DMetadata meta; QMap urlParametersMap; QList > foldersList; }; BOXTalker::BOXTalker(QWidget* const parent) : d(new Private) { d->parent = parent; d->netMngr = new QNetworkAccessManager(this); connect(this, SIGNAL(boxLinkingFailed()), this, SLOT(slotLinkingFailed())); connect(this, SIGNAL(boxLinkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); d->o2 = new O2(this); d->o2->setClientId(d->clientId); d->o2->setClientSecret(d->clientSecret); 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("Box")); 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))); } BOXTalker::~BOXTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir("box"); delete d; } void BOXTalker::link() { emit signalBusy(true); d->o2->link(); } void BOXTalker::unLink() { d->o2->unlink(); d->settings->beginGroup(QLatin1String("Box")); d->settings->remove(QString()); d->settings->endGroup(); } void BOXTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Box fail"; emit signalBusy(false); } void BOXTalker::slotLinkingSucceeded() { if (!d->o2->linked()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Box ok"; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Box ok"; emit signalLinkingSucceeded(); } bool BOXTalker::authenticated() { return d->o2->linked(); } void BOXTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } void BOXTalker::slotOpenBrowser(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser..."; QDesktopServices::openUrl(url); } void BOXTalker::createFolder(QString& path) { QString name = path.section(QLatin1Char('/'), -1); QString folderPath = path.section(QLatin1Char('/'), -2, -2); QString id; for (int i = 0 ; i < d->foldersList.size() ; ++i) { if (d->foldersList.value(i).second == folderPath) { id = d->foldersList.value(i).first; } } QUrl url(QLatin1String("https://api.box.com/2.0/folders")); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); QByteArray postData = QString::fromUtf8("{\"name\": \"%1\",\"parent\": {\"id\": \"%2\"}}").arg(name).arg(id).toUtf8(); d->reply = d->netMngr->post(netRequest, postData); d->state = Private::BOX_CREATEFOLDER; d->buffer.resize(0); emit signalBusy(true); } void BOXTalker::getUserName() { QUrl url(QLatin1String("https://api.box.com/2.0/users/me")); QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); d->reply = d->netMngr->get(netRequest); d->state = Private::BOX_USERNAME; d->buffer.resize(0); emit signalBusy(true); } void BOXTalker::listFolders(const QString& /*path*/) { QUrl url(QLatin1String("https://api.box.com/2.0/folders/0/items"));; QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->o2->token()).toUtf8()); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); d->reply = d->netMngr->get(netRequest); d->state = Private::BOX_LISTFOLDERS; d->buffer.resize(0); emit signalBusy(true); } bool BOXTalker::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); QMimeDatabase mimeDB; QString path = imgPath; QString mimeType = mimeDB.mimeTypeForFile(path).name(); if (mimeType.startsWith(QLatin1String("image/"))) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { emit signalBusy(false); return false; } path = WSToolUtils::makeTemporaryDir("box").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.setImageDimensions(image.size()); d->meta.setImageOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); d->meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); d->meta.save(path); } } QString id; for (int i = 0 ; i < d->foldersList.size() ; ++i) { if (d->foldersList.value(i).second == uploadFolder) { id = d->foldersList.value(i).first; } } QHttpMultiPart* const multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart attributes; QString attributesHeader = QLatin1String("form-data; name=\"attributes\""); attributes.setHeader(QNetworkRequest::ContentDispositionHeader, attributesHeader); QString postData = QLatin1String("{\"name\":\"") + QFileInfo(imgPath).fileName() + QLatin1Char('"') + QLatin1String(", \"parent\":{\"id\":\"") + id + QLatin1String("\"}}"); attributes.setBody(postData.toUtf8()); multiPart->append(attributes); QFile* const file = new QFile(path); file->open(QIODevice::ReadOnly); QHttpPart imagePart; QString imagePartHeader = QLatin1String("form-data; name=\"file\"; filename=\"") + QFileInfo(imgPath).fileName() + QLatin1Char('"'); imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, imagePartHeader); imagePart.setHeader(QNetworkRequest::ContentTypeHeader, mimeType); imagePart.setBodyDevice(file); multiPart->append(imagePart); QUrl url(QString::fromLatin1("https://upload.box.com/api/2.0/files/content?access_token=%1").arg(d->o2->token())); QNetworkRequest netRequest(url); QString content = QLatin1String("multipart/form-data;boundary=") + multiPart->boundary(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, content); d->reply = d->netMngr->post(netRequest, multiPart); // delete the multiPart and file with the reply multiPart->setParent(d->reply); d->state = Private::BOX_ADDPHOTO; d->buffer.resize(0); return true; } void BOXTalker::slotFinished(QNetworkReply* reply) { if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state != Private::BOX_CREATEFOLDER) { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); reply->deleteLater(); return; } } d->buffer.append(reply->readAll()); switch (d->state) { case Private::BOX_LISTFOLDERS: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In BOX_LISTFOLDERS"; parseResponseListFolders(d->buffer); break; case Private::BOX_CREATEFOLDER: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In BOX_CREATEFOLDER"; parseResponseCreateFolder(d->buffer); break; case Private::BOX_ADDPHOTO: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In BOX_ADDPHOTO"; parseResponseAddPhoto(d->buffer); break; case Private::BOX_USERNAME: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In BOX_USERNAME"; parseResponseUserName(d->buffer); break; default: break; } reply->deleteLater(); } void BOXTalker::parseResponseAddPhoto(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool success = jsonObject.contains(QLatin1String("total_count")); emit signalBusy(false); if (!success) { emit signalAddPhotoFailed(i18n("Failed to upload photo")); } else { emit signalAddPhotoSucceeded(); } } void BOXTalker::parseResponseUserName(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QString name = doc.object()[QLatin1String("name")].toString(); emit signalBusy(false); emit signalSetUserName(name); } void BOXTalker::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(); d->foldersList.clear(); d->foldersList.append(qMakePair(QLatin1String("0"), QLatin1String("root"))); foreach (const QJsonValue& value, jsonArray) { QString path; QString folderName; QString type; QString id; QJsonObject obj = value.toObject(); type = obj[QLatin1String("type")].toString(); if (type == "folder") { folderName = obj[QLatin1String("name")].toString(); id = obj[QLatin1String("id")].toString(); d->foldersList.append(qMakePair(id, folderName)); } } emit signalBusy(false); emit signalListAlbumsDone(d->foldersList); } void BOXTalker::parseResponseCreateFolder(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool fail = jsonObject.contains(QLatin1String("error")); emit signalBusy(false); if (fail) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); emit signalCreateFolderFailed(jsonObject[QLatin1String("error_summary")].toString()); } else { emit signalCreateFolderSucceeded(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/box/boxtalker.h b/core/utilities/assistants/webservices/box/boxtalker.h index 1ee80eb3e1..3395bc0fd3 100644 --- a/core/utilities/assistants/webservices/box/boxtalker.h +++ b/core/utilities/assistants/webservices/box/boxtalker.h @@ -1,105 +1,103 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-05-20 * Description : a tool to export images to Box web service * * Copyright (C) 2018 by Tarek Talaat * * 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_BOX_TALKER_H #define DIGIKAM_BOX_TALKER_H // Qt includes #include #include #include -#include #include -#include // Local includes #include "boxitem.h" #include "o2.h" #include "o0globals.h" #include "dmetadata.h" namespace Digikam { class BOXTalker : public QObject { Q_OBJECT public: explicit BOXTalker(QWidget* const parent); ~BOXTalker(); public: void link(); void unLink(); void getUserName(); bool authenticated(); void cancel(); bool addPhoto(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality); void listFolders(const QString& path = QString()); void createFolder(QString& path); void setAccessToken(const QString& token); QMap ParseUrlParameters(const QString& url); 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(); void boxLinkingSucceeded(); void boxLinkingFailed(); private Q_SLOTS: void slotLinkingFailed(); void slotLinkingSucceeded(); void slotFinished(QNetworkReply* reply); void slotOpenBrowser(const QUrl& url); private: void parseResponseUserName(const QByteArray& data); void parseResponseListFolders(const QByteArray& data); //QList > parseListFoldersRequest(const QByteArray& data); void parseResponseCreateFolder(const QByteArray& data); void parseResponseAddPhoto(const QByteArray& data); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_BOX_TALKER_H diff --git a/core/utilities/assistants/webservices/dropbox/dbtalker.cpp b/core/utilities/assistants/webservices/dropbox/dbtalker.cpp index 42d3ab64d8..db3c5e4121 100644 --- a/core/utilities/assistants/webservices/dropbox/dbtalker.cpp +++ b/core/utilities/assistants/webservices/dropbox/dbtalker.cpp @@ -1,472 +1,477 @@ /* ============================================================ * * 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 "dbitem.h" #include "dbmpform.h" -#include "previewloadthread.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; QWidget* parent; QNetworkAccessManager* netMngr; QNetworkReply* reply; QSettings* settings; State state; QByteArray buffer; DMetadata meta; 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; d->buffer.resize(0); 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; d->buffer.resize(0); emit signalBusy(true); } /** Get list of folders by parsing json sent by dropbox */ void DBTalker::listFolders(const QString& path) { QUrl url(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()); QByteArray postData = QString::fromUtf8("{\"path\": \"%1\",\"recursive\": true}").arg(path).toUtf8(); d->reply = d->netMngr->post(netRequest, postData); d->state = Private::DB_LISTFOLDERS; d->buffer.resize(0); 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.setImageDimensions(image.size()); d->meta.setImageOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); d->meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); d->meta.save(path); } 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; d->buffer.resize(0); 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; } } d->buffer.append(reply->readAll()); switch (d->state) { case Private::DB_LISTFOLDERS: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_LISTFOLDERS"; parseResponseListFolders(d->buffer); break; case Private::DB_CREATEFOLDER: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_CREATEFOLDER"; parseResponseCreateFolder(d->buffer); break; case Private::DB_ADDPHOTO: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_ADDPHOTO"; parseResponseAddPhoto(d->buffer); break; case Private::DB_USERNAME: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In DB_USERNAME"; parseResponseUserName(d->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"))); 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 name = path.section(QLatin1Char('/'), -1); list.append(qMakePair(path, name)); } } emit signalBusy(false); emit signalListAlbumsDone(list); } 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 cc87b84bfc..45707b41ff 100644 --- a/core/utilities/assistants/webservices/dropbox/dbtalker.h +++ b/core/utilities/assistants/webservices/dropbox/dbtalker.h @@ -1,101 +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 #include -#include - -// Local includes - -#include "dbitem.h" -#include "o2.h" -#include "o0globals.h" -#include "dmetadata.h" 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()); 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 diff --git a/core/utilities/assistants/webservices/facebook/fbtalker.cpp b/core/utilities/assistants/webservices/facebook/fbtalker.cpp index 35ecc7afc0..020a3a5004 100644 --- a/core/utilities/assistants/webservices/facebook/fbtalker.cpp +++ b/core/utilities/assistants/webservices/facebook/fbtalker.cpp @@ -1,789 +1,790 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-12-26 * Description : a tool to export items to Facebook web service * * Copyright (C) 2008-2010 by Luka Renko * Copyright (c) 2011 by Dirk Tilger * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2018 by Thanh Trung Dinh * * 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 "fbtalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include // Local includes #include "digikam_version.h" #include "fbmpform.h" #include "fbnewalbumdlg.h" #include "digikam_debug.h" #include "o0settingsstore.h" #include "wstoolutils.h" namespace Digikam { bool operator< (const FbUser& first, const FbUser& second) { return first.name < second.name; } bool operator< (const FbAlbum& first, const FbAlbum& second) { return first.title < second.title; } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN FbTalker::Private { public: explicit Private(WSNewAlbumDialog* albumDlg) : dialog(0), parent(0), apiURL(QLatin1String("https://graph.facebook.com/%1/%2")), authUrl(QLatin1String("https://www.facebook.com/dialog/oauth")), tokenUrl(QLatin1String("https://graph.facebook.com/oauth/access_token")), apikey(QLatin1String("400589753481372")), clientSecret(QLatin1String("5b0b5cd096e110cd4f4c72f517e2c544")), loginInProgress(false), albumDlg(dynamic_cast(albumDlg)), o2(0), scope(QLatin1String("user_photos,publish_pages,manage_pages")) //publish_to_groups,user_friends not necessary? { } QDialog* dialog; QWidget* parent; QString apiURL; QString authUrl; QString tokenUrl; QString apikey; QString clientSecret; bool loginInProgress; FbUser user; FbNewAlbumDlg* const albumDlg; // Pointer to FbNewAlbumDlg* const so that no modification can impact this pointer //Ported to O2 here O2* o2; QString scope; }; // ----------------------------------------------------------------------------- FbTalker::FbTalker(QWidget* const parent, WSNewAlbumDialog* albumDlg) : WSTalker(parent), d(new Private(albumDlg)) { d->parent = parent; //TODO: Ported to O2 here d->o2 = new O2(this); d->o2->setClientId(d->apikey); d->o2->setClientSecret(d->clientSecret); d->o2->setRequestUrl(d->authUrl); d->o2->setTokenUrl(d->tokenUrl); d->o2->setRefreshTokenUrl(d->tokenUrl); d->o2->setLocalhostPolicy(QLatin1String("https://www.facebook.com/connect/login_success.html")); d->o2->setUseExternalWebInterceptor(true); d->o2->setLocalPort(8000); d->o2->setGrantFlow(O2::GrantFlow::GrantFlowImplicit); d->o2->setScope(d->scope); m_store->setGroupKey(QLatin1String("Facebook")); d->o2->setStore(m_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))); connect(d->o2, SIGNAL(closeBrowser()), this, SLOT(slotCloseBrowser())); } FbTalker::~FbTalker() { delete d; } //TODO: Ported to O2 here // ---------------------------------------------------------------------------------------------- void FbTalker::link() { emit signalBusy(true); d->loginInProgress = true; d->o2->link(); } void FbTalker::unlink() { emit signalBusy(true); d->o2->unlink(); } void FbTalker::slotResponseTokenReceived(const QMap& rep) { d->o2->onVerificationReceived(rep); } bool FbTalker::linked() const { return d->o2->linked(); } void FbTalker::resetTalker(const QString& expire, const QString& accessToken, const QString& refreshToken) { m_store->setValue(QString(O2_KEY_EXPIRES).arg(d->apikey), expire); m_store->setValue(QString(O2_KEY_LINKED).arg(d->apikey), "1"); m_store->setValue(QString(O2_KEY_REFRESH_TOKEN).arg(d->apikey), refreshToken); m_store->setValue(QString(O2_KEY_TOKEN).arg(d->apikey), accessToken); } FbUser FbTalker::getUser() const { return d->user; } void FbTalker::authenticate() { d->loginInProgress = true; emit signalLoginProgress(2, 9, i18n("Validate previous session...")); WSTalker::authenticate(); } void FbTalker::getLoggedInUser() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getLoggedInUser called "; if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(true); emit signalLoginProgress(3); QUrl url(d->apiURL.arg("me") .arg("")); QUrlQuery q; // q.addQueryItem(QLatin1String("fields"), QLatin1String("id,name,link")); q.addQueryItem(QLatin1String("access_token"), d->o2->token().toUtf8()); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url = " << netRequest.url(); m_reply = m_netMngr->get(netRequest); m_state = WSTalker::GETUSER; m_buffer.resize(0); } // ---------------------------------------------------------------------------------------------- /** Compute MD5 signature using url queries keys and values: * http://wiki.developers.facebook.com/index.php/How_Facebook_Authenticates_Your_Application * This method was used for the legacy authentication scheme and has been obsoleted with OAuth2 authentication. */ /* Q S*tring FbTalker::getApiSig(const QMap& args) { QString concat; // NOTE: QMap iterator will sort alphabetically for (QMap::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) { concat.append(it.key()); concat.append("="); concat.append(it.value()); } if (args["session_key"].isEmpty()) concat.append(d->clientSecret); else concat.append(d->sessionSecret); KMD5 md5(concat.toUtf8()); return md5.hexDigest().data(); } */ void FbTalker::logout() { if (m_reply) { m_reply->abort(); m_reply = 0; } QMap args; args[QLatin1String("next")] = QLatin1String("http://www.digikam.org"); args[QLatin1String("access_token")] = d->o2->token().toUtf8(); QUrl url(QLatin1String("https://www.facebook.com/logout.php")); QUrlQuery q; q.addQueryItem(QLatin1String("next"), QLatin1String("http://www.digikam.org")); q.addQueryItem(QLatin1String("access_token"), d->o2->token().toUtf8()); url.setQuery(q); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Logout URL: " << url; QDesktopServices::openUrl(url); emit signalBusy(false); } //TODO: Ported to O2 //---------------------------------------------------------------------------------------------------- void FbTalker::listAlbums(long long userID) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Requesting albums for user " << userID; if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(true); QUrl url; /* * If userID is specified, load albums of that user, * else load albums of current user */ if(!userID) { url = QUrl(d->apiURL.arg(d->user.id) .arg("albums")); } else { url = QUrl(d->apiURL.arg(userID) .arg("albums")); } QUrlQuery q; q.addQueryItem(QLatin1String("fields"), QLatin1String("id,name,description,privacy,link,location")); q.addQueryItem(QLatin1String("access_token"), d->o2->token().toUtf8()); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); m_reply = m_netMngr->get(netRequest); m_state = WSTalker::LISTALBUMS; m_buffer.resize(0); } void FbTalker::createAlbum(const FbAlbum& album) { if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(true); QUrlQuery params; params.addQueryItem("access_token", d->o2->token().toUtf8()); params.addQueryItem("name", album.title); if (!album.location.isEmpty()) params.addQueryItem("location", album.location); /* * description is deprecated and now a param of message */ if (!album.description.isEmpty()) params.addQueryItem("message", album.description); // TODO (Dirk): Wasn't that a requested feature in Bugzilla? switch (album.privacy) { case FB_ME: params.addQueryItem("privacy","{'value':'SELF'}"); break; case FB_FRIENDS: params.addQueryItem("privacy","{'value':'ALL_FRIENDS'}"); break; case FB_FRIENDS_OF_FRIENDS: params.addQueryItem("privacy","{'value':'FRIENDS_OF_FRIENDS'}"); break; case FB_EVERYONE: params.addQueryItem("privacy","{'value':'EVERYONE'}"); break; case FB_CUSTOM: //TODO params.addQueryItem("privacy","{'value':'CUSTOM'}"); break; } QUrl url(QUrl(d->apiURL.arg(d->user.id) .arg("albums"))); // url.setQuery(params); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url to create new album " << netRequest.url() << params.query(); m_reply = m_netMngr->post(netRequest, params.query().toUtf8()); m_state = WSTalker::CREATEALBUM; m_buffer.resize(0); } void FbTalker::createNewAlbum() { FbAlbum album; d->albumDlg->getAlbumProperties(album); createAlbum(album); } void FbTalker::addPhoto(const QString& imgPath, const QString& albumID, const QString& caption) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Adding photo " << imgPath << " to album with id " << albumID << " using caption '" << caption << "'"; if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(true); QMap args; args[QLatin1String("access_token")] = d->o2->token().toUtf8(); if (!caption.isEmpty()) args[QLatin1String("message")] = caption; FbMPForm form; for (QMap::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) { form.addPair(it.key(), it.value()); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "FORM: " << endl << form.formData(); if (!form.addFile(QUrl::fromLocalFile(imgPath).fileName(), imgPath)) { emit signalAddPhotoDone(666, i18n("Cannot open file")); emit signalBusy(false); return; } form.finish(); QVariant arg_1; if (albumID.isEmpty()) { arg_1 = d->user.id; } else { arg_1 = albumID; } QNetworkRequest netRequest(QUrl(d->apiURL.arg(arg_1.toString()).arg("photos"))); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); m_reply = m_netMngr->post(netRequest, form.formData()); m_state = WSTalker::ADDPHOTO; m_buffer.resize(0); } //---------------------------------------------------------------------------------------------------- QString FbTalker::errorToText(int errCode, const QString &errMsg) { QString transError; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "errorToText: " << errCode << ": " << errMsg; switch (errCode) { case 0: transError = QLatin1String(""); break; case 2: transError = i18n("The service is not available at this time."); break; case 4: transError = i18n("The application has reached the maximum number of requests allowed."); break; case 102: transError = i18n("Invalid session key or session expired. Try to log in again."); break; case 120: transError = i18n("Invalid album ID."); break; case 321: transError = i18n("Album is full."); break; case 324: transError = i18n("Missing or invalid file."); break; case 325: transError = i18n("Too many unapproved photos pending."); break; default: transError = errMsg; break; } return transError; } /* * (Trung) This has to be adapted in slotFinished in WSTalker * void FbTalker::slotFinished(QNetworkReply* reply) { if (reply != m_reply) { return; } m_reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->loginInProgress) { authenticationDone(reply->error(), reply->errorString()); } else if (d->state == Private::FB_ADDPHOTO) { emit signalBusy(false); emit signalAddPhotoDone(reply->error(), reply->errorString()); } else { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->error() << " text :"<< QString(reply->readAll()); reply->deleteLater(); return; } m_buffer.append(reply->readAll()); switch(d->state) { case (Private::FB_GETLOGGEDINUSER): parseResponseGetLoggedInUser(m_buffer); break; case (Private::FB_LISTALBUMS): parseResponseListAlbums(m_buffer); break; case (Private::FB_CREATEALBUM): parseResponseCreateAlbum(m_buffer); break; case (Private::FB_ADDPHOTO): parseResponseAddPhoto(m_buffer); break; } reply->deleteLater(); } */ void FbTalker::authenticationDone(int errCode, const QString &errMsg) { if (errCode != 0) { d->user.clear(); } else { saveUserAccount(d->user.name, d->user.id, d->o2->expires(), d->o2->token(), d->o2->refreshToken()); } emit signalLoginDone(errCode, errMsg); d->loginInProgress = false; WSTalker::authenticationDone(errCode, errMsg); } int FbTalker::parseErrorResponse(const QDomElement& e, QString& errMsg) { int errCode = -1; for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) { if (!node.isElement()) continue; if (node.nodeName() == QLatin1String("error_code")) { errCode = node.toElement().text().toInt(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error Code:" << errCode; } else if (node.nodeName() == QLatin1String("error_msg")) { errMsg = node.toElement().text(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error Text:" << errMsg; } } return errCode; } //TODO: Port to O2 void FbTalker::parseResponseGetLoggedInUser(const QByteArray& data) { QString errMsg; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Logged in data " << doc; if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); d->user.id = jsonObject[QLatin1String("id")].toString(); if (!(QString::compare(jsonObject[QLatin1String("id")].toString(), QLatin1String(""), Qt::CaseInsensitive) == 0)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ID found in response of GetLoggedInUser"; } d->user.name = jsonObject[QLatin1String("name")].toString(); m_userName = d->user.name; d->user.profileURL = jsonObject[QLatin1String("link")].toString(); } void FbTalker::parseResponseAddPhoto(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) <<"Parse Add Photo data is "< albumsList; // QList albumsList; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); if (jsonObject.contains(QLatin1String("data"))) { QJsonArray jsonArray = jsonObject[QLatin1String("data")].toArray(); foreach (const QJsonValue & value, jsonArray) { QJsonObject obj = value.toObject(); WSAlbum album; //FbAlbum album; album.id = obj[QLatin1String("id")].toString(); album.title = obj[QLatin1String("name")].toString(); album.location = obj[QLatin1String("location")].toString(); album.url = obj[QLatin1String("link")].toString(); album.description = obj[QLatin1String("description")].toString(); album.uploadable = obj[QLatin1String("can_upload")].toBool(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "can_upload " << album.uploadable; /* if (QString::compare(obj[QLatin1String("privacy")].toString(), QLatin1String("ALL_FRIENDS"), Qt::CaseInsensitive) == 0) { album.privacy = FB_FRIENDS; } else if (QString::compare(obj[QLatin1String("privacy")].toString(), QLatin1String("FRIENDS_OF_FRIENDS"), Qt::CaseInsensitive) == 0) { album.privacy = FB_FRIENDS; } else if (QString::compare(obj[QLatin1String("privacy")].toString(), QLatin1String("EVERYONE"), Qt::CaseInsensitive) == 0) { album.privacy = FB_EVERYONE; } else if (QString::compare(obj[QLatin1String("privacy")].toString(), QLatin1String("CUSTOM"), Qt::CaseInsensitive) == 0) { album.privacy = FB_CUSTOM; } else if (QString::compare(obj[QLatin1String("privacy")].toString(), QLatin1String("SELF"), Qt::CaseInsensitive) == 0) { album.privacy = FB_ME; } */ albumsList.append(album); } errCode = 0; } if (jsonObject.contains(QLatin1String("error"))) { QJsonObject obj = jsonObject[QLatin1String("error")].toObject(); errCode = obj[QLatin1String("code")].toInt(); errMsg = obj[QLatin1String("message")].toString(); } /* std::sort(albumsList.begin(), albumsList.end()); * This function is replaced by method below which is defined in WSTalker as a virtual method for further evolution if needed */ sortAlbumsList(albumsList); emit signalBusy(false); emit signalListAlbumsDone(errCode, errorToText(errCode, errMsg), albumsList); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/facebook/fbtalker.h b/core/utilities/assistants/webservices/facebook/fbtalker.h index 06d27a993f..0d32eb6336 100644 --- a/core/utilities/assistants/webservices/facebook/fbtalker.h +++ b/core/utilities/assistants/webservices/facebook/fbtalker.h @@ -1,115 +1,114 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-12-26 * Description : a tool to export items to Facebook web service * * Copyright (C) 2008-2009 by Luka Renko * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2018 by Thanh Trung Dinh * * 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_FB_TALKER_H #define DIGIKAM_FB_TALKER_H // Qt includes #include #include #include #include #include #include -#include #include #include // Local includes #include "fbitem.h" #include "wsnewalbumdialog.h" #include "wstalker.h" #include "wsitem.h" // O2 include #include "o2.h" #include "o0globals.h" class QDomElement; namespace Digikam { class FbTalker : public WSTalker { Q_OBJECT public: explicit FbTalker(QWidget* const parent, WSNewAlbumDialog* albumDlg=0); ~FbTalker(); - + void link(); void unlink(); bool linked() const; - + void resetTalker(const QString& expire, const QString& accessToken, const QString& refreshToken); FbUser getUser() const; - + void authenticate(); - void logout(); - + void logout(); + void listAlbums(long long userID = 0); void createNewAlbum(); void createAlbum(const FbAlbum& album); void addPhoto(const QString& imgPath, const QString& albumID, const QString& caption); Q_SIGNALS: void signalLoginProgress(int step, int maxStep = 0, const QString& label = QString()); void signalLoginDone(int errCode, const QString& errMsg); private: //QString getApiSig(const QMap& args); void authenticationDone(int errCode, const QString& errMsg); void getLoggedInUser(); QString errorToText(int errCode, const QString& errMsg); int parseErrorResponse(const QDomElement& e, QString& errMsg); void parseResponseGetLoggedInUser(const QByteArray& data); void parseResponseAddPhoto(const QByteArray& data); void parseResponseCreateAlbum(const QByteArray& data); void parseResponseListAlbums(const QByteArray& data); private Q_SLOTS: - + void slotResponseTokenReceived(const QMap& rep); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_FB_TALKER_H diff --git a/core/utilities/assistants/webservices/flickr/flickrtalker.cpp b/core/utilities/assistants/webservices/flickr/flickrtalker.cpp index bb5beaa918..de44cfb3ab 100644 --- a/core/utilities/assistants/webservices/flickr/flickrtalker.cpp +++ b/core/utilities/assistants/webservices/flickr/flickrtalker.cpp @@ -1,1174 +1,1178 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-07-07 * Description : a tool to export images to Flickr web service * * Copyright (C) 2005-2009 by Vardhman Jain * Copyright (C) 2009-2018 by Gilles Caulier * Copyright (C) 2017 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 "flickrtalker.h" // Qt includes -#include #include #include -#include +#include #include -#include +#include #include +#include #include -#include +#include #include +#include #include -#include +#include // Local includes #include "dmetadata.h" #include "wstoolutils.h" #include "flickrmpform.h" -#include "flickritem.h" #include "flickrwindow.h" #include "digikam_debug.h" #include "digikam_version.h" #include "previewloadthread.h" +#include "o1.h" +#include "o0globals.h" +#include "o1requestor.h" +#include "o0settingsstore.h" namespace Digikam { class Q_DECL_HIDDEN FlickrTalker::Private { public: explicit Private() { parent = 0; netMngr = 0; reply = 0; settings = 0; state = FE_LOGOUT; iface = 0; o1 = 0; store = 0; requestor = 0; } QWidget* parent; QByteArray buffer; QString serviceName; QString apiUrl; QString authUrl; QString tokenUrl; QString accessUrl; QString uploadUrl; QString apikey; QString secret; QString maxSize; QString username; QString userId; QString lastTmpFile; QNetworkAccessManager* netMngr; QNetworkReply* reply; QSettings* settings; State state; DInfoInterface* iface; O1* o1; O0SettingsStore* store; O1Requestor* requestor; }; FlickrTalker::FlickrTalker(QWidget* const parent, const QString& serviceName, DInfoInterface* const iface) : d(new Private) { d->parent = parent; d->serviceName = serviceName; d->iface = iface; m_photoSetsList = 0; m_authProgressDlg = 0; if (d->serviceName == QLatin1String("23")) { d->apiUrl = QLatin1String("http://www.23hq.com/services/rest/"); d->authUrl = QLatin1String("http://www.23hq.com/services/auth/"); d->uploadUrl = QLatin1String("http://www.23hq.com/services/upload/"); // bshanks: do 23 and flickr really share API keys? or does 23 not need // one? d->apikey = QLatin1String("49d585bafa0758cb5c58ab67198bf632"); d->secret = QLatin1String("34b39925e6273ffd"); } else { d->apiUrl = QLatin1String("https://www.flickr.com/services/rest/"); d->authUrl = QLatin1String("https://www.flickr.com/services/oauth/authorize?perms=write"); d->tokenUrl = QLatin1String("https://www.flickr.com/services/oauth/request_token"); d->accessUrl = QLatin1String("https://www.flickr.com/services/oauth/access_token"); d->uploadUrl = QLatin1String("https://up.flickr.com/services/upload/"); d->apikey = QLatin1String("49d585bafa0758cb5c58ab67198bf632"); d->secret = QLatin1String("34b39925e6273ffd"); } d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); /* Initialize selected photo set as empty. */ m_selectedPhotoSet = FPhotoSet(); /* Initialize photo sets list. */ m_photoSetsList = new QLinkedList(); d->o1 = new O1(this); d->o1->setClientId(d->apikey); d->o1->setClientSecret(d->secret); d->o1->setAuthorizeUrl(QUrl(d->authUrl)); d->o1->setAccessTokenUrl(QUrl(d->accessUrl)); d->o1->setRequestTokenUrl(QUrl(d->tokenUrl)); d->settings = WSToolUtils::getOauthSettings(this); d->store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this); d->store->setGroupKey(d->serviceName); d->o1->setStore(d->store); connect(d->o1, SIGNAL(linkingFailed()), this, SLOT(slotLinkingFailed())); connect(d->o1, SIGNAL(linkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->o1, SIGNAL(openBrowser(QUrl)), this, SLOT(slotOpenBrowser(QUrl))); d->requestor = new O1Requestor(d->netMngr, d->o1, this); } FlickrTalker::~FlickrTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir(d->serviceName.toLatin1().constData()); delete m_photoSetsList; delete d; } void FlickrTalker::link(const QString& userName) { emit signalBusy(true); if (userName.isEmpty()) { d->store->setGroupKey(d->serviceName); } else { d->store->setGroupKey(d->serviceName + userName); } d->o1->link(); } void FlickrTalker::unLink() { d->o1->unlink(); } void FlickrTalker::removeUserName(const QString& userName) { if (userName.startsWith(d->serviceName)) { d->settings->beginGroup(userName); d->settings->remove(QString()); d->settings->endGroup(); } } void FlickrTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Flickr fail"; d->username = QString(); emit signalBusy(false); } void FlickrTalker::slotLinkingSucceeded() { if (!d->o1->linked()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Flickr ok"; d->username = QString(); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Flickr ok"; d->username = d->o1->extraTokens()[QLatin1String("username")].toString(); d->userId = d->o1->extraTokens()[QLatin1String("user_nsid")].toString(); if (d->store->groupKey() == d->serviceName) { d->settings->beginGroup(d->serviceName); QStringList keys = d->settings->allKeys(); d->settings->endGroup(); foreach(const QString& key, keys) { d->settings->beginGroup(d->serviceName); QVariant value = d->settings->value(key); d->settings->endGroup(); d->settings->beginGroup(d->serviceName + d->username); d->settings->setValue(key, value); d->settings->endGroup(); } d->store->setGroupKey(d->serviceName + d->username); removeUserName(d->serviceName); } emit signalLinkingSucceeded(); } void FlickrTalker::slotOpenBrowser(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser... (" << url << ")"; QDesktopServices::openUrl(url); } QString FlickrTalker::getMaxAllowedFileSize() { return d->maxSize; } void FlickrTalker::maxAllowedFileSize() { if (d->reply) { d->reply->abort(); d->reply = 0; } if (!d->o1->linked()) return; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.people.getLimits"); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_GETMAXSIZE; m_authProgressDlg->setLabelText(i18n("Getting the maximum allowed file size.")); m_authProgressDlg->setMaximum(4); m_authProgressDlg->setValue(1); d->buffer.resize(0); emit signalBusy(true); } void FlickrTalker::listPhotoSets() { if (d->reply) { d->reply->abort(); d->reply = 0; } if (!d->o1->linked()) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "List photoset invoked"; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.photosets.getList"); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_LISTPHOTOSETS; d->buffer.resize(0); emit signalBusy(true); } void FlickrTalker::getPhotoProperty(const QString& method, const QStringList& argList) { if (d->reply) { d->reply->abort(); d->reply = 0; } if (!d->o1->linked()) return; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", method.toLatin1()); for (QStringList::const_iterator it = argList.constBegin(); it != argList.constEnd(); ++it) { QStringList str = (*it).split(QLatin1Char('='), QString::SkipEmptyParts); reqParams << O0RequestParameter(str[0].toLatin1(), str[1].toLatin1()); } QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_GETPHOTOPROPERTY; d->buffer.resize(0); emit signalBusy(true); } void FlickrTalker::listPhotos(const QString& /*albumName*/) { // TODO } void FlickrTalker::createPhotoSet(const QString& /*albumName*/, const QString& albumTitle, const QString& albumDescription, const QString& primaryPhotoId) { if (d->reply) { d->reply->abort(); d->reply = 0; } if (!d->o1->linked()) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Create photoset invoked"; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.photosets.create"); reqParams << O0RequestParameter("title", albumTitle.toLatin1()); reqParams << O0RequestParameter("description", albumDescription.toLatin1()); reqParams << O0RequestParameter("primary_photo_id", primaryPhotoId.toLatin1()); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_CREATEPHOTOSET; d->buffer.resize(0); emit signalBusy(true); } void FlickrTalker::addPhotoToPhotoSet(const QString& photoId, const QString& photoSetId) { if (d->reply) { d->reply->abort(); d->reply = 0; } if (!d->o1->linked()) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "AddPhotoToPhotoSet invoked"; /* If the photoset id starts with the special string "UNDEFINED_", it means * it doesn't exist yet on Flickr and needs to be created. Note that it's * not necessary to subsequently add the photo to the photo set, as this * is done in the set creation call to Flickr. */ if (photoSetId.startsWith(QLatin1String("UNDEFINED_"))) { createPhotoSet(QLatin1String(""), m_selectedPhotoSet.title, m_selectedPhotoSet.description, photoId); } else { QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.photosets.addPhoto"); reqParams << O0RequestParameter("photoset_id", photoSetId.toLatin1()); reqParams << O0RequestParameter("photo_id", photoId.toLatin1()); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_ADDPHOTOTOPHOTOSET; d->buffer.resize(0); emit signalBusy(true); } } bool FlickrTalker::addPhoto(const QString& photoPath, const FPhotoInfo& info, bool original, bool rescale, int maxDim, int imageQuality) { if (d->reply) { d->reply->abort(); d->reply = 0; } if (!d->o1->linked()) return false; emit signalBusy(true); QUrl url(d->uploadUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); QString path = photoPath; FlickrMPForm form; QString ispublic = (info.is_public == 1) ? QLatin1String("1") : QLatin1String("0"); form.addPair(QLatin1String("is_public"), ispublic, QLatin1String("text/plain")); reqParams << O0RequestParameter("is_public", ispublic.toLatin1()); QString isfamily = (info.is_family == 1) ? QLatin1String("1") : QLatin1String("0"); form.addPair(QLatin1String("is_family"), isfamily, QLatin1String("text/plain")); reqParams << O0RequestParameter("is_family", isfamily.toLatin1()); QString isfriend = (info.is_friend == 1) ? QLatin1String("1") : QLatin1String("0"); form.addPair(QLatin1String("is_friend"), isfriend, QLatin1String("text/plain")); reqParams << O0RequestParameter("is_friend", isfriend.toLatin1()); QString safetyLevel = QString::number(static_cast(info.safety_level)); form.addPair(QLatin1String("safety_level"), safetyLevel, QLatin1String("text/plain")); reqParams << O0RequestParameter("safety_level", safetyLevel.toLatin1()); QString contentType = QString::number(static_cast(info.content_type)); form.addPair(QLatin1String("content_type"), contentType, QLatin1String("text/plain")); reqParams << O0RequestParameter("content_type", contentType.toLatin1()); QString tags = QLatin1Char('"') + info.tags.join(QLatin1String("\" \"")) + QLatin1Char('"'); if (tags.length() > 0) { form.addPair(QLatin1String("tags"), tags, QLatin1String("text/plain")); reqParams << O0RequestParameter("tags", tags.toUtf8()); } if (!info.title.isEmpty()) { form.addPair(QLatin1String("title"), info.title, QLatin1String("text/plain")); reqParams << O0RequestParameter("title", info.title.toUtf8()); } if (!info.description.isEmpty()) { form.addPair(QLatin1String("description"), info.description, QLatin1String("text/plain")); reqParams << O0RequestParameter("description", info.description.toUtf8()); } if (!original) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage(); if (image.isNull()) { image.load(photoPath); } if (!image.isNull()) { if (!d->lastTmpFile.isEmpty()) { QFile::remove(d->lastTmpFile); } path = WSToolUtils::makeTemporaryDir(d->serviceName.toLatin1().constData()).filePath(QFileInfo(photoPath) .baseName().trimmed() + QLatin1String(".jpg")); if (rescale) { if (image.width() > maxDim || image.height() > maxDim) image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); } image.save(path, "JPEG", imageQuality); d->lastTmpFile = path; // Restore all metadata. DMetadata meta; if (meta.load(photoPath)) { meta.setImageDimensions(image.size()); meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL); // NOTE: see bug #153207: Flickr use IPTC keywords to create Tags in web interface // As IPTC do not support UTF-8, we need to remove it. // This function call remove all Application2 Tags. meta.removeIptcTags(QStringList() << QLatin1String("Application2")); // NOTE: see bug # 384260: Flickr use Xmp.dc.subject to create Tags // in web interface, we need to remove it. // This function call remove all Dublin Core Tags. meta.removeXmpTags(QStringList() << QLatin1String("dc")); meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); meta.save(path); } else { qCWarning(DIGIKAM_WEBSERVICES_LOG) << "Flickr::Image do not have metadata"; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing and saving to temp file: " << path; } } QFileInfo tempFileInfo(path); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "QUrl path is " << QUrl::fromLocalFile(path) << "Image size (in bytes) is "<< tempFileInfo.size(); if (tempFileInfo.size() > (getMaxAllowedFileSize().toLongLong())) { emit signalAddPhotoFailed(i18n("File Size exceeds maximum allowed file size.")); emit signalBusy(false); return false; } if (!form.addFile(QLatin1String("photo"), path)) { emit signalBusy(false); return false; } form.finish(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); d->reply = d->requestor->post(netRequest, reqParams, form.formData()); d->state = FE_ADDPHOTO; d->buffer.resize(0); return true; } void FlickrTalker::setGeoLocation(const QString& photoId, const QString& lat, const QString& lon) { if (d->reply) { d->reply->abort(); d->reply = 0; } if (!d->o1->linked()) return; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); QList reqParams = QList(); reqParams << O0RequestParameter("method", "flickr.photos.geo.setLocation"); reqParams << O0RequestParameter("photo_id", photoId.toLatin1()); reqParams << O0RequestParameter("lat", lat.toLatin1()); reqParams << O0RequestParameter("lon", lon.toLatin1()); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_SETGEO; d->buffer.resize(0); emit signalBusy(true); } QString FlickrTalker::getUserName() const { return d->username; } QString FlickrTalker::getUserId() const { return d->userId; } void FlickrTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } if (m_authProgressDlg && !m_authProgressDlg->isHidden()) { m_authProgressDlg->hide(); } } void FlickrTalker::slotError(const QString& error) { QString transError; int errorNo = error.toInt(); switch (errorNo) { case 2: transError = i18n("No photo specified"); break; case 3: transError = i18n("General upload failure"); break; case 4: transError = i18n("Filesize was zero"); break; case 5: transError = i18n("Filetype was not recognized"); break; case 6: transError = i18n("User exceeded upload limit"); break; case 96: transError = i18n("Invalid signature"); break; case 97: transError = i18n("Missing signature"); break; case 98: transError = i18n("Login Failed / Invalid auth token"); break; case 100: transError = i18n("Invalid API Key"); break; case 105: transError = i18n("Service currently unavailable"); break; case 108: transError = i18n("Invalid Frob"); break; case 111: transError = i18n("Format \"xxx\" not found"); break; case 112: transError = i18n("Method \"xxx\" not found"); break; case 114: transError = i18n("Invalid SOAP envelope"); break; case 115: transError = i18n("Invalid XML-RPC Method Call"); break; case 116: transError = i18n("The POST method is now required for all setters"); break; default: transError = i18n("Unknown error"); break; }; QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("Error Occurred: %1\nCannot proceed any further.", transError)); } void FlickrTalker::slotFinished(QNetworkReply* reply) { emit signalBusy(false); if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state == FE_ADDPHOTO) { emit signalAddPhotoFailed(reply->errorString()); } else { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); } reply->deleteLater(); return; } d->buffer.append(reply->readAll()); switch (d->state) { case (FE_LOGIN): //parseResponseLogin(d->buffer); break; case (FE_LISTPHOTOSETS): parseResponseListPhotoSets(d->buffer); break; case (FE_LISTPHOTOS): parseResponseListPhotos(d->buffer); break; case (FE_GETPHOTOPROPERTY): parseResponsePhotoProperty(d->buffer); break; case (FE_ADDPHOTO): parseResponseAddPhoto(d->buffer); break; case (FE_ADDPHOTOTOPHOTOSET): parseResponseAddPhotoToPhotoSet(d->buffer); break; case (FE_CREATEPHOTOSET): parseResponseCreatePhotoSet(d->buffer); break; case (FE_GETMAXSIZE): parseResponseMaxSize(d->buffer); break; case (FE_SETGEO): parseResponseSetGeoLocation(d->buffer); break; default: // FR_LOGOUT break; } reply->deleteLater(); } void FlickrTalker::parseResponseMaxSize(const QByteArray& data) { QString errorString; QDomDocument doc(QLatin1String("mydocument")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("person")) { e = node.toElement(); QDomNode details = e.firstChild(); while (!details.isNull()) { if (details.isElement()) { e = details.toElement(); if (details.nodeName() == QLatin1String("videos")) { QDomAttr a = e.attributeNode(QLatin1String("maxupload")); d->maxSize = a.value(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Max upload size is"<maxSize; } } details = details.nextSibling(); } } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; errorString = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << errorString; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); } node = node.nextSibling(); } m_authProgressDlg->hide(); } void FlickrTalker::parseResponseCreatePhotoSet(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse response create photoset received " << data; //bool success = false; QDomDocument doc(QLatin1String("getListPhotoSets")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photoset")) { // Parse the id from the response. QString new_id = node.toElement().attribute(QLatin1String("id")); // Set the new id in the photo sets list. QLinkedList::iterator it = m_photoSetsList->begin(); while (it != m_photoSetsList->end()) { if (it->id == m_selectedPhotoSet.id) { it->id = new_id; break; } ++it; } // Set the new id in the selected photo set. m_selectedPhotoSet.id = new_id; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "PhotoSet created successfully with id" << new_id; emit signalAddPhotoSetSucceeded(); } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; QString msg = node.toElement().attribute(QLatin1String("msg")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << msg; QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("PhotoSet creation failed: ") + msg); } node = node.nextSibling(); } } void FlickrTalker::parseResponseListPhotoSets(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotosets" << data; bool success = false; QDomDocument doc(QLatin1String("getListPhotoSets")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; QString photoSet_id, photoSet_title, photoSet_description; m_photoSetsList->clear(); while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photosets")) { e = node.toElement(); QDomNode details = e.firstChild(); FPhotoSet fps; QDomNode detailsNode = details; while (!detailsNode.isNull()) { if (detailsNode.isElement()) { e = detailsNode.toElement(); if (detailsNode.nodeName() == QLatin1String("photoset")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "id=" << e.attribute(QLatin1String("id")); photoSet_id = e.attribute(QLatin1String("id")); // this is what is obtained from data. fps.id = photoSet_id; QDomNode photoSetDetails = detailsNode.firstChild(); QDomElement e_detail; while (!photoSetDetails.isNull()) { e_detail = photoSetDetails.toElement(); if (photoSetDetails.nodeName() == QLatin1String("title")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Title=" << e_detail.text(); photoSet_title = e_detail.text(); fps.title = photoSet_title; } else if (photoSetDetails.nodeName() == QLatin1String("description")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Description =" << e_detail.text(); photoSet_description = e_detail.text(); fps.description = photoSet_description; } photoSetDetails = photoSetDetails.nextSibling(); } m_photoSetsList->append(fps); } } detailsNode = detailsNode.nextSibling(); } details = details.nextSibling(); success = true; } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); emit signalError(code); } node = node.nextSibling(); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "GetPhotoList finished"; if (!success) { emit signalListPhotoSetsFailed(i18n("Failed to fetch list of photo sets.")); } else { emit signalListPhotoSetsSucceeded(); maxAllowedFileSize(); } } void FlickrTalker::parseResponseListPhotos(const QByteArray& data) { QDomDocument doc(QLatin1String("getPhotosList")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); //QDomElement e; //TODO } void FlickrTalker::parseResponseCreateAlbum(const QByteArray& data) { QDomDocument doc(QLatin1String("getCreateAlbum")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); //TODO } void FlickrTalker::parseResponseAddPhoto(const QByteArray& data) { bool success = false; QString line; QDomDocument doc(QLatin1String("AddPhoto Response")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; QString photoId; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photoid")) { e = node.toElement(); // try to convert the node to an element. QDomNode details = e.firstChild(); photoId = e.text(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Photoid= " << photoId; success = true; } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); emit signalError(code); } node = node.nextSibling(); } if (!success) { emit signalAddPhotoFailed(i18n("Failed to upload photo")); } else { QString photoSetId = m_selectedPhotoSet.id; if (photoSetId == QLatin1String("-1")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "PhotoSet Id not set, not adding the photo to any photoset"; emit signalAddPhotoSucceeded(photoId); } else { addPhotoToPhotoSet(photoId, photoSetId); } } } void FlickrTalker::parseResponsePhotoProperty(const QByteArray& data) { bool success = false; QString line; QDomDocument doc(QLatin1String("Photos Properties")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photoid")) { e = node.toElement(); // try to convert the node to an element. QDomNode details = e.firstChild(); success = true; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Photoid=" << e.text(); } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); emit signalError(code); } node = node.nextSibling(); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "GetToken finished"; if (!success) { emit signalAddPhotoFailed(i18n("Failed to query photo information")); } else { emit signalAddPhotoSucceeded(QLatin1String("")); } } void FlickrTalker::parseResponseAddPhotoToPhotoSet(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotosets" << data; emit signalAddPhotoSucceeded(QLatin1String("")); } void FlickrTalker::parseResponseSetGeoLocation(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseSetGeoLocation" << data; emit signalAddPhotoSucceeded(QLatin1String("")); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/flickr/flickrtalker.h b/core/utilities/assistants/webservices/flickr/flickrtalker.h index d6385a993b..9781860021 100644 --- a/core/utilities/assistants/webservices/flickr/flickrtalker.h +++ b/core/utilities/assistants/webservices/flickr/flickrtalker.h @@ -1,151 +1,146 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-07-07 * Description : a tool to export images to Flickr web service * * Copyright (C) 2005-2009 by Vardhman Jain * Copyright (C) 2009-2018 by Gilles Caulier * Copyright (C) 2017 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. * * ============================================================ */ #ifndef DIGIKAM_FLICKR_TALKER_H #define DIGIKAM_FLICKR_TALKER_H // Qt includes #include #include #include #include #include #include -#include // Local includes #include "dinfointerface.h" #include "flickritem.h" -#include "o1.h" -#include "o0globals.h" -#include "o1requestor.h" -#include "o0settingsstore.h" class QProgressDialog; namespace Digikam { class FPhotoInfo; class FPhotoSet; class FlickrTalker : public QObject { Q_OBJECT public: enum State { FE_LOGOUT = -1, FE_LOGIN = 0, FE_LISTPHOTOSETS, FE_LISTPHOTOS, FE_GETPHOTOPROPERTY, FE_ADDPHOTO, FE_CREATEPHOTOSET, FE_ADDPHOTOTOPHOTOSET, FE_GETMAXSIZE, FE_SETGEO }; public: explicit FlickrTalker(QWidget* const parent, const QString& serviceName, DInfoInterface* const iface); ~FlickrTalker(); void link(const QString& userName); void unLink(); void removeUserName(const QString& userName); QString getUserName() const; QString getUserId() const; void maxAllowedFileSize(); QString getMaxAllowedFileSize(); void getPhotoProperty(const QString& method, const QStringList& argList); void cancel(); void listPhotoSets(); void listPhotos(const QString& albumName); void createPhotoSet(const QString& name, const QString& title, const QString& desc, const QString& primaryPhotoId); void addPhotoToPhotoSet(const QString& photoId, const QString& photoSetId); bool addPhoto(const QString& photoPath, const FPhotoInfo& info, bool original = false, bool rescale = false, int maxDim = 600, int imageQuality = 85); void setGeoLocation(const QString& photoId, const QString& lat, const QString& lon); public: QProgressDialog* m_authProgressDlg; QLinkedList * m_photoSetsList; FPhotoSet m_selectedPhotoSet; Q_SIGNALS: void signalError(const QString& msg); void signalBusy(bool val); void signalAddPhotoSucceeded(const QString&); void signalAddPhotoSetSucceeded(); void signalListPhotoSetsSucceeded(); void signalListPhotoSetsFailed(QString& msg); void signalAddPhotoFailed(const QString& msg); void signalListPhotoSetsFailed(const QString& msg); void signalLinkingSucceeded(); private: // void parseResponseLogin(const QByteArray& data); void parseResponseMaxSize(const QByteArray& data); void parseResponseListPhotoSets(const QByteArray& data); void parseResponseListPhotos(const QByteArray& data); void parseResponseCreateAlbum(const QByteArray& data); void parseResponseAddPhoto(const QByteArray& data); void parseResponsePhotoProperty(const QByteArray& data); void parseResponseCreatePhotoSet(const QByteArray& data); void parseResponseAddPhotoToPhotoSet(const QByteArray& data); void parseResponseSetGeoLocation(const QByteArray& data); private Q_SLOTS: void slotLinkingFailed(); void slotLinkingSucceeded(); void slotOpenBrowser(const QUrl& url); void slotError(const QString& msg); void slotFinished(QNetworkReply* reply); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_FLICKR_TALKER_H diff --git a/core/utilities/assistants/webservices/onedrive/odtalker.cpp b/core/utilities/assistants/webservices/onedrive/odtalker.cpp index a1e5bbd4a7..202d8a4e9e 100644 --- a/core/utilities/assistants/webservices/onedrive/odtalker.cpp +++ b/core/utilities/assistants/webservices/onedrive/odtalker.cpp @@ -1,552 +1,553 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-05-20 * Description : a tool to export images to Onedrive web service * * Copyright (C) 2018 by Tarek Talaat * * 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 "odtalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include // KDE includes #include #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "digikam_version.h" #include "wstoolutils.h" #include "odwindow.h" #include "oditem.h" #include "odmpform.h" #include "previewloadthread.h" #ifdef HAVE_QWEBENGINE # include "webwidget_qwebengine.h" #else # include "webwidget.h" #endif namespace Digikam { class Q_DECL_HIDDEN ODTalker::Private { public: enum State { OD_USERNAME = 0, OD_LISTFOLDERS, OD_CREATEFOLDER, OD_ADDPHOTO }; public: explicit Private() { clientId = QLatin1String("83de95b7-0f35-4abf-bac1-7729ced74c01"); clientSecret = QLatin1String("tgjV796:)yezuRWMZSZ45+!"); scope = QLatin1String("User.Read Files.ReadWrite"); authUrl = QLatin1String("https://login.live.com/oauth20_authorize.srf"); tokenUrl = QLatin1String("https://login.live.com/oauth20_token.srf"); redirectUrl = QLatin1String("https://login.live.com/oauth20_desktop.srf"); state = OD_USERNAME; parent = 0; netMngr = 0; reply = 0; view = 0; } public: QString clientId; QString clientSecret; QString authUrl; QString tokenUrl; QString scope; QString redirectUrl; QString accessToken; QWidget* parent; QNetworkAccessManager* netMngr; QNetworkReply* reply; State state; QByteArray buffer; DMetadata meta; QMap urlParametersMap; WebWidget* view; QString tokenKey; }; ODTalker::ODTalker(QWidget* const parent) : d(new Private) { d->parent = parent; d->netMngr = new QNetworkAccessManager(this); d->view = new WebWidget(d->parent); connect(this, SIGNAL(oneDriveLinkingFailed()), this, SLOT(slotLinkingFailed())); connect(this, SIGNAL(oneDriveLinkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); connect(d->view, SIGNAL(closeView(bool)), this, SIGNAL(signalBusy(bool))); } ODTalker::~ODTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir("onedrive"); delete d; } void ODTalker::link() { emit signalBusy(true); QUrl url(d->authUrl); QUrlQuery query(url); query.addQueryItem(QLatin1String("client_id"), d->clientId); query.addQueryItem(QLatin1String("scope"), d->scope); query.addQueryItem(QLatin1String("redirect_uri"), d->redirectUrl); query.addQueryItem(QLatin1String("response_type"), "token"); url.setQuery(query); d->view->setWindowFlags(Qt::Dialog); d->view->load(url); d->view->show(); connect(d->view, SIGNAL(urlChanged(QUrl)), this, SLOT(slotCatchUrl(QUrl))); } void ODTalker::unLink() { d->accessToken = QString(); KConfig config; KConfigGroup grp = config.group("Onedrive User Settings"); grp.deleteGroup(); #ifdef HAVE_QWEBENGINE d->view->page()->profile()->cookieStore()->deleteAllCookies(); #else d->view->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); #endif emit oneDriveLinkingSucceeded(); } void ODTalker::slotCatchUrl(const QUrl& url) { d->urlParametersMap = ParseUrlParameters(url.toString()); d->accessToken = d->urlParametersMap.value("access_token"); //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received URL from webview in link function: " << url ; if (!d->accessToken.isEmpty()) { qDebug(DIGIKAM_WEBSERVICES_LOG) << "Access Token Received"; emit oneDriveLinkingSucceeded(); } else { emit oneDriveLinkingFailed(); } } QMap ODTalker::ParseUrlParameters(const QString& url) { QMap urlParameters; if (url.indexOf('?') == -1) { return urlParameters; } QString tmp = url.right(url.length() - url.indexOf('?') - 1); tmp = tmp.right(tmp.length() - tmp.indexOf('#') - 1); QStringList paramlist = tmp.split('&'); for (int i = 0 ; i < paramlist.count() ; ++i) { QStringList paramarg = paramlist.at(i).split('='); urlParameters.insert(paramarg.at(0),paramarg.at(1)); } return urlParameters; } void ODTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Onedrive fail"; emit signalBusy(false); } void ODTalker::slotLinkingSucceeded() { if (d->accessToken.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Onedrive ok"; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Onedrive ok"; writeSettings(); d->view->close(); emit signalLinkingSucceeded(); } bool ODTalker::authenticated() { return (!d->accessToken.isEmpty()); } void ODTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } void ODTalker::createFolder(QString& path) { //path also has name of new folder so send path parameter accordingly QString name = path.section(QLatin1Char('/'), -1); QString folderPath = path.section(QLatin1Char('/'), -2, -2); QUrl url; if (folderPath.isEmpty()) { url = QLatin1String("https://graph.microsoft.com/v1.0/me/drive/root/children"); } else { url = QString("https://graph.microsoft.com/v1.0/me/drive/root:/%1:/children").arg(folderPath); } QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", QString::fromLatin1("bearer %1").arg(d->accessToken).toUtf8()); QByteArray postData = QString::fromUtf8("{\"name\": \"%1\",\"folder\": {}}").arg(name).toUtf8(); d->reply = d->netMngr->post(netRequest, postData); d->state = Private::OD_CREATEFOLDER; d->buffer.resize(0); emit signalBusy(true); } void ODTalker::getUserName() { QUrl url(QLatin1String("https://graph.microsoft.com/v1.0/me")); QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("bearer %1").arg(d->accessToken).toUtf8()); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); d->reply = d->netMngr->get(netRequest); d->state = Private::OD_USERNAME; d->buffer.resize(0); emit signalBusy(true); } /** Get list of folders by parsing json sent by onedrive */ void ODTalker::listFolders() { QUrl url(QLatin1String("https://graph.microsoft.com/v1.0/me//drive/root/children?select=name,folder,path,parentReference"));; QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("bearer %1").arg(d->accessToken).toUtf8()); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); d->reply = d->netMngr->get(netRequest); d->state = Private::OD_LISTFOLDERS; d->buffer.resize(0); emit signalBusy(true); } bool ODTalker::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); ODMPForm form; QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { emit signalBusy(false); return false; } QString path = WSToolUtils::makeTemporaryDir("onedrive").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.setImageDimensions(image.size()); d->meta.setImageOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); d->meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); d->meta.save(path); } if (!form.addFile(path)) { emit signalBusy(false); return false; } QString uploadPath = uploadFolder + QUrl(QUrl::fromLocalFile(imgPath)).fileName(); QUrl url(QString::fromLatin1("https://graph.microsoft.com/v1.0/me/drive/root:/%1:/content").arg(uploadPath)); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/octet-stream")); netRequest.setRawHeader("Authorization", QString::fromLatin1("bearer {%1}").arg(d->accessToken).toUtf8()); d->reply = d->netMngr->put(netRequest, form.formData()); d->state = Private::OD_ADDPHOTO; d->buffer.resize(0); return true; } void ODTalker::slotFinished(QNetworkReply* reply) { if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state != Private::OD_CREATEFOLDER) { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); reply->deleteLater(); return; } } d->buffer.append(reply->readAll()); switch (d->state) { case Private::OD_LISTFOLDERS: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In OD_LISTFOLDERS"; parseResponseListFolders(d->buffer); break; case Private::OD_CREATEFOLDER: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In OD_CREATEFOLDER"; parseResponseCreateFolder(d->buffer); break; case Private::OD_ADDPHOTO: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In OD_ADDPHOTO"; parseResponseAddPhoto(d->buffer); break; case Private::OD_USERNAME: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In OD_USERNAME"; parseResponseUserName(d->buffer); break; default: break; } reply->deleteLater(); } void ODTalker::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 ODTalker::parseResponseUserName(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QString name = doc.object()[QLatin1String("displayName")].toString(); emit signalBusy(false); emit signalSetUserName(name); } void ODTalker::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(); //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Json: " << doc; QJsonArray jsonArray = jsonObject[QLatin1String("value")].toArray(); QList > list; list.append(qMakePair(QLatin1String(""), QLatin1String("root"))); foreach (const QJsonValue& value, jsonArray) { QString path; QString folderName; QJsonObject folder; QJsonObject obj = value.toObject(); folder = obj[QLatin1String("folder")].toObject(); if (!folder.isEmpty()) { folderName = obj[QLatin1String("name")].toString(); path = QLatin1Char('/') + folderName; list.append(qMakePair(path, folderName)); } } emit signalBusy(false); emit signalListAlbumsDone(list); } void ODTalker::parseResponseCreateFolder(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool fail = jsonObject.contains(QLatin1String("error")); emit signalBusy(false); if (fail) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); emit signalCreateFolderFailed(jsonObject[QLatin1String("error_summary")].toString()); } else { emit signalCreateFolderSucceeded(); } } void ODTalker::writeSettings() { KConfig config; KConfigGroup grp = config.group("Onedrive User Settings"); grp.writeEntry("access_token", d->accessToken); config.sync(); } void ODTalker::readSettings() { KConfig config; KConfigGroup grp = config.group("Onedrive User Settings"); if (!grp.readEntry("access_token", false)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Linking..."; link(); } else { d->accessToken = grp.readEntry("access_token", QString()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Already Linked"; emit oneDriveLinkingSucceeded(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/onedrive/odtalker.h b/core/utilities/assistants/webservices/onedrive/odtalker.h index a933170501..b56f515920 100644 --- a/core/utilities/assistants/webservices/onedrive/odtalker.h +++ b/core/utilities/assistants/webservices/onedrive/odtalker.h @@ -1,104 +1,102 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-05-20 * Description : a tool to export images to Onedrive web service * * Copyright (C) 2018 by Tarek Talaat * * 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_OD_TALKER_H #define DIGIKAM_OD_TALKER_H // Qt includes #include #include #include -#include #include -#include // Local includes #include "oditem.h" #include "dmetadata.h" namespace Digikam { class ODTalker : public QObject { Q_OBJECT public: explicit ODTalker(QWidget* const parent); ~ODTalker(); public: void link(); void unLink(); void getUserName(); bool authenticated(); void cancel(); bool addPhoto(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality); void listFolders(); void createFolder(QString& path); void setAccessToken(const QString& token); QMap ParseUrlParameters(const QString& url); void readSettings(); void writeSettings(); 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(); void oneDriveLinkingSucceeded(); void oneDriveLinkingFailed(); private Q_SLOTS: void slotLinkingFailed(); void slotLinkingSucceeded(); void slotCatchUrl(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_OD_TALKER_H diff --git a/core/utilities/assistants/webservices/pinterest/ptalker.cpp b/core/utilities/assistants/webservices/pinterest/ptalker.cpp index f20e1e5a47..ee6c5b332a 100644 --- a/core/utilities/assistants/webservices/pinterest/ptalker.cpp +++ b/core/utilities/assistants/webservices/pinterest/ptalker.cpp @@ -1,625 +1,626 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-05-20 * Description : a tool to export images to Pinterest web service * * Copyright (C) 2018 by Tarek Talaat * * 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 "ptalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include // KDE includes #include #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "digikam_version.h" #include "wstoolutils.h" #include "pwindow.h" #include "pitem.h" #include "pmpform.h" #include "previewloadthread.h" #ifdef HAVE_QWEBENGINE # include "webwidget_qwebengine.h" #else # include "webwidget.h" #endif namespace Digikam { class Q_DECL_HIDDEN PTalker::Private { public: enum State { P_USERNAME = 0, P_LISTBOARDS, P_CREATEBOARD, P_ADDPIN, P_ACCESSTOKEN }; public: explicit Private() { clientId = QLatin1String("4983380570301022071"); clientSecret = QLatin1String("2a698db679125930d922a2dfb897e16b668a67c6f614593636e83fc3d8d9b47d"); authUrl = QLatin1String("https://api.pinterest.com/oauth/"); tokenUrl = QLatin1String("https://api.pinterest.com/v1/oauth/token"); redirectUrl = QLatin1String("https://login.live.com/oauth20_desktop.srf"); scope = QLatin1String("read_public,write_public"); state = P_USERNAME; parent = 0; netMngr = 0; reply = 0; view = 0; } public: QString clientId; QString clientSecret; QString authUrl; QString tokenUrl; QString redirectUrl; QString accessToken; QString scope; QWidget* parent; QNetworkAccessManager* netMngr; QNetworkReply* reply; State state; QByteArray buffer; DMetadata meta; QMap urlParametersMap; WebWidget* view; QString userName; }; PTalker::PTalker(QWidget* const parent) : d(new Private) { d->parent = parent; d->netMngr = new QNetworkAccessManager(this); d->view = new WebWidget(d->parent); #ifndef HAVE_QWEBENGINE d->view->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); #endif connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); connect(this, SIGNAL(pinterestLinkingFailed()), this, SLOT(slotLinkingFailed())); connect(this, SIGNAL(pinterestLinkingSucceeded()), this, SLOT(slotLinkingSucceeded())); } PTalker::~PTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir("pinterest"); delete d; } void PTalker::link() { emit signalBusy(true); QUrl url(d->authUrl); QUrlQuery query(url); query.addQueryItem(QLatin1String("client_id"), d->clientId); query.addQueryItem(QLatin1String("scope"), d->scope); query.addQueryItem(QLatin1String("redirect_uri"), d->redirectUrl); query.addQueryItem(QLatin1String("response_type"), "code"); url.setQuery(query); d->view->setWindowFlags(Qt::Dialog); d->view->show(); d->view->load(url); connect(d->view, SIGNAL(urlChanged(QUrl)), this, SLOT(slotCatchUrl(QUrl))); connect(d->view, SIGNAL(closeView(bool)), this, SIGNAL(signalBusy(bool))); } void PTalker::unLink() { d->accessToken = QString(); KConfig config; KConfigGroup grp = config.group("Pinterest User Settings"); grp.deleteGroup(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "group deleted"; #ifdef HAVE_QWEBENGINE d->view->page()->profile()->cookieStore()->deleteAllCookies(); #else d->view->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); #endif emit pinterestLinkingSucceeded(); } void PTalker::slotCatchUrl(const QUrl& url) { d->urlParametersMap = ParseUrlParameters(url.toString()); QString code = d->urlParametersMap.value("code"); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received URL from webview in link function: " << url ; if (!code.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "CODE Received"; d->view->close(); getToken(code); emit signalBusy(false); } } void PTalker::getToken(const QString& code) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Code: " << code; QUrl url(d->tokenUrl); QUrlQuery query(url); query.addQueryItem(QLatin1String("grant_type"), "authorization_code"); query.addQueryItem(QLatin1String("client_id"), d->clientId); query.addQueryItem(QLatin1String("client_secret"), d->clientSecret); query.addQueryItem(QLatin1String("code"), code); url.setQuery(query); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Token Request URL: " << url.toString(); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); netRequest.setRawHeader("Accept", "application/json"); d->reply = d->netMngr->post(netRequest, QByteArray()); d->state = Private::P_ACCESSTOKEN; d->buffer.resize(0); } QMap PTalker::ParseUrlParameters(const QString& url) { QMap urlParameters; if (url.indexOf('?') == -1) { return urlParameters; } QString tmp = url.right(url.length()-url.indexOf('?') - 1); QStringList paramlist = tmp.split('&'); for (int i = 0 ; i < paramlist.count() ; ++i) { QStringList paramarg = paramlist.at(i).split('='); urlParameters.insert(paramarg.at(0), paramarg.at(1)); } return urlParameters; } void PTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Pinterest fail"; emit signalBusy(false); } void PTalker::slotLinkingSucceeded() { if (d->accessToken.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Pinterest ok"; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Pinterest ok"; writeSettings(); emit signalLinkingSucceeded(); } bool PTalker::authenticated() { return (!d->accessToken.isEmpty()); } void PTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } void PTalker::createBoard(QString& boardName) { QUrl url("https://api.pinterest.com/v1/boards/"); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->accessToken).toUtf8()); QByteArray postData = QString::fromUtf8("{\"name\": \"%1\"}").arg(boardName).toUtf8(); //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "createBoard:" << postData; d->reply = d->netMngr->post(netRequest, postData); d->state = Private::P_CREATEBOARD; d->buffer.resize(0); emit signalBusy(true); } void PTalker::getUserName() { QUrl url(QLatin1String("https://api.pinterest.com/v1/me/?fields=username")); QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->accessToken).toUtf8()); d->reply = d->netMngr->get(netRequest); d->state = Private::P_USERNAME; d->buffer.resize(0); emit signalBusy(true); } /** Get list of boards by parsing json sent by pinterest */ void PTalker::listBoards(const QString& /*path*/) { QUrl url(QLatin1String("https://api.pinterest.com/v1/me/boards/"));; QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("Bearer %1").arg(d->accessToken).toUtf8()); //netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); d->reply = d->netMngr->get(netRequest); d->state = Private::P_LISTBOARDS; d->buffer.resize(0); emit signalBusy(true); } bool PTalker::addPin(const QString& imgPath, const QString& uploadBoard, bool rescale, int maxDim, int imageQuality) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); PMPForm form; QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { emit signalBusy(false); return false; } QString path = WSToolUtils::makeTemporaryDir("pinterest").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.setImageDimensions(image.size()); d->meta.setImageOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); d->meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); d->meta.save(path); } QString boardParam = d->userName + QLatin1Char('/') + uploadBoard; if (!form.addFile(path)) { emit signalBusy(false); return false; } QUrl url(QString::fromLatin1("https://api.pinterest.com/v1/pins/?access_token=%1").arg(d->accessToken)); QHttpMultiPart* const multiPart = new QHttpMultiPart (QHttpMultiPart::FormDataType); ///Board Section QHttpPart board; QString boardHeader = QString("form-data; name=\"board\"") ; board.setHeader(QNetworkRequest::ContentDispositionHeader, boardHeader); QByteArray postData = boardParam.toUtf8(); board.setBody(postData); multiPart->append(board); ///Note section QHttpPart note; QString noteHeader = QString("form-data; name=\"note\"") ; note.setHeader(QNetworkRequest::ContentDispositionHeader, noteHeader); postData = QByteArray(); note.setBody(postData); multiPart->append(note); ///image section QFile* const file = new QFile(imgPath); file->open(QIODevice::ReadOnly); QHttpPart imagePart; QString imagePartHeader = QLatin1String("form-data; name=\"image\"; filename=\"") + QFileInfo(imgPath).fileName() + QLatin1Char('"'); imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, imagePartHeader); imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("image/jpeg")); imagePart.setBodyDevice(file); multiPart->append(imagePart); QString content = QLatin1String("multipart/form-data;boundary=") + multiPart->boundary(); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, content); d->reply = d->netMngr->post(netRequest, multiPart); // delete the multiPart and file with the reply multiPart->setParent(d->reply); d->state = Private::P_ADDPIN; d->buffer.resize(0); return true; } void PTalker::slotFinished(QNetworkReply* reply) { if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state != Private::P_CREATEBOARD) { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error content: " << QString(reply->readAll()); reply->deleteLater(); return; } } d->buffer.append(reply->readAll()); //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "BUFFER" << QString(d->buffer); switch (d->state) { case Private::P_LISTBOARDS: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In P_LISTBOARDS"; parseResponseListBoards(d->buffer); break; case Private::P_CREATEBOARD: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In P_CREATEBOARD"; parseResponseCreateBoard(d->buffer); break; case Private::P_ADDPIN: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In P_ADDPIN"; parseResponseAddPin(d->buffer); break; case Private::P_USERNAME: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In P_USERNAME"; parseResponseUserName(d->buffer); break; case Private::P_ACCESSTOKEN: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In P_ACCESSTOKEN"; parseResponseAccessToken(d->buffer); break; default: break; } reply->deleteLater(); } void PTalker::parseResponseAccessToken(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); d->accessToken = jsonObject[QLatin1String("access_token")].toString(); if (!d->accessToken.isEmpty()) { qDebug(DIGIKAM_WEBSERVICES_LOG) << "Access token Received: " << d->accessToken; emit pinterestLinkingSucceeded(); } else { emit pinterestLinkingFailed(); } emit signalBusy(false); } void PTalker::parseResponseAddPin(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object()[QLatin1String("data")].toObject(); bool success = jsonObject.contains(QLatin1String("id")); emit signalBusy(false); if (!success) { emit signalAddPinFailed(i18n("Failed to upload Pin")); } else { emit signalAddPinSucceeded(); } } void PTalker::parseResponseUserName(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object()[QLatin1String("data")].toObject(); QString name = jsonObject[QLatin1String("username")].toString(); emit signalBusy(false); d->userName = name; emit signalSetUserName(name); } void PTalker::parseResponseListBoards(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListBoardsFailed(i18n("Failed to list boards")); return; } QJsonObject jsonObject = doc.object(); //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Json Listing Boards : " << doc; QJsonArray jsonArray = jsonObject[QLatin1String("data")].toArray(); QList > list; QString boardID; QString boardName; foreach (const QJsonValue& value, jsonArray) { QString boardID; QString boardName; QJsonObject obj = value.toObject(); boardID = obj[QLatin1String("id")].toString(); boardName = obj[QLatin1String("name")].toString(); list.append(qMakePair(boardID, boardName)); } emit signalBusy(false); emit signalListBoardsDone(list); } void PTalker::parseResponseCreateBoard(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool fail = jsonObject.contains(QLatin1String("error")); emit signalBusy(false); if (fail) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); emit signalCreateBoardFailed(jsonObject[QLatin1String("error_summary")].toString()); } else { emit signalCreateBoardSucceeded(); } } void PTalker::writeSettings() { KConfig config; KConfigGroup grp = config.group("Pinterest User Settings"); grp.writeEntry("access_token", d->accessToken); config.sync(); } void PTalker::readSettings() { KConfig config; KConfigGroup grp = config.group("Pinterest User Settings"); //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In read settings"; if (!grp.readEntry("access_token", false)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Linking..."; link(); } else { d->accessToken = grp.readEntry("access_token",QString()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Already Linked"; emit pinterestLinkingSucceeded(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/pinterest/ptalker.h b/core/utilities/assistants/webservices/pinterest/ptalker.h index 98aebdb394..398a907929 100644 --- a/core/utilities/assistants/webservices/pinterest/ptalker.h +++ b/core/utilities/assistants/webservices/pinterest/ptalker.h @@ -1,106 +1,104 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-05-20 * Description : a tool to export images to Pinterest web service * * Copyright (C) 2018 by Tarek Talaat * * 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_P_TALKER_H #define DIGIKAM_P_TALKER_H // Qt includes #include #include #include -#include #include -#include // Local includes #include "pitem.h" #include "dmetadata.h" namespace Digikam { class PTalker : public QObject { Q_OBJECT public: explicit PTalker(QWidget* const parent); ~PTalker(); public: void link(); void unLink(); void getUserName(); bool authenticated(); void cancel(); bool addPin(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality); void listBoards(const QString& path = QString()); void createBoard(QString& boardName); void setAccessToken(const QString& token); QMap ParseUrlParameters(const QString& url); void getToken(const QString& code); void readSettings(); void writeSettings(); Q_SIGNALS: void signalBusy(bool val); void signalLinkingSucceeded(); void signalLinkingFailed(); void signalSetUserName(const QString& msg); void signalListBoardsFailed(const QString& msg); void signalListBoardsDone(const QList >& list); void signalCreateBoardFailed(const QString& msg); void signalCreateBoardSucceeded(); void signalAddPinFailed(const QString& msg); void signalAddPinSucceeded(); void pinterestLinkingSucceeded(); void pinterestLinkingFailed(); private Q_SLOTS: void slotLinkingFailed(); void slotLinkingSucceeded(); void slotCatchUrl(const QUrl& url); void slotFinished(QNetworkReply* reply); private: void parseResponseUserName(const QByteArray& data); void parseResponseListBoards(const QByteArray& data); void parseResponseCreateBoard(const QByteArray& data); void parseResponseAddPin(const QByteArray& data); void parseResponseAccessToken(const QByteArray& data); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_P_TALKER_H diff --git a/core/utilities/assistants/webservices/twitter/twittertalker.cpp b/core/utilities/assistants/webservices/twitter/twittertalker.cpp index 3c8a99906a..17053eb9d1 100644 --- a/core/utilities/assistants/webservices/twitter/twittertalker.cpp +++ b/core/utilities/assistants/webservices/twitter/twittertalker.cpp @@ -1,700 +1,703 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-06-29 * Description : a tool to export images to Twitter social network * * Copyright (C) 2018 by Tarek Talaat * * 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 "twittertalker.h" // Qt includes #include #include #include #include #include #include +#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 "wstoolutils.h" #include "twitterwindow.h" #include "twitteritem.h" #include "twittermpform.h" #include "previewloadthread.h" #include "o0settingsstore.h" #include "o1requestor.h" namespace Digikam { class Q_DECL_HIDDEN TwTalker::Private { public: enum State { OD_USERNAME = 0, OD_LISTFOLDERS, OD_CREATEFOLDER, OD_ADDPHOTO }; public: explicit Private() { clientId = QLatin1String("Kej10Xqld2SzYHpl1zPNXBkdz"); clientSecret = QLatin1String("u7012XOx5Xd4t2oH10UMsffY8NseowtsfrXscoOzi4I0c039MF"); //scope = QLatin1String("User.Read Files.ReadWrite"); authUrl = QLatin1String("https://api.twitter.com/oauth/request_token"); requestTokenUrl = QLatin1String("https://api.twitter.com/oauth/authenticate"); accessTokenUrl = QLatin1String("https://api.twitter.com/oauth/access_token"); redirectUrl = QLatin1String("http://127.0.0.1:8000"); state = OD_USERNAME; parent = 0; netMngr = 0; reply = 0; settings = 0; o1Twitter = 0; } public: QString clientId; QString clientSecret; QString authUrl; QString requestTokenUrl; QString accessTokenUrl; QString scope; QString redirectUrl; QString accessToken; QWidget* parent; QNetworkAccessManager* netMngr; QNetworkReply* reply; State state; QByteArray buffer; DMetadata meta; QMap urlParametersMap; //QWebEngineView* view; QSettings* settings; O1Twitter* o1Twitter; }; TwTalker::TwTalker(QWidget* const parent) : d(new Private) { d->parent = parent; d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); d->o1Twitter = new O1Twitter(this); d->o1Twitter->setClientId(d->clientId); d->o1Twitter->setClientSecret(d->clientSecret); d->o1Twitter->setLocalPort(8000); d->settings = WSToolUtils::getOauthSettings(this); O0SettingsStore* const store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this); store->setGroupKey(QLatin1String("twitter")); d->o1Twitter->setStore(store); connect(d->o1Twitter, SIGNAL(linkingFailed()), this, SLOT(slotLinkingFailed())); connect(d->o1Twitter, SIGNAL(linkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->o1Twitter, SIGNAL(openBrowser(QUrl)), this, SLOT(slotOpenBrowser(QUrl))); } TwTalker::~TwTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir("twitter"); delete d; } void TwTalker::link() { /* emit signalBusy(true); QUrl url(d->requestTokenUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", QString::fromLatin1("OAuth oauth_callback= \"%1\"").arg(d->redirectUrl).toUtf8()); QNetworkAccessManager requestMngr; QNetworkReply* reply; reply = requestMngr.post(netRequest); if (reply->error() != QNetworkReply::NoError){ } QByteArray buffer; buffer.append(reply->readAll()); QString response = fromLatin1(buffer); QMap headers; // Discard the first line response = response.mid(response.indexOf('\n') + 1).trimmed(); foreach (QString line, response.split('\n')) { int colon = line.indexOf(':'); QString headerName = line.left(colon).trimmed(); QString headerValue = line.mid(colon + 1).trimmed(); headers.insertMulti(headerName, headerValue); } QString oauthToken = headers[oauth_token]; QSting oauthTokenSecret = headers[oauth_token_secret]; QUrlQuery query(url); query.addQueryItem(QLatin1String("client_id"), d->clientId); query.addQueryItem(QLatin1String("scope"), d->scope); query.addQueryItem(QLatin1String("redirect_uri"), d->redirectUrl); query.addQueryItem(QLatin1String("response_type"), "token"); url.setQuery(query); d->view = new QWebEngineView(d->parent); d->view->setWindowFlags(Qt::Dialog); d->view->load(url); d->view->show(); connect(d->view, SIGNAL(urlChanged(QUrl)), this, SLOT(slotCatchUrl(QUrl))); */ emit signalBusy(true); d->o1Twitter->link(); } void TwTalker::unLink() { /* d->accessToken = QString(); d->view->page()->profile()->cookieStore()->deleteAllCookies(); emit oneDriveLinkingSucceeded(); */ d->o1Twitter->unlink(); d->settings->beginGroup(QLatin1String("Dropbox")); d->settings->remove(QString()); d->settings->endGroup(); } void TwTalker::slotOpenBrowser(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser..."; QDesktopServices::openUrl(url); } QMap TwTalker::ParseUrlParameters(const QString &url) { QMap urlParameters; if (url.indexOf('?') == -1) { return urlParameters; } QString tmp = url.right(url.length()-url.indexOf('?')-1); tmp = tmp.right(tmp.length() - tmp.indexOf('#')-1); QStringList paramlist = tmp.split('&'); for (int i = 0 ; i < paramlist.count() ; ++i) { QStringList paramarg = paramlist.at(i).split('='); urlParameters.insert(paramarg.at(0),paramarg.at(1)); } return urlParameters; } void TwTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter fail"; emit signalBusy(false); } void TwTalker::slotLinkingSucceeded() { /* if (d->accessToken.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Twitter ok"; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter ok"; d->view->close(); emit signalLinkingSucceeded(); */ if (!d->o1Twitter->linked()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Twitter ok"; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter ok"; QVariantMap extraTokens = d->o1Twitter->extraTokens(); if (!extraTokens.isEmpty()) { //emit extraTokensReady(extraTokens); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Extra tokens in response:"; foreach (const QString& key, extraTokens.keys()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "\t" << key << ":" << (extraTokens.value(key).toString().left(3) + "..."); } } emit signalLinkingSucceeded(); } bool TwTalker::authenticated() { return d->o1Twitter->linked(); } void TwTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } /* void TwTalker::upload(QByteArray& data){ QByteArray encoded = data.toBase64(QByteArray::Base64UrlEncoding); QUrl url = QUrl("https://upload.twitter.com/1.1/media/upload.json"); } */ bool TwTalker::addPhoto(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality) { //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Status update message:" << message.toLatin1().constData(); emit signalBusy(true); TwMPForm form; QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); qint64 imageSize = QFileInfo(imgPath).size(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SIZE of image using qfileinfo: " << imageSize; qCDebug(DIGIKAM_WEBSERVICES_LOG) << " " ; if (image.isNull()) { emit signalBusy(false); return false; } QString path = WSToolUtils::makeTemporaryDir("twitter").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.setImageDimensions(image.size()); d->meta.setImageOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); d->meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); d->meta.save(path); } if (!form.addFile(path)) { emit signalBusy(false); return false; } QString uploadPath = uploadFolder + QUrl(QUrl::fromLocalFile(imgPath)).fileName(); if (form.formData().isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA Empty:"; } if (form.formData().isNull()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA null:"; } QUrl url = QUrl("https://upload.twitter.com/1.1/media/upload.json"); O1Requestor* const requestor = new O1Requestor(d->netMngr, d->o1Twitter, this); QList reqParams = QList(); //These are the parameters passed for the first step in chuncked media upload. //reqParams << O0RequestParameter(QByteArray("command"), QByteArray("INIT")); //reqParams << O0RequestParameter(QByteArray("media_type"), QByteArray("image/jpeg")); //reqParams << O0RequestParameter(QByteArray("total_bytes"), QString::fromLatin1("%1").arg(imageSize).toUtf8()); reqParams << O0RequestParameter(QByteArray("media"), form.formData()); QByteArray postData = O1::createQueryParameters(reqParams); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data"); QNetworkReply* const reply2 = requestor->post(request, reqParams, postData); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "reply size: " << reply2->readBufferSize(); //always returns 0 QByteArray replyData = reply2->readAll(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Media reply:" << replyData; //always Empty //uncomment this to try to post a tweet QUrl url2 = QUrl("https://api.twitter.com/1.1/statuses/update.json"); reqParams = QList(); reqParams << O0RequestParameter(QByteArray("status"), "Hello"); //reqParams << O0RequestParameter(QByteArray("media_ids"), mediaId.toUtf8()); postData = O1::createQueryParameters(reqParams); request.setUrl(url2); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply* const reply = requestor->post(request, reqParams, postData); connect(reply, SIGNAL(finished()), this, SLOT(slotTweetDone())); connect(reply2, SIGNAL(finished()), this, SLOT(reply2finish())); return true; } void TwTalker::reply2finish() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Reply2:"; QNetworkReply* const reply2 = qobject_cast(sender()); QString mediaId; if (reply2->error() != QNetworkReply::NoError) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ERROR:" << reply2->errorString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Content:" << reply2->readAll(); emit signalAddPhotoFailed(i18n("Failed to upload photo")); } else { QByteArray replyData = reply2->readAll(); QJsonDocument doc = QJsonDocument::fromJson(replyData); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Media reply:" << replyData; QJsonObject jsonObject = doc.object(); mediaId = jsonObject[QLatin1String("media_id")].toString(); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Media ID:" << mediaId; } void TwTalker::getUserName() { QUrl url(QLatin1String("https://graph.microsoft.com/v1.0/me")); QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("bearer %1").arg(d->accessToken).toUtf8()); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); d->reply = d->netMngr->get(netRequest); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "AFter Request Usar " << d->reply; d->state = Private::OD_USERNAME; d->buffer.resize(0); emit signalBusy(true); } void TwTalker::slotTweetDone() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In slotTweetDone:"; QNetworkReply* const reply = qobject_cast(sender()); if (reply->error() != QNetworkReply::NoError) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ERROR:" << reply->errorString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Content:" << reply->readAll(); emit signalAddPhotoFailed(i18n("Failed to upload photo")); } else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tweet posted successfully!"; emit signalAddPhotoSucceeded(); } } /* bool TwTalker::addPhoto(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "PATH " << imgPath; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Folder " << uploadFolder; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Others: " << rescale << " " << maxDim << " " <reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); ODMPForm form; QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { return false; } QString path = WSToolUtils::makeTemporaryDir("twitter").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.setImageDimensions(image.size()); d->meta.setImageOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); d->meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); d->meta.save(path); } if (!form.addFile(path)) { emit signalBusy(false); return false; } QString uploadPath = uploadFolder + QUrl(QUrl::fromLocalFile(imgPath)).fileName(); QUrl url(QString::fromLatin1("https://graph.microsoft.com/v1.0/me/drive/root:/%1:/content").arg(uploadPath)); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/octet-stream")); netRequest.setRawHeader("Authorization", QString::fromLatin1("bearer {%1}").arg(d->accessToken).toUtf8()); d->reply = d->netMngr->put(netRequest, form.formData()); d->state = Private::OD_ADDPHOTO; d->buffer.resize(0); emit signalBusy(true); return true; } */ void TwTalker::slotFinished(QNetworkReply* reply) { if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state != Private::OD_CREATEFOLDER) { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); reply->deleteLater(); return; } } d->buffer.append(reply->readAll()); switch (d->state) { case Private::OD_LISTFOLDERS: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_LISTFOLDERS"; parseResponseListFolders(d->buffer); break; case Private::OD_CREATEFOLDER: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_CREATEFOLDER"; parseResponseCreateFolder(d->buffer); break; case Private::OD_ADDPHOTO: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_ADDPHOTO"; parseResponseAddPhoto(d->buffer); break; case Private::OD_USERNAME: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_USERNAME"; parseResponseUserName(d->buffer); break; default: break; } reply->deleteLater(); } void TwTalker::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 TwTalker::parseResponseUserName(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseUserName: "< > list; list.append(qMakePair(QLatin1String(""), QLatin1String("root"))); foreach (const QJsonValue& value, jsonArray) { QString path; QString folderName; QJsonObject folder; QJsonObject obj = value.toObject(); folder = obj[QLatin1String("folder")].toObject(); if (!folder.isEmpty()) { folderName = obj[QLatin1String("name")].toString(); path = QLatin1Char('/') + folderName; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Folder Name is" << folderName; list.append(qMakePair(path, folderName)); } } emit signalBusy(false); emit signalListAlbumsDone(list); } void TwTalker::parseResponseCreateFolder(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool fail = jsonObject.contains(QLatin1String("error")); emit signalBusy(false); if (fail) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateFolder ERROR: " << doc; emit signalCreateFolderFailed(jsonObject[QLatin1String("error_summary")].toString()); } else { emit signalCreateFolderSucceeded(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/twitter/twittertalker.h b/core/utilities/assistants/webservices/twitter/twittertalker.h index d48c3f7a08..6b11b59443 100644 --- a/core/utilities/assistants/webservices/twitter/twittertalker.h +++ b/core/utilities/assistants/webservices/twitter/twittertalker.h @@ -1,108 +1,106 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-06-29 * Description : a tool to export images to Twitter social network * * Copyright (C) 2018 by Tarek Talaat * * 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_TWITTER_TALKER_H #define DIGIKAM_TWITTER_TALKER_H // Qt includes #include #include #include -#include #include -#include // Local includes #include "twitteritem.h" #include "o2.h" #include "o0globals.h" #include "dmetadata.h" #include "o1twitter.h" namespace Digikam { class TwTalker : public QObject { Q_OBJECT public: explicit TwTalker(QWidget* const parent); ~TwTalker(); public: void link(); void unLink(); void getUserName(); bool authenticated(); void cancel(); bool addPhoto(const QString& imgPath, const QString& uploadFolder, bool rescale, int maxDim, int imageQuality); void listFolders(const QString& path = QString()); void createFolder(QString& path); void setAccessToken(const QString& token); QMap ParseUrlParameters(const QString& url); 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(); void twitterLinkingSucceeded(); void twitterLinkingFailed(); private Q_SLOTS: void slotLinkingFailed(); void slotLinkingSucceeded(); void slotOpenBrowser(const QUrl& url); void slotFinished(QNetworkReply* reply); void slotTweetDone(); //void slotGetMediaId(QNetworkReply* reply); void reply2finish(); 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_TWITTER_TALKER_H