diff --git a/core/utilities/assistants/webservices/box/boxtalker.cpp b/core/utilities/assistants/webservices/box/boxtalker.cpp index df3c8d76af..18df567546 100644 --- a/core/utilities/assistants/webservices/box/boxtalker.cpp +++ b/core/utilities/assistants/webservices/box/boxtalker.cpp @@ -1,502 +1,503 @@ /* ============================================================ * * 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 // 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; } - QMimeDatabase mimeDB; + 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); - emit signalBusy(true); 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/dropbox/dbtalker.cpp b/core/utilities/assistants/webservices/dropbox/dbtalker.cpp index 13b1c797d3..42d3ab64d8 100644 --- a/core/utilities/assistants/webservices/dropbox/dbtalker.cpp +++ b/core/utilities/assistants/webservices/dropbox/dbtalker.cpp @@ -1,471 +1,472 @@ /* ============================================================ * * 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 // Local includes #include "digikam_debug.h" #include "digikam_version.h" #include "wstoolutils.h" #include "dbwindow.h" #include "dbitem.h" #include "dbmpform.h" #include "previewloadthread.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()); - + 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); - emit signalBusy(true); + 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/facebook/fbtalker.cpp b/core/utilities/assistants/webservices/facebook/fbtalker.cpp index a8ae9276ea..35ecc7afc0 100644 --- a/core/utilities/assistants/webservices/facebook/fbtalker.cpp +++ b/core/utilities/assistants/webservices/facebook/fbtalker.cpp @@ -1,789 +1,789 @@ /* ============================================================ * * 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 // 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 signalBusy(false); emit signalAddPhotoDone(666, i18n("Cannot open file")); + emit signalBusy(false); + return; } form.finish(); QVariant arg_1; - if(albumID.isEmpty()) + + if (albumID.isEmpty()) { arg_1 = d->user.id; } else { arg_1 = albumID; } - - QNetworkRequest netRequest(QUrl(d->apiURL.arg(arg_1.toString()) - .arg("photos"))); + 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/flickr/flickrtalker.cpp b/core/utilities/assistants/webservices/flickr/flickrtalker.cpp index 159e3bedde..bb5beaa918 100644 --- a/core/utilities/assistants/webservices/flickr/flickrtalker.cpp +++ b/core/utilities/assistants/webservices/flickr/flickrtalker.cpp @@ -1,1171 +1,1174 @@ /* ============================================================ * * 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 // 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" 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); - emit signalBusy(true); 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/google/gdrive/gdtalker.cpp b/core/utilities/assistants/webservices/google/gdrive/gdtalker.cpp index a49e8c9430..2f18a1f6cb 100644 --- a/core/utilities/assistants/webservices/google/gdrive/gdtalker.cpp +++ b/core/utilities/assistants/webservices/google/gdrive/gdtalker.cpp @@ -1,502 +1,503 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export items to Google web services * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2013-2018 by Caulier Gilles * * 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 #include #include #include #include #include #include // Local includes #include "wstoolutils.h" #include "digikam_version.h" #include "gswindow.h" #include "gdmpform.h" #include "digikam_debug.h" #include "previewloadthread.h" #include "dmetadata.h" namespace Digikam { static bool gdriveLessThan(const GSFolder& p1, const GSFolder& p2) { return (p1.title.toLower() < p2.title.toLower()); } class Q_DECL_HIDDEN GDTalker::Private { public: enum State { GD_LOGOUT = -1, GD_LISTFOLDERS = 0, GD_CREATEFOLDER, GD_ADDPHOTO, GD_USERNAME, }; public: explicit Private() { apiUrl = QLatin1String("https://www.googleapis.com/drive/v2/%1"); uploadUrl = QLatin1String("https://www.googleapis.com/upload/drive/v2/files"); state = GD_LOGOUT; netMngr = 0; rootid = QLatin1String("root"); rootfoldername = QLatin1String("GoogleDrive Root"); listPhotoId = QStringList(); } public: QString apiUrl; QString uploadUrl; QString rootid; QString rootfoldername; QString username; State state; QStringList listPhotoId; QNetworkAccessManager* netMngr; }; GDTalker::GDTalker(QWidget* const parent) : GSTalkerBase(parent, QStringList(QLatin1String("https://www.googleapis.com/auth/drive")), QLatin1String("GoogleDrive")), d(new Private) { d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); connect(this, SIGNAL(signalReadyToUpload()), this, SLOT(slotUploadPhoto())); } GDTalker::~GDTalker() { if (m_reply) { m_reply->abort(); } WSToolUtils::removeTemporaryDir("google"); delete d; } /** * Gets username */ void GDTalker::getUserName() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getUserName"; QUrl url(d->apiUrl.arg("about")); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->get(netRequest); d->state = Private::GD_USERNAME; m_buffer.resize(0); emit signalBusy(true); } /** * Gets list of folder of user in json format */ void GDTalker::listFolders() { QUrl url(d->apiUrl.arg("files")); QUrlQuery q; q.addQueryItem(QLatin1String("q"), QLatin1String("mimeType = 'application/vnd.google-apps.folder'")); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->get(netRequest); d->state = Private::GD_LISTFOLDERS; m_buffer.resize(0); emit signalBusy(true); } /** * Creates folder inside any folder(of which id is passed) */ void GDTalker::createFolder(const QString& title, const QString& id) { if (m_reply) { m_reply->abort(); m_reply = 0; } QUrl url(d->apiUrl.arg("files")); QByteArray data; data += "{\"title\":\""; data += title.toLatin1(); data += "\",\r\n"; data += "\"parents\":"; data += "[{"; data += "\"id\":\""; data += id.toLatin1(); data += "\"}],\r\n"; data += "\"mimeType\":"; data += "\"application/vnd.google-apps.folder\""; data += "}\r\n"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "data:" << data; QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->post(netRequest, data); d->state = Private::GD_CREATEFOLDER; m_buffer.resize(0); emit signalBusy(true); } bool GDTalker::addPhoto(const QString& imgPath, const GSPhoto& info, const QString& id, bool rescale, int maxDim, int imageQuality) { if (m_reply) { m_reply->abort(); m_reply = 0; } + emit signalBusy(true); + GDMPForm form; form.addPair(QUrl::fromLocalFile(imgPath).fileName(), info.description, imgPath, id); QString path = imgPath; QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(path).name().startsWith(QLatin1String("image/"))) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { image.load(imgPath); } if (image.isNull()) { + emit signalBusy(false); return false; } path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(imgPath) .baseName().trimmed() + QLatin1String(".jpg")); int imgQualityToApply = 100; if (rescale) { if (image.width() > maxDim || image.height() > maxDim) image = image.scaled(maxDim,maxDim,Qt::KeepAspectRatio,Qt::SmoothTransformation); imgQualityToApply = imageQuality; } image.save(path, "JPEG", imgQualityToApply); DMetadata meta; if (meta.load(imgPath)) { meta.setImageDimensions(image.size()); meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL); meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); meta.save(path); } } if (!form.addFile(path)) { emit signalBusy(false); return false; } form.finish(); QUrl url(d->uploadUrl); QUrlQuery q; q.addQueryItem(QLatin1String("uploadType"), QLatin1String("multipart")); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); netRequest.setRawHeader("Host", "www.googleapis.com"); m_reply = d->netMngr->post(netRequest, form.formData()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In add photo"; d->state = Private::GD_ADDPHOTO; m_buffer.resize(0); - emit signalBusy(true); - return true; } void GDTalker::slotFinished(QNetworkReply* reply) { if (reply != m_reply) { return; } m_reply = 0; if (reply->error() != QNetworkReply::NoError) { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); reply->deleteLater(); return; } m_buffer.append(reply->readAll()); switch (d->state) { case (Private::GD_LOGOUT): break; case (Private::GD_LISTFOLDERS): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_LISTFOLDERS"; parseResponseListFolders(m_buffer); break; case (Private::GD_CREATEFOLDER): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_CREATEFOLDER"; parseResponseCreateFolder(m_buffer); break; case (Private::GD_ADDPHOTO): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_ADDPHOTO"; // << m_buffer; parseResponseAddPhoto(m_buffer); break; case (Private::GD_USERNAME): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_USERNAME"; // << m_buffer; parseResponseUserName(m_buffer); break; default: break; } reply->deleteLater(); } void GDTalker::slotUploadPhoto() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << d->listPhotoId.join(", "); emit signalUploadPhotoDone(1, QString(), d->listPhotoId); } void GDTalker::parseResponseUserName(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); qCDebug(DIGIKAM_WEBSERVICES_LOG)<<"User Name is: " << jsonObject[QLatin1String("name")].toString(); QString temp = jsonObject[QLatin1String("name")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in parseResponseUserName"; emit signalBusy(false); emit signalSetUserName(temp); } void GDTalker::parseResponseListFolders(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << doc; if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListAlbumsDone(0,i18n("Failed to list folders"),QList()); return; } QJsonObject jsonObject = doc.object(); QJsonArray jsonArray = jsonObject[QLatin1String("items")].toArray(); QList albumList; GSFolder fps; fps.id = d->rootid; fps.title = d->rootfoldername; albumList.append(fps); foreach (const QJsonValue& value, jsonArray) { QJsonObject obj = value.toObject(); // Verify if album is in trash QJsonObject labels = obj[QLatin1String("labels")].toObject(); bool trashed = labels[QLatin1String("trashed")].toBool(); // Verify if album is editable bool editable = obj[QLatin1String("editable")].toBool(); /* Verify if album is visualized in a folder inside My Drive * If parents is empty, album is shared by another person and not added to My Drive yet */ QJsonArray parents = obj[QLatin1String("parents")].toArray(); fps.id = obj[QLatin1String("id")].toString(); fps.title = obj[QLatin1String("title")].toString(); if(editable && !trashed && !parents.isEmpty()) { albumList.append(fps); } } std::sort(albumList.begin(), albumList.end(), gdriveLessThan); emit signalBusy(false); emit signalListAlbumsDone(1, QString(), albumList); } void GDTalker::parseResponseCreateFolder(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); QString temp = jsonObject[QLatin1String("alternateLink")].toString(); bool success = false; if (!(QString::compare(temp, QLatin1String(""), Qt::CaseInsensitive) == 0)) success = true; emit signalBusy(false); if (!success) { emit signalCreateFolderDone(0,i18n("Failed to create folder")); } else { emit signalCreateFolderDone(1,QString()); } } void GDTalker::parseResponseAddPhoto(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); QString altLink = jsonObject[QLatin1String("alternateLink")].toString(); QString photoId = jsonObject[QLatin1String("id")].toString(); bool success = false; if (!(QString::compare(altLink, QLatin1String(""), Qt::CaseInsensitive) == 0)) success = true; emit signalBusy(false); if (!success) { emit signalAddPhotoDone(0, i18n("Failed to upload photo")); } else { d->listPhotoId << photoId; emit signalAddPhotoDone(1, QString()); } } void GDTalker::cancel() { if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(false); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp b/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp index ad2811e2c3..ff4cbe5d34 100644 --- a/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp +++ b/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp @@ -1,929 +1,935 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-16-07 * Description : a tool to export items to Google web services * * Copyright (C) 2007-2008 by Vardhman Jain * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2009 by Luka Renko * 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 "gptalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "wstoolutils.h" #include "digikam_version.h" #include "gswindow.h" #include "gpmpform.h" #include "digikam_debug.h" #include "previewloadthread.h" #include "dmetadata.h" #define NB_MAX_ITEM_UPLOAD 50 namespace Digikam { static bool gphotoLessThan(const GSFolder& p1, const GSFolder& p2) { return (p1.title.toLower() < p2.title.toLower()); } class Q_DECL_HIDDEN GPTalker::Private { public: enum State { GP_LOGOUT = -1, GP_LISTALBUMS = 0, GP_GETUSER, GP_LISTPHOTOS, GP_ADDPHOTO, GP_UPDATEPHOTO, GP_UPLOADPHOTO, GP_GETPHOTO, GP_CREATEALBUM }; public: explicit Private() { state = GP_LOGOUT; netMngr = 0; userInfoUrl = QLatin1String("https://www.googleapis.com/plus/v1/people/me"); apiVersion = QLatin1String("v1"); apiUrl = QString::fromLatin1("https://photoslibrary.googleapis.com/%1/%2").arg(apiVersion); albumIdToUpload = QLatin1String("-1"); previousImageId = QLatin1String("-1"); } public: QString userInfoUrl; QString apiUrl; QString apiVersion; State state; QString albumIdToUpload; QString previousImageId; QStringList uploadTokenList; QNetworkAccessManager* netMngr; }; GPTalker::GPTalker(QWidget* const parent) : GSTalkerBase(parent, (QStringList() << QLatin1String("https://www.googleapis.com/auth/plus.login") // to get user login (temporary until gphoto supports it officially) << QLatin1String("https://www.googleapis.com/auth/photoslibrary") // to add and download photo in the library << QLatin1String("https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata") // to download photo created by digiKam on GPhoto << QLatin1String(" https://www.googleapis.com/auth/photoslibrary.sharing")), // for shared albums QLatin1String("GooglePhotos")), d(new Private) { m_reply = 0; d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); connect(this, SIGNAL(signalError(QString)), this, SLOT(slotError(QString))); connect(this, SIGNAL(signalReadyToUpload()), this, SLOT(slotUploadPhoto())); } GPTalker::~GPTalker() { if (m_reply) { m_reply->abort(); } WSToolUtils::removeTemporaryDir("google"); delete d; } QStringList GPTalker::getUploadTokenList() { return d->uploadTokenList; } /** * (Trung): Comments below are not valid anymore with google photos api * Google Photo's Album listing request/response * First a request is sent to the url below and then we might(?) get a redirect URL * We then need to send the GET request to the Redirect url. * This uses the authenticated album list fetching to get all the albums included the unlisted-albums * which is not returned for an unauthorised request as done without the Authorization header. */ void GPTalker::listAlbums() { if (m_reply) { m_reply->abort(); m_reply = 0; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list albums"; QUrl url(d->apiUrl.arg("albums")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url for list albums " << url; QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->get(netRequest); d->state = Private::GP_LISTALBUMS; m_buffer.resize(0); emit signalBusy(true); } /** * We get user profile from Google Plus API * This is a temporary solution until Google Photo support API for user profile */ void GPTalker::getLoggedInUser() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getLoggedInUser"; if (m_reply) { m_reply->abort(); m_reply = 0; } QUrl url(d->userInfoUrl); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url for list albums " << url; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "m_accessToken " << m_accessToken; QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->get(netRequest); d->state = Private::GP_GETUSER; m_buffer.resize(0); emit signalBusy(true); } void GPTalker::listPhotos(const QString& albumId, const QString& /*imgmax*/) { if (m_reply) { m_reply->abort(); m_reply = 0; } QUrl url(d->apiUrl.arg("mediaItems:search")); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toUtf8()); QByteArray data; data += "{\"pageSize\": \"100\","; data += "\"albumId\":\""; data += albumId.toUtf8(); data += "\"}"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "data to list photos : " << QString(data); m_reply = d->netMngr->post(netRequest, data); d->state = Private::GP_LISTPHOTOS; m_buffer.resize(0); emit signalBusy(true); } void GPTalker::createAlbum(const GSFolder& album) { if (m_reply) { m_reply->abort(); m_reply = 0; } // Create body in json QByteArray data; data += "{\"album\":"; data += "{\"title\":\""; data += album.title.toLatin1(); data += "\"}}"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString(data); QUrl url(d->apiUrl.arg("albums")); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->post(netRequest, data); d->state = Private::GP_CREATEALBUM; m_buffer.resize(0); emit signalBusy(true); } /** * First a request is sent to the url below and then we will get an upload token * Upload token then will be sent with url in GPTlaker::uploadPhoto to create real photos on user accont */ bool GPTalker::addPhoto(const QString& photoPath, GSPhoto& /*info*/, const QString& albumId, bool rescale, int maxDim, int imageQuality) { if (m_reply) { m_reply->abort(); m_reply = 0; } QUrl url(d->apiUrl.arg("uploads")); // Save album ID to upload d->albumIdToUpload = albumId; // GPMPForm form; QString path = photoPath; QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(path).name().startsWith(QLatin1String("image/"))) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage(); if (image.isNull()) { image.load(photoPath); } if (image.isNull()) { return false; } path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(photoPath) .baseName().trimmed() + QLatin1String(".jpg")); int imgQualityToApply = 100; if (rescale) { if (image.width() > maxDim || image.height() > maxDim) image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); imgQualityToApply = imageQuality; } image.save(path, "JPEG", imgQualityToApply); DMetadata meta; if (meta.load(photoPath)) { meta.setImageDimensions(image.size()); meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL); meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); meta.save(path); } } // Create the body for temporary upload QFile imageFile(path); if (!imageFile.open(QIODevice::ReadOnly)) { return false; } QByteArray data = imageFile.readAll(); imageFile.close(); QString imageName = QUrl::fromLocalFile(path).fileName().toLatin1(); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/octet-stream")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); netRequest.setRawHeader("X-Goog-Upload-File-Name", imageName.toLatin1()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << imageName; m_reply = d->netMngr->post(netRequest, data); d->state = Private::GP_ADDPHOTO; m_buffer.resize(0); emit signalBusy(true); return true; } bool GPTalker::updatePhoto(const QString& photoPath, GSPhoto& info/*, const QString& albumId*/, bool rescale, int maxDim, int imageQuality) { if (m_reply) { m_reply->abort(); m_reply = 0; } + emit signalBusy(true); + GPMPForm form; QString path = photoPath; QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(path).name().startsWith(QLatin1String("image/"))) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage(); if (image.isNull()) { image.load(photoPath); } if (image.isNull()) { + emit signalBusy(false); return false; } path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(photoPath) .baseName().trimmed() + QLatin1String(".jpg")); int imgQualityToApply = 100; if (rescale) { if (image.width() > maxDim || image.height() > maxDim) image = image.scaled(maxDim,maxDim, Qt::KeepAspectRatio,Qt::SmoothTransformation); imgQualityToApply = imageQuality; } image.save(path, "JPEG", imgQualityToApply); DMetadata meta; if (meta.load(photoPath)) { meta.setImageDimensions(image.size()); meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL); meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion()); meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY); meta.save(path); } } //Create the Body in atom-xml QDomDocument docMeta; QDomProcessingInstruction instr = docMeta.createProcessingInstruction( QLatin1String("xml"), QLatin1String("version='1.0' encoding='UTF-8'")); docMeta.appendChild(instr); QDomElement entryElem = docMeta.createElement(QLatin1String("entry")); docMeta.appendChild(entryElem); entryElem.setAttribute( QLatin1String("xmlns"), QLatin1String("http://www.w3.org/2005/Atom")); QDomElement titleElem = docMeta.createElement(QLatin1String("title")); entryElem.appendChild(titleElem); QDomText titleText = docMeta.createTextNode(QFileInfo(path).fileName()); titleElem.appendChild(titleText); QDomElement summaryElem = docMeta.createElement(QLatin1String("summary")); entryElem.appendChild(summaryElem); QDomText summaryText = docMeta.createTextNode(info.description); summaryElem.appendChild(summaryText); QDomElement categoryElem = docMeta.createElement(QLatin1String("category")); entryElem.appendChild(categoryElem); categoryElem.setAttribute( QLatin1String("scheme"), QLatin1String("http://schemas.google.com/g/2005#kind")); categoryElem.setAttribute( QLatin1String("term"), QLatin1String("http://schemas.google.com/photos/2007#photo")); QDomElement mediaGroupElem = docMeta.createElementNS( QLatin1String("http://search.yahoo.com/mrss/"), QLatin1String("media:group")); entryElem.appendChild(mediaGroupElem); QDomElement mediaKeywordsElem = docMeta.createElementNS( QLatin1String("http://search.yahoo.com/mrss/"), QLatin1String("media:keywords")); mediaGroupElem.appendChild(mediaKeywordsElem); QDomText mediaKeywordsText = docMeta.createTextNode(info.tags.join(QLatin1Char(','))); mediaKeywordsElem.appendChild(mediaKeywordsText); if (!info.gpsLat.isEmpty() && !info.gpsLon.isEmpty()) { QDomElement whereElem = docMeta.createElementNS( QLatin1String("http://www.georss.org/georss"), QLatin1String("georss:where")); entryElem.appendChild(whereElem); QDomElement pointElem = docMeta.createElementNS( QLatin1String("http://www.opengis.net/gml"), QLatin1String("gml:Point")); whereElem.appendChild(pointElem); QDomElement gpsElem = docMeta.createElementNS( QLatin1String("http://www.opengis.net/gml"), QLatin1String("gml:pos")); pointElem.appendChild(gpsElem); QDomText gpsVal = docMeta.createTextNode(info.gpsLat + QLatin1Char(' ') + info.gpsLon); gpsElem.appendChild(gpsVal); } form.addPair(QLatin1String("descr"), docMeta.toString(), QLatin1String("application/atom+xml")); if (!form.addFile(QLatin1String("photo"), path)) + { + emit signalBusy(false); return false; + } form.finish(); QNetworkRequest netRequest(info.editUrl); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1() + "\nIf-Match: *"); m_reply = d->netMngr->put(netRequest, form.formData()); d->state = Private::GP_UPDATEPHOTO; m_buffer.resize(0); - emit signalBusy(true); + return true; } void GPTalker::getPhoto(const QString& imgPath) { if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(true); QUrl url(imgPath); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "link to get photo " << url.url(); m_reply = d->netMngr->get(QNetworkRequest(url)); d->state = Private::GP_GETPHOTO; m_buffer.resize(0); } void GPTalker::cancel() { if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(false); } void GPTalker::slotError(const QString & error) { QString transError; int errorNo = 0; if (!error.isEmpty()) 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("File-size was zero"); break; case 5: transError=i18n("File-type 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"); }; QMessageBox::critical(QApplication::activeWindow(), i18nc("@title:window", "Error"), i18n("Error occurred: %1\nUnable to proceed further.",transError + error)); } void GPTalker::slotFinished(QNetworkReply* reply) { emit signalBusy(false); if (reply != m_reply) { return; } m_reply = 0; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "reply error : " << reply->error() << " - " << reply->errorString(); if (reply->error() != QNetworkReply::NoError) { if (d->state == Private::GP_ADDPHOTO) { emit signalAddPhotoDone(reply->error(), reply->errorString()); } else { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); } reply->deleteLater(); return; } m_buffer.append(reply->readAll()); switch (d->state) { case (Private::GP_LOGOUT): break; case (Private::GP_GETUSER): parseResponseGetLoggedInUser(m_buffer); break; case (Private::GP_CREATEALBUM): parseResponseCreateAlbum(m_buffer); break; case (Private::GP_LISTALBUMS): parseResponseListAlbums(m_buffer); break; case (Private::GP_LISTPHOTOS): parseResponseListPhotos(m_buffer); break; case (Private::GP_ADDPHOTO): parseResponseAddPhoto(m_buffer); break; case (Private::GP_UPDATEPHOTO): emit signalAddPhotoDone(1, QLatin1String("")); break; case (Private::GP_UPLOADPHOTO): parseResponseUploadPhoto(m_buffer); break; case (Private::GP_GETPHOTO): qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString(m_buffer); // all we get is data of the image emit signalGetPhotoDone(1, QString(), m_buffer); break; } reply->deleteLater(); } void GPTalker::slotUploadPhoto() { /* Keep track of number of items will be uploaded, because * Google Photo API upload maximum NB_MAX_ITEM_UPLOAD items in at a time */ int nbItemsUpload = 0; if (m_reply) { m_reply->abort(); m_reply = 0; } QUrl url(d->apiUrl.arg("mediaItems:batchCreate")); QByteArray data; data += '{'; if (d->albumIdToUpload != QLatin1String("-1")) { data += "\"albumId\": \""; data += d->albumIdToUpload.toLatin1(); data += "\","; } data += "\"newMediaItems\": ["; if (d->uploadTokenList.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "token list is empty"; } while (!d->uploadTokenList.isEmpty() && nbItemsUpload < NB_MAX_ITEM_UPLOAD) { const QString& uploadToken = d->uploadTokenList.takeFirst(); data += "{\"description\": \"\","; data += "\"simpleMediaItem\": {"; data += "\"uploadToken\": \""; data += uploadToken; data += "\"}}"; if (d->uploadTokenList.length() > 0) { data += ','; } nbItemsUpload ++; } if (d->previousImageId == QLatin1String("-1")) { data += ']'; } else { data += "],\"albumPosition\": {"; data += "\"position\": \"AFTER_MEDIA_ITEM\","; data += "\"relativeMediaItemId\": \""; data += d->previousImageId.toLatin1(); data += "\"}\r\n"; } data += "}\r\n"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString(data); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->post(netRequest, data); d->state = Private::GP_UPLOADPHOTO; m_buffer.resize(0); emit signalBusy(true); } void GPTalker::parseResponseListAlbums(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListAlbums"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListAlbumsDone(0, QString::fromLatin1("Code: %1 - %2").arg(err.error) .arg(err.errorString()), QList()); return; } QJsonObject jsonObject = doc.object(); QJsonArray jsonArray = jsonObject[QLatin1String("albums")].toArray(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json array " << doc; QList albumList; /** * Google-photos allows user to post photos on their main page (not in any albums) * so this folder is created for that purpose */ GSFolder mainPage; albumList.append(mainPage); foreach (const QJsonValue& value, jsonArray) { QJsonObject obj = value.toObject(); GSFolder album; album.id = obj[QLatin1String("id")].toString(); album.title = obj[QLatin1String("title")].toString(); album.url = obj[QLatin1String("productUrl")].toString(); albumList.append(album); } std::sort(albumList.begin(), albumList.end(), gphotoLessThan); emit signalListAlbumsDone(1, QLatin1String(""), albumList); } void GPTalker::parseResponseListPhotos(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotos"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListPhotosDone(0, i18n("Failed to fetch photo-set list"), QList()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error code: " << err.error << ", msg: " << err.errorString(); return; } QJsonObject jsonObject = doc.object(); QJsonArray jsonArray = jsonObject[QLatin1String("mediaItems")].toArray(); QList photoList; foreach (const QJsonValue& value, jsonArray) { QJsonObject obj = value.toObject(); GSPhoto photo; photo.baseUrl = obj[QLatin1String("baseUrl")].toString(); photo.description = obj[QLatin1String("description")].toString(); photo.id = obj[QLatin1String("id")].toString(); photo.mimeType = obj[QLatin1String("mimeType")].toString(); photo.location = obj[QLatin1String("Location")].toString(); // Not yet available in v1 but will be in the future QJsonObject metadata = obj[QLatin1String("mediaMetadata")].toObject(); photo.creationTime = metadata[QLatin1String("creationTime")].toString(); photo.width = metadata[QLatin1String("width")].toString(); photo.height = metadata[QLatin1String("height")].toString(); photo.originalURL = QUrl(photo.baseUrl + QString::fromLatin1("=w%1-h%2").arg(photo.width) .arg(photo.height)); qCDebug(DIGIKAM_WEBSERVICES_LOG) << photo.originalURL.url(); photoList.append(photo); } emit signalListPhotosDone(1, QLatin1String(""), photoList); } void GPTalker::parseResponseCreateAlbum(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateAlbums"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalCreateAlbumDone(0, QString::fromLatin1("Code: %1 - %2").arg(err.error) .arg(err.errorString()), QString()); return; } QJsonObject jsonObject = doc.object(); QString albumId = jsonObject[QLatin1String("id")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "album Id " << doc; emit signalCreateAlbumDone(1, QLatin1String(""), albumId); } void GPTalker::parseResponseAddPhoto(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "response " << QString(data); d->uploadTokenList << QString(data); emit signalAddPhotoDone(1, QLatin1String("")); } void GPTalker::parseResponseGetLoggedInUser(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseGetLoggedInUser"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); QString userName = jsonObject[QLatin1String("displayName")].toString(); emit signalSetUserName(userName); listAlbums(); } //TODO: Parse and return photoID void GPTalker::parseResponseUploadPhoto(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseUploadPhoto"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "doc " << doc; if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalUploadPhotoDone(0, err.errorString(), QStringList()); return; } QJsonObject jsonObject = doc.object(); QJsonArray jsonArray = jsonObject[QLatin1String("newMediaItemResults")].toArray(); QStringList listPhotoId; foreach (const QJsonValue& value, jsonArray) { QJsonObject obj = value.toObject(); QJsonObject mediaItem = obj[QLatin1String("mediaItem")].toObject(); listPhotoId << mediaItem[QLatin1String("id")].toString(); } d->previousImageId = listPhotoId.last(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list photo Id " << listPhotoId.join(", "); emit signalBusy(false); emit signalUploadPhotoDone(1, QLatin1String(""), listPhotoId); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/onedrive/odtalker.cpp b/core/utilities/assistants/webservices/onedrive/odtalker.cpp index 13a3089ceb..a1e5bbd4a7 100644 --- a/core/utilities/assistants/webservices/onedrive/odtalker.cpp +++ b/core/utilities/assistants/webservices/onedrive/odtalker.cpp @@ -1,551 +1,552 @@ /* ============================================================ * * 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 // 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); - emit signalBusy(true); + 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/pinterest/ptalker.cpp b/core/utilities/assistants/webservices/pinterest/ptalker.cpp index 0dbd879be1..f20e1e5a47 100644 --- a/core/utilities/assistants/webservices/pinterest/ptalker.cpp +++ b/core/utilities/assistants/webservices/pinterest/ptalker.cpp @@ -1,626 +1,625 @@ /* ============================================================ * * 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 // 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); - emit signalBusy(true); 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