File indexing completed on 2025-01-05 03:53:42
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2008-12-01 0007 * Description : a tool to export images to Smugmug web service 0008 * 0009 * SPDX-FileCopyrightText: 2008-2009 by Luka Renko <lure at kubuntu dot org> 0010 * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * SPDX-FileCopyrightText: 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "smugtalker.h" 0018 0019 // Qt includes 0020 0021 #include <QByteArray> 0022 #include <QDomDocument> 0023 #include <QDomElement> 0024 #include <QJsonDocument> 0025 #include <QJsonParseError> 0026 #include <QJsonObject> 0027 #include <QJsonValue> 0028 #include <QJsonArray> 0029 #include <QTextDocument> 0030 #include <QFile> 0031 #include <QFileInfo> 0032 #include <QMessageBox> 0033 #include <QApplication> 0034 #include <QDesktopServices> 0035 #include <QCryptographicHash> 0036 #include <QUrlQuery> 0037 #include <QNetworkReply> 0038 0039 // KDE includes 0040 0041 #include <klocalizedstring.h> 0042 0043 // Local includes 0044 0045 #include "digikam_debug.h" 0046 #include "digikam_version.h" 0047 #include "smugmpform.h" 0048 0049 // OAuth2 library includes 0050 0051 #if defined(Q_CC_CLANG) 0052 # pragma clang diagnostic push 0053 # pragma clang diagnostic ignored "-Wextra-semi" 0054 #endif 0055 0056 #include "wstoolutils.h" 0057 #include "networkmanager.h" 0058 #include "o0settingsstore.h" 0059 0060 #if defined(Q_CC_CLANG) 0061 # pragma clang diagnostic pop 0062 #endif 0063 0064 using namespace Digikam; 0065 0066 namespace DigikamGenericSmugPlugin 0067 { 0068 0069 class Q_DECL_HIDDEN SmugTalker::Private 0070 { 0071 0072 public: 0073 0074 enum State 0075 { 0076 SMUG_LOGIN = 0, 0077 SMUG_LOGOUT, 0078 SMUG_LISTALBUMS, 0079 SMUG_LISTPHOTOS, 0080 SMUG_LISTALBUMTEMPLATES, 0081 SMUG_CREATEALBUM, 0082 SMUG_ADDPHOTO, 0083 SMUG_GETPHOTO 0084 /* 0085 SMUG_LISTCATEGORIES, 0086 SMUG_LISTSUBCATEGORIES, 0087 */ 0088 }; 0089 0090 public: 0091 0092 explicit Private() 0093 : parent(nullptr), 0094 userAgent(QString::fromLatin1("digiKam/%1 (digikamdeveloper@gmail.com)").arg(digiKamVersion())), 0095 apiURL(QLatin1String("https://api.smugmug.com%1")), 0096 uploadUrl(QLatin1String("https://upload.smugmug.com/")), 0097 requestTokenUrl(QLatin1String("https://api.smugmug.com/services/oauth/1.0a/getRequestToken")), 0098 authUrl(QLatin1String("https://api.smugmug.com/services/oauth/1.0a/authorize")), 0099 accessTokenUrl(QLatin1String("https://api.smugmug.com/services/oauth/1.0a/getAccessToken")), 0100 apiVersion(QLatin1String("v2")), 0101 apikey(QLatin1String("xKp43CXF8MHgjhgGdgdgfgc7cWjqQcck")), 0102 clientSecret(QLatin1String("3CKcLcWx64Rm8HVRwX3bf4HCtJpnGrwnk9xSn4DK8wRhGLVsRBBFktD95W4HTRHD")), 0103 iface(nullptr), 0104 netMngr(nullptr), 0105 reply(nullptr), 0106 state(SMUG_LOGOUT), 0107 settings(nullptr), 0108 requestor(nullptr), 0109 o1(nullptr) 0110 { 0111 } 0112 0113 public: 0114 0115 QWidget* parent; 0116 0117 QString userAgent; 0118 0119 QString apiURL; 0120 QString uploadUrl; 0121 QString requestTokenUrl; 0122 QString authUrl; 0123 QString accessTokenUrl; 0124 0125 QString apiVersion; 0126 QString apikey; 0127 QString clientSecret; 0128 QString sessionID; 0129 0130 SmugUser user; 0131 DInfoInterface* iface; 0132 0133 QNetworkAccessManager* netMngr; 0134 0135 QNetworkReply* reply; 0136 0137 State state; 0138 0139 QSettings* settings; 0140 O1Requestor* requestor; 0141 O1SmugMug* o1; 0142 }; 0143 0144 SmugTalker::SmugTalker(DInfoInterface* const iface, QWidget* const parent) 0145 : d(new Private) 0146 { 0147 d->parent = parent; 0148 d->iface = iface; 0149 d->netMngr = NetworkManager::instance()->getNetworkManager(this); 0150 0151 connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), 0152 this, SLOT(slotFinished(QNetworkReply*))); 0153 0154 // Init 0155 0156 d->o1 = new O1SmugMug(this, d->netMngr); 0157 0158 // Config for authentication flow 0159 0160 d->o1->setRequestTokenUrl(QUrl(d->requestTokenUrl)); 0161 d->o1->setAuthorizeUrl(QUrl(d->authUrl)); 0162 d->o1->setAccessTokenUrl(QUrl(d->accessTokenUrl)); 0163 d->o1->setLocalPort(8000); 0164 0165 // Application credentials 0166 0167 d->o1->setClientId(d->apikey); 0168 d->o1->setClientSecret(d->clientSecret); 0169 0170 // Set userAgent to work around error : 0171 // O1::onTokenRequestError: 201 "Error transferring requestTokenUrl() - server replied: Forbidden" "Bad bot" 0172 0173 d->o1->setUserAgent(d->userAgent.toUtf8()); 0174 0175 // Setting to store oauth config 0176 0177 d->settings = WSToolUtils::getOauthSettings(this); 0178 O0SettingsStore* const store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this); 0179 store->setGroupKey(QLatin1String("Smugmug")); 0180 d->o1->setStore(store); 0181 0182 // Connect signaux slots 0183 0184 connect(d->o1, SIGNAL(linkingFailed()), 0185 this, SLOT(slotLinkingFailed())); 0186 0187 connect(this, SIGNAL(signalLinkingSucceeded()), 0188 this, SLOT(slotLinkingSucceeded())); 0189 0190 connect(d->o1, SIGNAL(linkingSucceeded()), 0191 this, SLOT(slotLinkingSucceeded())); 0192 0193 connect(d->o1, SIGNAL(openBrowser(QUrl)), 0194 this, SLOT(slotOpenBrowser(QUrl))); 0195 0196 d->requestor = new O1Requestor(d->netMngr, d->o1, this); 0197 } 0198 0199 SmugTalker::~SmugTalker() 0200 { 0201 /* We shouldn't logout without user's consent 0202 0203 if (loggedIn()) 0204 { 0205 logout(); 0206 0207 while (d->reply && d->reply->isRunning()) 0208 { 0209 qApp->processEvents(); 0210 } 0211 } 0212 */ 0213 0214 if (d->reply) 0215 d->reply->abort(); 0216 0217 delete d; 0218 } 0219 0220 /** 0221 * TODO: Porting to O2 0222 */ 0223 void SmugTalker::link() 0224 { 0225 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Smug "; 0226 d->o1->link(); 0227 } 0228 0229 void SmugTalker::unlink() 0230 { 0231 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Smug "; 0232 d->o1->unlink(); 0233 } 0234 0235 void SmugTalker::removeUserAccount(const QString& /*userName*/) 0236 { 0237 /* 0238 if (userName.startsWith(d->serviceName)) 0239 { 0240 d->settings->beginGroup(userName); 0241 d->settings->remove(QString()); 0242 d->settings->endGroup(); 0243 } 0244 */ 0245 } 0246 0247 bool SmugTalker::loggedIn() const 0248 { 0249 return d->o1->linked(); 0250 } 0251 0252 void SmugTalker::slotLinkingFailed() 0253 { 0254 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Smug fail"; 0255 Q_EMIT signalBusy(false); 0256 getLoginedUser(); 0257 } 0258 0259 void SmugTalker::slotLinkingSucceeded() 0260 { 0261 if (!d->o1->linked()) 0262 { 0263 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Smug ok"; 0264 0265 // Remove user account 0266 0267 removeUserAccount(d->user.nickName); 0268 0269 // Clear user field 0270 0271 d->user.clear(); 0272 0273 // Set state to SMUG_LOGOUT 0274 0275 d->state = Private::SMUG_LOGOUT; 0276 0277 Q_EMIT signalBusy(false); 0278 return; 0279 } 0280 0281 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Smug ok"; 0282 0283 getLoginedUser(); 0284 } 0285 0286 void SmugTalker::slotOpenBrowser(const QUrl& url) 0287 { 0288 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser..."; 0289 QDesktopServices::openUrl(url); 0290 } 0291 0292 void SmugTalker::slotCloseBrowser() 0293 { 0294 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Close Browser..."; 0295 } 0296 0297 SmugUser SmugTalker::getUser() const 0298 { 0299 return d->user; 0300 } 0301 0302 /** 0303 * (Trung) 0304 * There are some characters not valid for album title (e.g "_") 0305 * and for url (e.g "-") that are not mentioned on the API page, 0306 * so if found, that has to be treated here 0307 */ 0308 QString SmugTalker::createAlbumName(const QString& word) 0309 { 0310 QString w(word); 0311 0312 // First we remove space at beginning and end 0313 0314 w = w.trimmed(); 0315 0316 // We replace all character "_" with space 0317 0318 w = w.replace(QLatin1Char('_'), QLatin1Char(' ')); 0319 0320 // Then we replace first letter with its uppercase 0321 0322 w.replace(0, 1, w[0].toUpper()); 0323 0324 qCDebug(DIGIKAM_WEBSERVICES_LOG) << w; 0325 0326 return w; 0327 } 0328 0329 QString SmugTalker::createAlbumUrl(const QString& name) 0330 { 0331 QString n(name); 0332 0333 // First we create a valid name 0334 0335 n = createAlbumName(n); 0336 0337 // Then we replace space with "-" 0338 0339 QStringList words = n.split(QLatin1Char(' ')); 0340 n = words.join(QLatin1Char('-')); 0341 0342 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url name : " << n; 0343 0344 return n; 0345 } 0346 0347 void SmugTalker::cancel() 0348 { 0349 if (d->reply) 0350 { 0351 d->reply->abort(); 0352 d->reply = nullptr; 0353 } 0354 0355 Q_EMIT signalBusy(false); 0356 } 0357 0358 void SmugTalker::login() 0359 { 0360 if (d->reply) 0361 { 0362 d->reply->abort(); 0363 d->reply = nullptr; 0364 } 0365 0366 Q_EMIT signalBusy(true); 0367 Q_EMIT signalLoginProgress(1, 4, i18n("Logging in to SmugMug service...")); 0368 0369 // Build authentication url 0370 0371 O1SmugMug::AuthorizationUrlBuilder builder; 0372 builder.setAccess(O1SmugMug::AccessFull); 0373 builder.setPermissions(O1SmugMug::PermissionsModify); 0374 d->o1->initAuthorizationUrl(builder); 0375 0376 if (!d->o1->linked()) 0377 { 0378 link(); 0379 } 0380 else 0381 { 0382 Q_EMIT signalLinkingSucceeded(); 0383 } 0384 } 0385 0386 void SmugTalker::getLoginedUser() 0387 { 0388 QUrl url(d->apiURL.arg(QLatin1String("/api/v2!authuser"))); 0389 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url = " << url.url(); 0390 0391 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>(); 0392 0393 QNetworkRequest netRequest(url); 0394 netRequest.setRawHeader("Accept", "application/json"); 0395 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); 0396 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0397 0398 d->reply = d->requestor->get(netRequest, reqParams); 0399 0400 d->state = Private::SMUG_LOGIN; 0401 } 0402 0403 void SmugTalker::logout() 0404 { 0405 if (d->reply) 0406 { 0407 d->reply->abort(); 0408 d->reply = nullptr; 0409 } 0410 0411 Q_EMIT signalBusy(true); 0412 0413 unlink(); 0414 } 0415 0416 void SmugTalker::listAlbums(const QString& /*nickName*/) 0417 { 0418 if (d->reply) 0419 { 0420 d->reply->abort(); 0421 d->reply = nullptr; 0422 } 0423 0424 Q_EMIT signalBusy(true); 0425 0426 QUrl url(d->apiURL.arg(QString::fromLatin1("%1!albums").arg(d->user.userUri))); 0427 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url = " << url.url(); 0428 0429 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>(); 0430 0431 QNetworkRequest netRequest(url); 0432 netRequest.setRawHeader("Accept", "application/json"); 0433 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); 0434 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0435 0436 d->reply = d->requestor->get(netRequest, reqParams); 0437 0438 d->state = Private::SMUG_LISTALBUMS; 0439 } 0440 0441 void SmugTalker::listPhotos(const qint64 /*albumID*/, 0442 const QString& albumKey, 0443 const QString& /*albumPassword*/, 0444 const QString& /*sitePassword*/) 0445 { 0446 if (d->reply) 0447 { 0448 d->reply->abort(); 0449 d->reply = nullptr; 0450 } 0451 0452 Q_EMIT signalBusy(true); 0453 0454 QUrl url(d->apiURL.arg(QString::fromLatin1("/api/v2/album/%1!images").arg(albumKey))); 0455 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list photo " << url.url(); 0456 0457 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>(); 0458 0459 QNetworkRequest netRequest(url); 0460 netRequest.setRawHeader("Accept", "application/json"); 0461 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); 0462 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0463 0464 d->reply = d->requestor->get(netRequest, reqParams); 0465 0466 d->state = Private::SMUG_LISTPHOTOS; 0467 } 0468 0469 void SmugTalker::listAlbumTmpl() 0470 { 0471 if (d->reply) 0472 { 0473 d->reply->abort(); 0474 d->reply = nullptr; 0475 } 0476 0477 Q_EMIT signalBusy(true); 0478 0479 QUrl url(d->apiURL.arg(QString::fromLatin1("%1!albumtemplates").arg(d->user.userUri))); 0480 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url to listAlbumTmpl " << url.url(); 0481 0482 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>(); 0483 0484 QNetworkRequest netRequest(url); 0485 netRequest.setRawHeader("Accept", "application/json"); 0486 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); 0487 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0488 0489 d->reply = d->requestor->get(netRequest, reqParams); 0490 0491 d->state = Private::SMUG_LISTALBUMTEMPLATES; 0492 } 0493 0494 /* Categories deprecated in API v2 0495 0496 void SmugTalker::listCategories() 0497 { 0498 if (d->reply) 0499 { 0500 d->reply->abort(); 0501 d->reply = 0; 0502 } 0503 0504 Q_EMIT signalBusy(true); 0505 0506 QUrl url(d->apiURL); 0507 QUrlQuery q; 0508 q.addQueryItem(QLatin1String("method"), QLatin1String("smugmug.categories.get")); 0509 q.addQueryItem(QLatin1String("SessionID"), d->sessionID); 0510 url.setQuery(q); 0511 0512 QNetworkRequest netRequest(url); 0513 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); 0514 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0515 0516 d->reply = d->netMngr->get(netRequest); 0517 0518 d->state = Private::SMUG_LISTCATEGORIES; 0519 } 0520 0521 void SmugTalker::listSubCategories(qint64 categoryID) 0522 { 0523 if (d->reply) 0524 { 0525 d->reply->abort(); 0526 d->reply = 0; 0527 } 0528 0529 Q_EMIT signalBusy(true); 0530 0531 QUrl url(d->apiURL); 0532 QUrlQuery q; 0533 q.addQueryItem(QLatin1String("method"), QLatin1String("smugmug.subcategories.get")); 0534 q.addQueryItem(QLatin1String("SessionID"), d->sessionID); 0535 q.addQueryItem(QLatin1String("CategoryID"), QString::number(categoryID)); 0536 url.setQuery(q); 0537 0538 QNetworkRequest netRequest(url); 0539 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); 0540 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0541 0542 d->reply = d->netMngr->get(netRequest); 0543 0544 d->state = Private::SMUG_LISTSUBCATEGORIES; 0545 } 0546 */ 0547 0548 void SmugTalker::createAlbum(const SmugAlbum& album) 0549 { 0550 if (d->reply) 0551 { 0552 d->reply->abort(); 0553 d->reply = nullptr; 0554 } 0555 0556 Q_EMIT signalBusy(true); 0557 0558 QUrl url(d->apiURL.arg(QString::fromLatin1("%1!albums").arg(d->user.folderUri))); 0559 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url to post " << url.url(); 0560 0561 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>(); 0562 0563 /** 0564 * Something must to be remembered here is that we HAVE TO start a name with upper case !!! 0565 * And url name with '-' instead of space 0566 */ 0567 0568 QByteArray data; 0569 data += "{\"Name\": \""; 0570 data += createAlbumName(album.title).toUtf8(); 0571 data += "\",\"UrlName\":\""; 0572 data += createAlbumUrl(album.title).toUtf8(); 0573 data += "\",\"Privacy\":\"Public\"}"; 0574 qCDebug(DIGIKAM_WEBSERVICES_LOG) << data; 0575 0576 QNetworkRequest netRequest(url); 0577 netRequest.setRawHeader("Accept", "application/json"); 0578 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); 0579 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0580 0581 d->reply = d->requestor->post(netRequest, reqParams, data); 0582 0583 d->state = Private::SMUG_CREATEALBUM; 0584 } 0585 0586 bool SmugTalker::addPhoto(const QString& imgPath, 0587 qint64 /*albumID*/, 0588 const QString& albumKey, 0589 const QString& caption) 0590 { 0591 if (d->reply) 0592 { 0593 d->reply->abort(); 0594 d->reply = nullptr; 0595 } 0596 0597 Q_EMIT signalBusy(true); 0598 0599 QString imgName = QFileInfo(imgPath).fileName(); 0600 0601 // load temporary image to buffer 0602 0603 QFile imgFile(imgPath); 0604 0605 if (!imgFile.open(QIODevice::ReadOnly)) 0606 { 0607 Q_EMIT signalBusy(false); 0608 return false; 0609 } 0610 0611 QByteArray imgData = imgFile.readAll(); 0612 imgFile.close(); 0613 0614 SmugMPForm form; 0615 0616 if (!caption.isEmpty()) 0617 { 0618 form.addPair(QLatin1String("Caption"), caption); 0619 } 0620 0621 if (!form.addFile(imgName, imgPath)) 0622 { 0623 return false; 0624 } 0625 0626 form.finish(); 0627 0628 QString customHdr; 0629 QUrl url(d->uploadUrl); 0630 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url to upload " << url.url(); 0631 0632 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>(); 0633 0634 QNetworkRequest netRequest(url); 0635 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); 0636 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0637 netRequest.setRawHeader("X-Smug-Caption", caption.toUtf8()); 0638 netRequest.setRawHeader("X-Smug-FileName", imgName.toUtf8()); 0639 netRequest.setRawHeader("X-Smug-AlbumUri", QString::fromLatin1("/api/v2/album/%1").arg(albumKey).toUtf8()); 0640 netRequest.setRawHeader("X-Smug-ResponseType", "JSON"); 0641 netRequest.setRawHeader("X-Smug-Version", d->apiVersion.toLatin1()); 0642 0643 d->reply = d->requestor->post(netRequest, reqParams, form.formData()); 0644 0645 d->state = Private::SMUG_ADDPHOTO; 0646 0647 return true; 0648 } 0649 0650 void SmugTalker::getPhoto(const QString& imgPath) 0651 { 0652 if (d->reply) 0653 { 0654 d->reply->abort(); 0655 d->reply = nullptr; 0656 } 0657 0658 Q_EMIT signalBusy(true); 0659 0660 QUrl url(imgPath); 0661 0662 QUrlQuery q; 0663 q.addQueryItem(QLatin1String("APIKey"), d->apikey); 0664 0665 url.setQuery(q); 0666 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "download link for image " << url.url(); 0667 0668 QNetworkRequest netRequest(url); 0669 netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); 0670 0671 d->reply = d->netMngr->get(netRequest); 0672 0673 d->state = Private::SMUG_GETPHOTO; 0674 } 0675 0676 QString SmugTalker::errorToText(int errCode, const QString& errMsg) const 0677 { 0678 QString transError; 0679 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "errorToText: " << errCode << ": " << errMsg; 0680 0681 switch (errCode) 0682 { 0683 case 0: 0684 transError = QString(); 0685 break; 0686 0687 case 1: 0688 transError = i18n("Login failed"); 0689 break; 0690 0691 case 4: 0692 transError = i18n("Invalid user/nick/password"); 0693 break; 0694 0695 case 18: 0696 transError = i18n("Invalid API key"); 0697 break; 0698 0699 default: 0700 transError = errMsg; 0701 break; 0702 } 0703 0704 return transError; 0705 } 0706 0707 void SmugTalker::slotFinished(QNetworkReply* reply) 0708 { 0709 if (reply != d->reply) 0710 { 0711 return; 0712 } 0713 0714 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error code : " << reply->error() << "error text " << reply->errorString(); 0715 0716 d->reply = nullptr; 0717 0718 if (reply->error() != QNetworkReply::NoError) 0719 { 0720 if (d->state == Private::SMUG_LOGIN) 0721 { 0722 d->sessionID.clear(); 0723 d->user.clear(); 0724 0725 Q_EMIT signalBusy(false); 0726 Q_EMIT signalLoginDone(reply->error(), reply->errorString()); 0727 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error code : " << reply->error() << "error text " << reply->errorString(); 0728 } 0729 else if (d->state == Private::SMUG_ADDPHOTO) 0730 { 0731 Q_EMIT signalBusy(false); 0732 Q_EMIT signalAddPhotoDone(reply->error(), reply->errorString()); 0733 } 0734 else if (d->state == Private::SMUG_GETPHOTO) 0735 { 0736 Q_EMIT signalBusy(false); 0737 Q_EMIT signalGetPhotoDone(reply->error(), reply->errorString(), QByteArray()); 0738 } 0739 else 0740 { 0741 Q_EMIT signalBusy(false); 0742 QMessageBox::critical(QApplication::activeWindow(), 0743 i18nc("@title:window", "Error"), reply->errorString()); 0744 } 0745 0746 reply->deleteLater(); 0747 return; 0748 } 0749 0750 QByteArray buffer = reply->readAll(); 0751 0752 switch (d->state) 0753 { 0754 case (Private::SMUG_LOGIN): 0755 parseResponseLogin(buffer); 0756 break; 0757 0758 case (Private::SMUG_LISTALBUMS): 0759 parseResponseListAlbums(buffer); 0760 break; 0761 0762 case (Private::SMUG_LISTPHOTOS): 0763 parseResponseListPhotos(buffer); 0764 break; 0765 0766 case (Private::SMUG_LISTALBUMTEMPLATES): 0767 parseResponseListAlbumTmpl(buffer); 0768 break; 0769 /* 0770 case (Private::SMUG_LISTCATEGORIES): 0771 parseResponseListCategories(buffer); 0772 break; 0773 0774 case (Private::SMUG_LISTSUBCATEGORIES): 0775 parseResponseListSubCategories(buffer); 0776 break; 0777 */ 0778 case (Private::SMUG_CREATEALBUM): 0779 parseResponseCreateAlbum(buffer); 0780 break; 0781 0782 case (Private::SMUG_ADDPHOTO): 0783 parseResponseAddPhoto(buffer); 0784 break; 0785 0786 case (Private::SMUG_GETPHOTO): 0787 0788 // all we get is data of the image 0789 0790 Q_EMIT signalBusy(false); 0791 Q_EMIT signalGetPhotoDone(0, QString(), buffer); 0792 break; 0793 0794 default: // Private::SMUG_LOGIN 0795 break; 0796 } 0797 0798 reply->deleteLater(); 0799 } 0800 0801 void SmugTalker::parseResponseLogin(const QByteArray& data) 0802 { 0803 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseLogin"; 0804 QJsonParseError err; 0805 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0806 0807 Q_EMIT signalLoginProgress(3); 0808 0809 if (err.error != QJsonParseError::NoError) 0810 { 0811 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "failed to parse to json"; 0812 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "errCode " << err.error; 0813 Q_EMIT signalLoginDone(err.error, errorToText(err.error, err.errorString())); 0814 Q_EMIT signalBusy(false); 0815 return; 0816 } 0817 0818 QJsonObject jsonObject = doc.object(); 0819 QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); 0820 QJsonObject userObject = response[QLatin1String("User")].toObject(); 0821 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json object " << userObject; 0822 0823 d->user.displayName = userObject[QLatin1String("Name")].toString(); 0824 d->user.nickName = userObject[QLatin1String("NickName")].toString(); 0825 d->user.userUri = userObject[QLatin1String("Uri")].toString(); 0826 0827 QJsonObject Uris = userObject[QLatin1String("Uris")].toObject(); 0828 QJsonObject node = Uris[QLatin1String("Node")].toObject(); 0829 QJsonObject folder = Uris[QLatin1String("Folder")].toObject(); 0830 0831 d->user.nodeUri = node[QLatin1String("Uri")].toString(); 0832 d->user.folderUri = folder[QLatin1String("Uri")].toString(); 0833 0834 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json data parse : " << d->user.displayName << "+ " << d->user.nodeUri; 0835 0836 Q_EMIT signalLoginProgress(4); 0837 Q_EMIT signalBusy(false); 0838 Q_EMIT signalLoginDone(0, QString()); 0839 } 0840 0841 /* Not necessary anymore 0842 0843 void SmugTalker::parseResponseLogout(const QByteArray& data) 0844 { 0845 int errCode = -1; 0846 QString errMsg; 0847 0848 QDomDocument doc(QLatin1String("logout")); 0849 0850 if (!doc.setContent(data)) 0851 return; 0852 0853 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse Logout response:" << QT_ENDL << data; 0854 0855 QDomElement e = doc.documentElement(); 0856 0857 for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) 0858 { 0859 if (!node.isElement()) 0860 { 0861 continue; 0862 } 0863 0864 e = node.toElement(); 0865 0866 if (e.tagName() == QLatin1String("Logout")) 0867 { 0868 errCode = 0; 0869 } 0870 else if (e.tagName() == QLatin1String("err")) 0871 { 0872 errCode = e.attribute(QLatin1String("code")).toInt(); 0873 errMsg = e.attribute(QLatin1String("msg")); 0874 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error:" << errCode << errMsg; 0875 } 0876 } 0877 0878 // consider we are logged out in any case 0879 d->sessionID.clear(); 0880 d->user.clear(); 0881 0882 Q_EMIT signalBusy(false); 0883 } 0884 */ 0885 0886 /** 0887 * A multi-part put response (which we get now) looks like: 0888 * <?xml version="1.0" encoding="utf-8"?> 0889 * <rsp stat="ok"> 0890 * <method>smugmug.images.upload</method> 0891 * <ImageID>884775096</ImageID> 0892 * <ImageKey>L7aq5</ImageKey> 0893 * <ImageURL>http://froody.smugmug.com/Other/Test/12372176_y7yNq#884775096_L7aq5</ImageURL> // krazy:exclude=insecurenet 0894 * </rsp> 0895 * 0896 * A simple put response (which we used to get) looks like: 0897 * <?xml version="1.0" encoding="utf-8"?> 0898 * <rsp stat="ok"> 0899 * <method>smugmug.images.upload</method> 0900 * <Image id="884790545" Key="seeQa" URL="http://froody.smugmug.com/Other/Test/12372176_y7yNq#884790545_seeQa"/> // krazy:exclude=insecurenet 0901 * </rsp> 0902 * 0903 * Since all we care about is success or not, we can just check the rsp stat. 0904 */ 0905 0906 void SmugTalker::parseResponseAddPhoto(const QByteArray& data) 0907 { 0908 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto"; 0909 0910 QJsonParseError err; 0911 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0912 0913 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json doc " << doc; 0914 0915 if (err.error != QJsonParseError::NoError) 0916 { 0917 Q_EMIT signalBusy(false); 0918 Q_EMIT signalAddPhotoDone(err.error, errorToText(err.error, err.errorString())); 0919 return; 0920 } 0921 0922 Q_EMIT signalBusy(false); 0923 Q_EMIT signalAddPhotoDone(err.error, errorToText(err.error, err.errorString())); 0924 } 0925 0926 void SmugTalker::parseResponseCreateAlbum(const QByteArray& data) 0927 { 0928 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateAlbum"; 0929 0930 QJsonParseError err; 0931 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0932 0933 if (err.error != QJsonParseError::NoError) 0934 { 0935 Q_EMIT signalBusy(false); 0936 Q_EMIT signalCreateAlbumDone(err.error, err.errorString(), 0, QString()); 0937 return; 0938 } 0939 0940 QJsonObject jsonObject = doc.object(); 0941 QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); 0942 QJsonObject album = response[QLatin1String("Album")].toObject(); 0943 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json data : " << jsonObject; 0944 0945 QString newAlbumKey = album[QLatin1String("AlbumKey")].toString(); 0946 0947 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "newAlbumKey " << newAlbumKey; 0948 0949 Q_EMIT signalBusy(false); 0950 Q_EMIT signalCreateAlbumDone(0, errorToText(0, QString()), 0, newAlbumKey); 0951 } 0952 0953 void SmugTalker::parseResponseListAlbums(const QByteArray& data) 0954 { 0955 QJsonParseError err; 0956 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 0957 0958 if (err.error != QJsonParseError::NoError) 0959 { 0960 Q_EMIT signalBusy(false); 0961 Q_EMIT signalListAlbumsDone(err.error,i18n("Failed to list albums"), QList<SmugAlbum>()); 0962 return; 0963 } 0964 0965 QJsonObject jsonObject = doc.object(); 0966 QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); 0967 QJsonArray jsonArray = response[QLatin1String("Album")].toArray(); 0968 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListAlbum : " << jsonObject; 0969 0970 QList<SmugAlbum> albumList; 0971 0972 Q_FOREACH (const QJsonValue& value, jsonArray) 0973 { 0974 QJsonObject obj = value.toObject(); 0975 0976 SmugAlbum album; 0977 0978 album.nodeID = obj[QLatin1String("NodeID")].toString(); 0979 album.name = obj[QLatin1String("Name")].toString(); 0980 album.key = obj[QLatin1String("AlbumKey")].toString(); 0981 album.title = obj[QLatin1String("Title")].toString(); 0982 album.description = obj[QLatin1String("Description")].toString(); 0983 album.keywords = obj[QLatin1String("Keywords")].toString(); 0984 album.canShare = obj[QLatin1String("CanShare")].toBool(); 0985 album.passwordHint = obj[QLatin1String("PasswordHint")].toString(); 0986 album.imageCount = obj[QLatin1String("ImageCount")].toInt(); 0987 0988 albumList.append(album); 0989 0990 QStringList albumParams; 0991 albumParams << album.nodeID 0992 << album.name 0993 << album.key 0994 << album.title 0995 << album.description 0996 << album.keywords 0997 << QString::number(album.canShare) 0998 << album.passwordHint 0999 << QString::number(album.imageCount); 1000 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "album " << albumParams.join(QLatin1String(",")); 1001 } 1002 1003 std::sort(albumList.begin(), albumList.end(), SmugAlbum::lessThan); 1004 1005 Q_EMIT signalBusy(false); 1006 Q_EMIT signalListAlbumsDone(err.error, errorToText(err.error, err.errorString()), albumList); 1007 } 1008 1009 void SmugTalker::parseResponseListPhotos(const QByteArray& data) 1010 { 1011 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotos"; 1012 1013 QJsonParseError err; 1014 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 1015 1016 if (err.error != QJsonParseError::NoError) 1017 { 1018 Q_EMIT signalBusy(false); 1019 Q_EMIT signalListPhotosDone(err.error, errorToText(err.error, err.errorString()), QList<SmugPhoto>()); 1020 return; 1021 } 1022 1023 QJsonObject jsonObject = doc.object(); 1024 QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); 1025 QJsonArray jsonArray = response[QLatin1String("AlbumImage")].toArray(); 1026 1027 QList<SmugPhoto> photosList; 1028 1029 Q_FOREACH (const QJsonValue& value, jsonArray) 1030 { 1031 QJsonObject obj = value.toObject(); 1032 1033 SmugPhoto photo; 1034 photo.key = obj[QLatin1String("ImageKey")].toString(); 1035 photo.caption = obj[QLatin1String("Caption")].toString(); 1036 photo.keywords = obj[QLatin1String("Keywords")].toString(); 1037 photo.thumbURL = obj[QLatin1String("ThumbnailUrl")].toString(); 1038 photo.originalURL = obj[QLatin1String("ArchivedUri")].toString(); 1039 1040 photosList.append(photo); 1041 1042 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "photo key: " << photo.key 1043 << ", captions: " << photo.caption 1044 << ", keywords: " << photo.keywords 1045 << ", ThumbnailUrl " << photo.thumbURL 1046 << ", originalURL " << photo.originalURL; 1047 1048 } 1049 1050 Q_EMIT signalBusy(false); 1051 Q_EMIT signalListPhotosDone(0, QString(), photosList); 1052 } 1053 1054 void SmugTalker::parseResponseListAlbumTmpl(const QByteArray& data) 1055 { 1056 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ParseResponseListAlbumTmpl"; 1057 1058 QJsonParseError err; 1059 QJsonDocument doc = QJsonDocument::fromJson(data, &err); 1060 1061 if (err.error != QJsonParseError::NoError) 1062 { 1063 Q_EMIT signalBusy(false); 1064 Q_EMIT signalListAlbumTmplDone(err.error,i18n("Failed to list album template"), QList<SmugAlbumTmpl>()); 1065 return; 1066 } 1067 1068 QJsonObject jsonObject = doc.object(); 1069 QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); 1070 QJsonArray jsonArray = response[QLatin1String("AlbumTemplate")].toArray(); 1071 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "listAlbumTmpl = " << jsonArray; 1072 1073 QList<SmugAlbumTmpl> albumTmplList; 1074 1075 Q_FOREACH (const QJsonValue &value, jsonArray) 1076 { 1077 QJsonObject obj = value.toObject(); 1078 1079 SmugAlbumTmpl albumTmpl; 1080 albumTmpl.name = obj[QLatin1String("Name")].toString(); 1081 albumTmpl.uri = obj[QLatin1String("Uri")].toString(); 1082 albumTmpl.isPublic = obj[QLatin1String("Public")].toBool(); 1083 albumTmpl.password = obj[QLatin1String("Password")].toString(); 1084 albumTmpl.passwordHint = obj[QLatin1String("PasswordHint")].toString(); 1085 1086 albumTmplList.append(albumTmpl); 1087 1088 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "albumTmpl : name " << albumTmpl.name 1089 << ", uri : " << albumTmpl.uri 1090 << ", isPublic " << albumTmpl.isPublic 1091 << ", password " << albumTmpl.password 1092 << ", passwordHint " << albumTmpl.passwordHint; 1093 } 1094 1095 Q_EMIT signalBusy(false); 1096 Q_EMIT signalListAlbumTmplDone(0, errorToText(0, QString()), albumTmplList); 1097 } 1098 1099 /* Categories are deprecated in API v2 1100 1101 void SmugTalker::parseResponseListCategories(const QByteArray& data) 1102 { 1103 int errCode = -1; 1104 QString errMsg; 1105 QDomDocument doc(QLatin1String("categories.get")); 1106 1107 if (!doc.setContent(data)) 1108 { 1109 return; 1110 } 1111 1112 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse Categories response:" << QT_ENDL << data; 1113 1114 QList <SmugCategory> categoriesList; 1115 QDomElement e = doc.documentElement(); 1116 1117 for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) 1118 { 1119 if (!node.isElement()) 1120 { 1121 continue; 1122 } 1123 1124 e = node.toElement(); 1125 1126 if (e.tagName() == QLatin1String("Categories")) 1127 { 1128 for (QDomNode nodeC = e.firstChild(); !nodeC.isNull(); nodeC = nodeC.nextSibling()) 1129 { 1130 if (!nodeC.isElement()) 1131 { 1132 continue; 1133 } 1134 1135 QDomElement e = nodeC.toElement(); 1136 1137 if (e.tagName() == QLatin1String("Category")) 1138 { 1139 SmugCategory category; 1140 category.id = e.attribute(QLatin1String("id")).toLongLong(); 1141 category.name = htmlToText(e.attribute(QLatin1String("Name"))); 1142 categoriesList.append(category); 1143 } 1144 } 1145 1146 errCode = 0; 1147 } 1148 else if (e.tagName() == QLatin1String("err")) 1149 { 1150 errCode = e.attribute(QLatin1String("code")).toInt(); 1151 errMsg = e.attribute(QLatin1String("msg")); 1152 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error:" << errCode << errMsg; 1153 } 1154 } 1155 1156 if (errCode == 15) // 15: empty list 1157 { 1158 errCode = 0; 1159 } 1160 1161 Q_EMIT signalBusy(false); 1162 Q_EMIT signalListCategoriesDone(errCode, errorToText(errCode, errMsg), categoriesList); 1163 } 1164 1165 void SmugTalker::parseResponseListSubCategories(const QByteArray& data) 1166 { 1167 int errCode = -1; 1168 QString errMsg; 1169 QDomDocument doc(QLatin1String("subcategories.get")); 1170 1171 if (!doc.setContent(data)) 1172 { 1173 return; 1174 } 1175 1176 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse SubCategories response:" << QT_ENDL << data; 1177 1178 QList <SmugCategory> categoriesList; 1179 QDomElement e = doc.documentElement(); 1180 1181 for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) 1182 { 1183 if (!node.isElement()) 1184 { 1185 continue; 1186 } 1187 1188 e = node.toElement(); 1189 1190 if (e.tagName() == QLatin1String("SubCategories")) 1191 { 1192 for (QDomNode nodeC = e.firstChild(); !nodeC.isNull(); nodeC = nodeC.nextSibling()) 1193 { 1194 if (!nodeC.isElement()) 1195 { 1196 continue; 1197 } 1198 1199 e = nodeC.toElement(); 1200 1201 if (e.tagName() == QLatin1String("SubCategory")) 1202 { 1203 SmugCategory category; 1204 category.id = e.attribute(QLatin1String("id")).toLongLong(); 1205 category.name = htmlToText(e.attribute(QLatin1String("Name"))); 1206 categoriesList.append(category); 1207 } 1208 } 1209 1210 errCode = 0; 1211 } 1212 else if (e.tagName() == QLatin1String("err")) 1213 { 1214 errCode = e.attribute(QLatin1String("code")).toInt(); 1215 errMsg = e.attribute(QLatin1String("msg")); 1216 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error:" << errCode << errMsg; 1217 } 1218 } 1219 1220 if (errCode == 15) // 15: empty list 1221 { 1222 errCode = 0; 1223 } 1224 1225 Q_EMIT signalBusy(false); 1226 Q_EMIT signalListSubCategoriesDone(errCode, errorToText(errCode, errMsg), categoriesList); 1227 } 1228 */ 1229 1230 QString SmugTalker::htmlToText(const QString& htmlText) const 1231 { 1232 QTextDocument txtDoc; 1233 txtDoc.setHtml(htmlText); 1234 1235 return txtDoc.toPlainText(); 1236 } 1237 1238 } // namespace DigikamGenericSmugPlugin 1239 1240 #include "moc_smugtalker.cpp"