File indexing completed on 2025-01-05 03:53:29

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-16-07
0007  * Description : a tool to export items to Google web services
0008  *
0009  * SPDX-FileCopyrightText: 2007-2008 by Vardhman Jain <vardhman at gmail dot com>
0010  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2009      by Luka Renko <lure at kubuntu dot org>
0012  * SPDX-FileCopyrightText: 2018      by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "gptalker.h"
0019 
0020 // Qt includes
0021 
0022 #include <QMimeDatabase>
0023 #include <QByteArray>
0024 #include <QDomDocument>
0025 #include <QDomElement>
0026 #include <QDomNode>
0027 #include <QJsonDocument>
0028 #include <QJsonParseError>
0029 #include <QJsonObject>
0030 #include <QJsonValue>
0031 #include <QJsonArray>
0032 #include <QFile>
0033 #include <QFileInfo>
0034 #include <QImage>
0035 #include <QStringList>
0036 #include <QUrlQuery>
0037 #include <QApplication>
0038 #include <QDir>
0039 #include <QMessageBox>
0040 
0041 // KDE includes
0042 
0043 #include <klocalizedstring.h>
0044 
0045 // Local includes
0046 
0047 #include "wstoolutils.h"
0048 #include "digikam_version.h"
0049 #include "gswindow.h"
0050 #include "gpmpform.h"
0051 #include "digikam_debug.h"
0052 #include "previewloadthread.h"
0053 #include "dmetadata.h"
0054 
0055 using namespace Digikam;
0056 
0057 #define NB_MAX_ITEM_UPLOAD 50
0058 
0059 namespace DigikamGenericGoogleServicesPlugin
0060 {
0061 
0062 static bool gphotoLessThan(const GSFolder& p1, const GSFolder& p2)
0063 {
0064     return (p1.title.toLower() < p2.title.toLower());
0065 }
0066 
0067 class Q_DECL_HIDDEN GPTalker::Private
0068 {
0069 public:
0070 
0071     enum State
0072     {
0073         GP_LOGOUT     = -1,
0074         GP_LISTALBUMS = 0,
0075         GP_GETUSER,
0076         GP_LISTPHOTOS,
0077         GP_ADDPHOTO,
0078         GP_UPDATEPHOTO,
0079         GP_UPLOADPHOTO,
0080         GP_GETPHOTO,
0081         GP_CREATEALBUM
0082     };
0083 
0084 public:
0085 
0086     explicit Private()
0087       : apiVersion(QLatin1String("v1")),
0088         userInfoUrl(QString::fromLatin1("https://www.googleapis.com/plus/%1/people/me").arg(apiVersion)),
0089         apiUrl(QString::fromLatin1("https://photoslibrary.googleapis.com/%1/%2").arg(apiVersion)),
0090         state(GP_LOGOUT),
0091         albumIdToUpload(QLatin1String("-1")),
0092         previousImageId(QLatin1String("-1"))
0093     {
0094     }
0095 
0096 public:
0097 
0098     QString                apiVersion;
0099 
0100     QString                userInfoUrl;
0101     QString                apiUrl;
0102 
0103     State                  state;
0104 
0105     QString                albumIdToUpload;
0106     QString                albumIdToImport;
0107     QString                previousImageId;
0108     QString                currDescription;
0109 
0110     QStringList            descriptionList;
0111     QStringList            uploadTokenList;
0112     QList<GSFolder>        albumList;
0113     QList<GSPhoto>         photoList;
0114 };
0115 
0116 GPTalker::GPTalker(QWidget* const parent)
0117     : GSTalkerBase(parent,
0118                    QStringList() // to get user login (temporary until gphoto supports it officially)
0119                                  << QLatin1String("https://www.googleapis.com/auth/plus.login")
0120                                  // to add and download photo in the library
0121                                  << QLatin1String("https://www.googleapis.com/auth/photoslibrary")
0122                                  // to download photo created by digiKam on GPhoto
0123                                  << QLatin1String("https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata")
0124                                  // for shared albums
0125                                  << QLatin1String("https://www.googleapis.com/auth/photoslibrary.sharing"),
0126                    QLatin1String("GooglePhotos")),
0127       d(new Private)
0128 {
0129     connect(m_service->networkAccessManager(), SIGNAL(finished(QNetworkReply*)),
0130             this, SLOT(slotFinished(QNetworkReply*)));
0131 
0132     connect(this, SIGNAL(signalError(QString)),
0133             this, SLOT(slotError(QString)));
0134 
0135     connect(this, SIGNAL(signalReadyToUpload()),
0136             this, SLOT(slotUploadPhoto()));
0137 }
0138 
0139 GPTalker::~GPTalker()
0140 {
0141     if (m_reply)
0142     {
0143         m_reply->abort();
0144         m_reply = nullptr;
0145     }
0146 
0147     WSToolUtils::removeTemporaryDir("google");
0148 
0149     delete d;
0150 }
0151 
0152 QStringList GPTalker::getUploadTokenList()
0153 {
0154     return d->uploadTokenList;
0155 }
0156 
0157 /**
0158  * (Trung): Comments below are not valid anymore with google photos api
0159  * Google Photo's Album listing request/response
0160  * First a request is sent to the url below and then we might(?) get a redirect URL
0161  * We then need to send the GET request to the Redirect url.
0162  * This uses the authenticated album list fetching to get all the albums included the unlisted-albums
0163  * which is not returned for an unauthorised request as done without the Authorization header.
0164  */
0165 void GPTalker::listAlbums(const QString& nextPageToken)
0166 {
0167     if (m_reply)
0168     {
0169         m_reply->abort();
0170         m_reply = nullptr;
0171     }
0172 
0173     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list albums";
0174 
0175     QUrl url(d->apiUrl.arg(QLatin1String("albums")));
0176 
0177     QUrlQuery query(url);
0178     query.addQueryItem(QLatin1String("pageSize"), QLatin1String("50"));
0179 
0180     if (nextPageToken.isEmpty())
0181     {
0182         d->albumList.clear();
0183     }
0184     else
0185     {
0186         query.addQueryItem(QLatin1String("pageToken"), nextPageToken);
0187     }
0188 
0189     url.setQuery(query);
0190 
0191     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url for list albums" << url;
0192 
0193     m_reply  = m_service->get(url);
0194 
0195     d->state = Private::GP_LISTALBUMS;
0196 
0197     Q_EMIT signalBusy(true);
0198 }
0199 
0200 /**
0201  * We get user profile from Google Plus API
0202  * This is a temporary solution until Google Photo support API for user profile
0203  */
0204 void GPTalker::getLoggedInUser()
0205 {
0206     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getLoggedInUser";
0207 
0208     if (m_reply)
0209     {
0210         m_reply->abort();
0211         m_reply = nullptr;
0212     }
0213 
0214     QUrl url(d->userInfoUrl);
0215 
0216     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url for list albums" << url;
0217 
0218     m_reply  = m_service->get(url);
0219 
0220     d->state = Private::GP_GETUSER;
0221 
0222     Q_EMIT signalBusy(true);
0223 }
0224 
0225 void GPTalker::listPhotos(const QString& albumId, const QString& nextPageToken)
0226 {
0227     if (m_reply)
0228     {
0229         m_reply->abort();
0230         m_reply = nullptr;
0231     }
0232 
0233     d->albumIdToImport = albumId;
0234 
0235     if (nextPageToken.isEmpty())
0236     {
0237         d->photoList.clear();
0238     }
0239 
0240     QUrl url(d->apiUrl.arg(QLatin1String("mediaItems:search")));
0241 
0242     QByteArray data;
0243     data += "{\"pageSize\": \"100\",";
0244 
0245     if (!nextPageToken.isEmpty())
0246     {
0247         data += "\"pageToken\": \"";
0248         data += nextPageToken.toLatin1();
0249         data += "\",";
0250     }
0251 
0252     data += "\"albumId\": \"";
0253     data += albumId.toLatin1();
0254     data += "\"}";
0255 
0256     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << "data to list photos:" << data;
0257 
0258     m_reply  = m_service->post(url, data);
0259 
0260     d->state = Private::GP_LISTPHOTOS;
0261 
0262     Q_EMIT signalBusy(true);
0263 }
0264 
0265 void GPTalker::createAlbum(const GSFolder& album)
0266 {
0267     if (m_reply)
0268     {
0269         m_reply->abort();
0270         m_reply = nullptr;
0271     }
0272 
0273     // Create body in json
0274     QByteArray data;
0275     data += "{\"album\": ";
0276     data += "{\"title\": \"";
0277     data += album.title.toUtf8();
0278     data += "\"}}";
0279     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << data;
0280 
0281     QUrl url(d->apiUrl.arg(QLatin1String("albums")));
0282 
0283     m_reply  = m_service->post(url, data);
0284 
0285     d->state = Private::GP_CREATEALBUM;
0286 
0287     Q_EMIT signalBusy(true);
0288 }
0289 
0290 /**
0291  * First a request is sent to the url below and then we will get an upload token
0292  * Upload token then will be sent with url in GPTlaker::uploadPhoto to create real photos on user account
0293  */
0294 bool GPTalker::addPhoto(const QString& photoPath,
0295                         GSPhoto& info,
0296                         const QString& albumId,
0297                         bool original,
0298                         bool rescale,
0299                         int maxDim,
0300                         int imageQuality)
0301 {
0302     if (m_reply)
0303     {
0304         m_reply->abort();
0305         m_reply = nullptr;
0306     }
0307 
0308     QUrl url(d->apiUrl.arg(QLatin1String("uploads")));
0309 
0310     // Save album ID and description to upload
0311     d->currDescription = info.description;
0312     d->albumIdToUpload = albumId;
0313 
0314     QString path(photoPath);
0315 
0316     QMimeDatabase mimeDB;
0317 
0318     if (!original && mimeDB.mimeTypeForFile(photoPath).name().startsWith(QLatin1String("image/")))
0319     {
0320         QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage();
0321 
0322         if (image.isNull())
0323         {
0324             image.load(photoPath);
0325         }
0326 
0327         if (image.isNull())
0328         {
0329             return false;
0330         }
0331 
0332         path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(photoPath)
0333                                              .baseName().trimmed() + QLatin1String(".jpg"));
0334 
0335         if (rescale && ((image.width() > maxDim) || (image.height() > maxDim)))
0336         {
0337             image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0338         }
0339 
0340         image.save(path, "JPEG", imageQuality);
0341 
0342         QScopedPointer<DMetadata> meta(new DMetadata);
0343 
0344         if (meta->load(photoPath))
0345         {
0346             meta->setItemDimensions(image.size());
0347             meta->setItemOrientation(MetaEngine::ORIENTATION_NORMAL);
0348             meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
0349             meta->save(path, true);
0350         }
0351     }
0352 
0353     // Create the body for temporary upload
0354     QFile imageFile(path);
0355 
0356     if (!imageFile.open(QIODevice::ReadOnly))
0357     {
0358         return false;
0359     }
0360 
0361     QByteArray data = imageFile.readAll();
0362     imageFile.close();
0363 
0364     QString imageName = QUrl::fromLocalFile(path).fileName();
0365 
0366     QNetworkRequest netRequest(url);
0367     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/octet-stream"));
0368     netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1());
0369     netRequest.setRawHeader("X-Goog-Upload-File-Name", imageName.toUtf8());
0370     netRequest.setRawHeader("X-Goog-Upload-Protocol", "raw");
0371 
0372     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << imageName;
0373 
0374     m_reply  = m_service->networkAccessManager()->post(netRequest, data);
0375 
0376     d->state = Private::GP_ADDPHOTO;
0377 
0378     Q_EMIT signalBusy(true);
0379 
0380     return true;
0381 }
0382 
0383 bool GPTalker::updatePhoto(const QString& /*photoPath*/, GSPhoto& /*info*/,
0384                            bool /*rescale*/, int /*maxDim*/, int /*imageQuality*/)
0385 {
0386 /*
0387     if (m_reply)
0388     {
0389         m_reply->abort();
0390         m_reply = nullptr;
0391     }
0392 
0393     Q_EMIT signalBusy(true);
0394 
0395     GPMPForm form;
0396     QString path = photoPath;
0397 
0398     QMimeDatabase mimeDB;
0399 
0400     if (mimeDB.mimeTypeForFile(photoPath).name().startsWith(QLatin1String("image/")))
0401     {
0402         QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage();
0403 
0404         if (image.isNull())
0405         {
0406             image.load(photoPath);
0407         }
0408 
0409         if (image.isNull())
0410         {
0411             Q_EMIT signalBusy(false);
0412             return false;
0413         }
0414 
0415         path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(photoPath)
0416                                              .baseName().trimmed() + QLatin1String(".jpg"));
0417 
0418         if (rescale && (image.width() > maxDim || image.height() > maxDim))
0419         {
0420             image = image.scaled(maxDim,maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0421         }
0422 
0423         image.save(path, "JPEG", imageQuality);
0424 
0425         QScopedPointer<DMetadata> meta(new DMetadata);
0426 
0427         if (meta->load(photoPath))
0428         {
0429             meta->setItemDimensions(image.size());
0430             meta->setItemOrientation(MetaEngine::ORIENTATION_NORMAL);
0431             meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
0432             meta->save(path, true);
0433         }
0434     }
0435 
0436     QNetworkRequest netRequest(info.editUrl);
0437     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
0438     netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1() + "\nIf-Match: *");
0439 
0440     m_reply = m_netMngr->put(netRequest, form.formData());
0441 
0442     d->state = Private::GP_UPDATEPHOTO;
0443 */
0444     return true;
0445 }
0446 
0447 void GPTalker::getPhoto(const QString& imgPath)
0448 {
0449     if (m_reply)
0450     {
0451         m_reply->abort();
0452         m_reply = nullptr;
0453     }
0454 
0455     Q_EMIT signalBusy(true);
0456 
0457     QUrl url(imgPath);
0458 
0459     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << "link to get photo" << url;
0460 
0461     m_reply  = m_service->get(url);
0462 
0463     d->state = Private::GP_GETPHOTO;
0464 }
0465 
0466 void GPTalker::cancel()
0467 {
0468     if (m_reply)
0469     {
0470         m_reply->abort();
0471         m_reply = nullptr;
0472     }
0473 
0474     d->descriptionList.clear();
0475     d->uploadTokenList.clear();
0476 
0477     Q_EMIT signalBusy(false);
0478 }
0479 
0480 void GPTalker::slotError(const QString& error)
0481 {
0482     QString transError;
0483     int     errorNo = 0;
0484 
0485     if (!error.isEmpty())
0486         errorNo = error.toInt();
0487 
0488     switch (errorNo)
0489     {
0490         case 2:
0491             transError=i18n("No photo specified");
0492             break;
0493         case 3:
0494             transError=i18n("General upload failure");
0495             break;
0496         case 4:
0497             transError=i18n("File-size was zero");
0498             break;
0499         case 5:
0500             transError=i18n("File-type was not recognized");
0501             break;
0502         case 6:
0503             transError=i18n("User exceeded upload limit");
0504             break;
0505         case 96:
0506             transError=i18n("Invalid signature");
0507             break;
0508         case 97:
0509             transError=i18n("Missing signature");
0510             break;
0511         case 98:
0512             transError=i18n("Login failed / Invalid auth token");
0513             break;
0514         case 100:
0515             transError=i18n("Invalid API Key");
0516             break;
0517         case 105:
0518             transError=i18n("Service currently unavailable");
0519             break;
0520         case 108:
0521             transError=i18n("Invalid Frob");
0522             break;
0523         case 111:
0524             transError=i18n("Format \"xxx\" not found");
0525             break;
0526         case 112:
0527             transError=i18n("Method \"xxx\" not found");
0528             break;
0529         case 114:
0530             transError=i18n("Invalid SOAP envelope");
0531             break;
0532         case 115:
0533             transError=i18n("Invalid XML-RPC Method Call");
0534             break;
0535         case 116:
0536             transError=i18n("The POST method is now required for all setters.");
0537             break;
0538         default:
0539             transError=i18n("Unknown error");
0540     };
0541 
0542     QMessageBox::critical(QApplication::activeWindow(), i18nc("@title:window", "Error"),
0543                           i18n("Error occurred: %1\nUnable to proceed further.", transError + error));
0544 }
0545 
0546 void GPTalker::slotFinished(QNetworkReply* reply)
0547 {
0548     Q_EMIT signalBusy(false);
0549 
0550     if (reply != m_reply)
0551     {
0552         return;
0553     }
0554 
0555     m_reply = nullptr;
0556 
0557     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "reply error:" << reply->error()
0558                                      << "-" << reply->errorString();
0559 
0560     if (reply->error() != QNetworkReply::NoError)
0561     {
0562         if (d->state == Private::GP_ADDPHOTO)
0563         {
0564             Q_EMIT signalAddPhotoDone(reply->error(), reply->errorString());
0565         }
0566         else if (reply->error() != QNetworkReply::OperationCanceledError)
0567         {
0568             QMessageBox::critical(QApplication::activeWindow(),
0569                                   i18nc("@title:window", "Error"), reply->errorString());
0570         }
0571 
0572         reply->deleteLater();
0573         return;
0574     }
0575 
0576     QByteArray buffer = reply->readAll();
0577 
0578     switch (d->state)
0579     {
0580         case (Private::GP_LOGOUT):
0581             break;
0582         case (Private::GP_GETUSER):
0583             parseResponseGetLoggedInUser(buffer);
0584             break;
0585         case (Private::GP_CREATEALBUM):
0586             parseResponseCreateAlbum(buffer);
0587             break;
0588         case (Private::GP_LISTALBUMS):
0589             parseResponseListAlbums(buffer);
0590             break;
0591         case (Private::GP_LISTPHOTOS):
0592             parseResponseListPhotos(buffer);
0593             break;
0594         case (Private::GP_ADDPHOTO):
0595             parseResponseAddPhoto(buffer);
0596             break;
0597         case (Private::GP_UPDATEPHOTO):
0598             Q_EMIT signalAddPhotoDone(1, QString());
0599             break;
0600         case (Private::GP_UPLOADPHOTO):
0601             parseResponseUploadPhoto(buffer);
0602             break;
0603         case (Private::GP_GETPHOTO):
0604         {
0605             // all we get is data of the image
0606             // qCDebug(DIGIKAM_WEBSERVICES_LOG) << buffer;
0607 
0608             QString header         = reply->header(QNetworkRequest::ContentDispositionHeader).toString();
0609             QStringList headerList = header.split(QLatin1Char(';'));
0610             QString fileName;
0611 
0612             if (headerList.count() > 1                          &&
0613                 headerList.at(0) == QLatin1String("attachment") &&
0614                 headerList.at(1).contains(QLatin1String("filename=")))
0615             {
0616                 fileName = headerList.at(1).section(QLatin1Char('"'), 1, 1);
0617             }
0618 
0619             Q_EMIT signalGetPhotoDone(1, QString(), buffer, fileName);
0620 
0621             break;
0622         }
0623     }
0624 
0625     reply->deleteLater();
0626 }
0627 
0628 void GPTalker::slotUploadPhoto()
0629 {
0630     /* Keep track of number of items will be uploaded, because
0631      * Google Photo API upload maximum NB_MAX_ITEM_UPLOAD items in at a time
0632      */
0633     int nbItemsUpload = 0;
0634 
0635     if (m_reply)
0636     {
0637         m_reply->abort();
0638         m_reply = nullptr;
0639     }
0640 
0641     QUrl url(d->apiUrl.arg(QLatin1String("mediaItems:batchCreate")));
0642 
0643     QByteArray data;
0644     data += '{';
0645 
0646     if (d->albumIdToUpload != QLatin1String("-1"))
0647     {
0648         data += "\"albumId\": \"";
0649         data += d->albumIdToUpload.toLatin1();
0650         data += "\",";
0651     }
0652 
0653     data += "\"newMediaItems\": [";
0654 
0655     if (d->uploadTokenList.isEmpty())
0656     {
0657         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "token list is empty";
0658     }
0659 
0660     while (!d->uploadTokenList.isEmpty() && nbItemsUpload < NB_MAX_ITEM_UPLOAD)
0661     {
0662         const QString& uploadToken = d->uploadTokenList.takeFirst();
0663         data += "{\"description\": \"";
0664 
0665         if (d->descriptionList.isEmpty())
0666         {
0667             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "description list is empty";
0668         }
0669         else
0670         {
0671             data += d->descriptionList.takeFirst().toUtf8();
0672         }
0673 
0674         data += "\",";
0675         data += "\"simpleMediaItem\": {";
0676         data += "\"uploadToken\": \"";
0677         data += uploadToken.toLatin1();
0678         data += "\"}}";
0679 
0680         if (d->uploadTokenList.length() > 0)
0681         {
0682             data += ',';
0683         }
0684 
0685         nbItemsUpload ++;
0686     }
0687 
0688     if (d->previousImageId == QLatin1String("-1"))
0689     {
0690         data += ']';
0691     }
0692     else
0693     {
0694         data += "],\"albumPosition\": {";
0695         data += "\"position\": \"AFTER_MEDIA_ITEM\",";
0696         data += "\"relativeMediaItemId\": \"";
0697         data += d->previousImageId.toLatin1();
0698         data += "\"}\r\n";
0699     }
0700 
0701     data += "}\r\n";
0702 
0703     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << data;
0704 
0705     QNetworkRequest netRequest(url);
0706     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
0707     netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1());
0708 
0709     m_reply  = m_service->networkAccessManager()->post(netRequest, data);
0710 
0711     d->state = Private::GP_UPLOADPHOTO;
0712 
0713     Q_EMIT signalBusy(true);
0714 }
0715 
0716 void GPTalker::parseResponseListAlbums(const QByteArray& data)
0717 {
0718     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListAlbums";
0719 
0720     QJsonParseError err;
0721     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0722 
0723     if (err.error != QJsonParseError::NoError)
0724     {
0725         Q_EMIT signalBusy(false);
0726         Q_EMIT signalListAlbumsDone(0, QString::fromLatin1("Code: %1 - %2").arg(err.error)
0727                                                                          .arg(err.errorString()),
0728                                   QList<GSFolder>());
0729         return;
0730     }
0731 
0732     QJsonObject jsonObject  = doc.object();
0733     QJsonArray jsonArray    = jsonObject[QLatin1String("albums")].toArray();
0734     // qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json array " << doc;
0735 
0736     /**
0737      * Google-photos allows user to post photos on their main page (not in any albums)
0738      * so this folder is created for that purpose
0739      */
0740 
0741     if (d->albumList.isEmpty())
0742     {
0743         GSFolder mainPage;
0744         d->albumList.append(mainPage);
0745     }
0746 
0747     Q_FOREACH (const QJsonValue& value, jsonArray)
0748     {
0749         GSFolder album;
0750 
0751         QJsonObject obj     = value.toObject();
0752         album.id            = obj[QLatin1String("id")].toString();
0753         album.title         = obj[QLatin1String("title")].toString();
0754         album.url           = obj[QLatin1String("productUrl")].toString();
0755         album.isWriteable   = obj[QLatin1String("isWriteable")].toBool();
0756 
0757         d->albumList.append(album);
0758     }
0759 
0760     QString nextPageToken   = jsonObject[QLatin1String("nextPageToken")].toString();
0761 
0762     if (!nextPageToken.isEmpty())
0763     {
0764         listAlbums(nextPageToken);
0765         return;
0766     }
0767 
0768     std::sort(d->albumList.begin(), d->albumList.end(), gphotoLessThan);
0769     Q_EMIT signalListAlbumsDone(1, QLatin1String(""), d->albumList);
0770 }
0771 
0772 void GPTalker::parseResponseListPhotos(const QByteArray& data)
0773 {
0774     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotos";
0775 
0776     QJsonParseError err;
0777     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0778 
0779     if (err.error != QJsonParseError::NoError)
0780     {
0781         Q_EMIT signalBusy(false);
0782         Q_EMIT signalListPhotosDone(0, i18n("Failed to fetch photo-set list"), QList<GSPhoto>());
0783         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error code:" << err.error
0784                                          << ", msg:" << err.errorString();
0785         return;
0786     }
0787 
0788     QJsonObject jsonObject  = doc.object();
0789     QJsonArray jsonArray    = jsonObject[QLatin1String("mediaItems")].toArray();
0790 
0791     Q_FOREACH (const QJsonValue& value, jsonArray)
0792     {
0793         QJsonObject obj = value.toObject();
0794 
0795         GSPhoto photo;
0796 
0797         photo.baseUrl        = obj[QLatin1String("baseUrl")].toString();
0798         photo.description    = obj[QLatin1String("description")].toString();
0799         photo.id             = obj[QLatin1String("id")].toString();
0800         photo.mimeType       = obj[QLatin1String("mimeType")].toString();
0801         photo.location       = obj[QLatin1String("Location")].toString(); // Not yet available in v1 but will be in the future
0802 
0803         QJsonObject metadata = obj[QLatin1String("mediaMetadata")].toObject();
0804 
0805         photo.creationTime   = metadata[QLatin1String("creationTime")].toString();
0806         photo.width          = metadata[QLatin1String("width")].toString();
0807         photo.height         = metadata[QLatin1String("height")].toString();
0808 
0809         QString option       = QLatin1String("=d");
0810 
0811         if (photo.mimeType.startsWith(QLatin1String("video/")))
0812         {
0813             option.append(QLatin1Char('v'));
0814         }
0815 
0816         photo.originalURL    = QUrl(photo.baseUrl + option);
0817         // qCDebug(DIGIKAM_WEBSERVICES_LOG) << photo.originalURL;
0818 
0819         d->photoList.append(photo);
0820     }
0821 
0822     QString nextPageToken    = jsonObject[QLatin1String("nextPageToken")].toString();
0823 
0824     if (!nextPageToken.isEmpty())
0825     {
0826         listPhotos(d->albumIdToImport, nextPageToken);
0827         return;
0828     }
0829 
0830     Q_EMIT signalListPhotosDone(1, QLatin1String(""), d->photoList);
0831 }
0832 
0833 void GPTalker::parseResponseCreateAlbum(const QByteArray& data)
0834 {
0835     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateAlbums";
0836 
0837     QJsonParseError err;
0838     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0839 
0840     if (err.error != QJsonParseError::NoError)
0841     {
0842         Q_EMIT signalBusy(false);
0843         Q_EMIT signalCreateAlbumDone(0, QString::fromLatin1("Code: %1 - %2").arg(err.error)
0844                                                                           .arg(err.errorString()),
0845                                    QString());
0846         return;
0847     }
0848 
0849     QJsonObject jsonObject  = doc.object();
0850     QString albumId         = jsonObject[QLatin1String("id")].toString();
0851     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "album Id"  << doc;
0852 
0853     Q_EMIT signalCreateAlbumDone(1, QLatin1String(""), albumId);
0854 }
0855 
0856 void GPTalker::parseResponseAddPhoto(const QByteArray& data)
0857 {
0858     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto";
0859     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "response" << data;
0860 
0861     d->uploadTokenList << QString::fromUtf8(data);
0862     d->descriptionList << d->currDescription;
0863 
0864     Q_EMIT signalAddPhotoDone(1, QString());
0865 }
0866 
0867 void GPTalker::parseResponseGetLoggedInUser(const QByteArray& data)
0868 {
0869     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseGetLoggedInUser";
0870 
0871     QJsonParseError err;
0872     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0873 
0874     if (err.error != QJsonParseError::NoError)
0875     {
0876         Q_EMIT signalBusy(false);
0877         return;
0878     }
0879 
0880     QJsonObject jsonObject = doc.object();
0881     QString userName       = jsonObject[QLatin1String("displayName")].toString();
0882 
0883     Q_EMIT signalSetUserName(userName);
0884 
0885     listAlbums();
0886 }
0887 
0888 //TODO: Parse and return photoID
0889 void GPTalker::parseResponseUploadPhoto(const QByteArray& data)
0890 {
0891     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseUploadPhoto";
0892 
0893     QJsonParseError err;
0894     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0895 
0896     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "doc" << doc;
0897 
0898     if (err.error != QJsonParseError::NoError)
0899     {
0900         Q_EMIT signalBusy(false);
0901         Q_EMIT signalUploadPhotoDone(0, err.errorString(), QStringList());
0902         return;
0903     }
0904 
0905     QJsonObject jsonObject  = doc.object();
0906     QJsonArray jsonArray    = jsonObject[QLatin1String("newMediaItemResults")].toArray();
0907 
0908     QStringList listPhotoId;
0909 
0910     Q_FOREACH (const QJsonValue& value, jsonArray)
0911     {
0912         QJsonObject obj = value.toObject();
0913 
0914         QJsonObject mediaItem = obj[QLatin1String("mediaItem")].toObject();
0915         listPhotoId << mediaItem[QLatin1String("id")].toString();
0916     }
0917 
0918     d->previousImageId = listPhotoId.last();
0919 
0920     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list photo Id" << listPhotoId.join(QLatin1String(", "));
0921 
0922     Q_EMIT signalBusy(false);
0923     Q_EMIT signalUploadPhotoDone(1, QString(), listPhotoId);
0924 }
0925 
0926 } // namespace DigikamGenericGoogleServicesPlugin
0927 
0928 #include "moc_gptalker.cpp"