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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2014-09-30
0007  * Description : a tool to export items to Piwigo web service
0008  *
0009  * SPDX-FileCopyrightText: 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006      by Colin Guthrie <kde at colin dot guthr dot ie>
0011  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  * SPDX-FileCopyrightText: 2008      by Andrea Diamantini <adjam7 at gmail dot com>
0013  * SPDX-FileCopyrightText: 2010-2019 by Frederic Coiffier <frederic dot coiffier at free dot com>
0014  *
0015  * SPDX-License-Identifier: GPL-2.0-or-later
0016  *
0017  * ============================================================ */
0018 
0019 #include "piwigotalker.h"
0020 
0021 // Qt includes
0022 
0023 #include <QByteArray>
0024 #include <QImage>
0025 #include <QRegularExpression>
0026 #include <QXmlStreamReader>
0027 #include <QFileInfo>
0028 #include <QMessageBox>
0029 #include <QApplication>
0030 #include <QCryptographicHash>
0031 #include <QUuid>
0032 
0033 // KDE includes
0034 
0035 #include <klocalizedstring.h>
0036 
0037 // Local includes
0038 
0039 #include "dmetadata.h"
0040 #include "digikam_debug.h"
0041 #include "digikam_version.h"
0042 #include "wstoolutils.h"
0043 #include "networkmanager.h"
0044 #include "previewloadthread.h"
0045 
0046 namespace DigikamGenericPiwigoPlugin
0047 {
0048 
0049 class Q_DECL_HIDDEN PiwigoTalker::Private
0050 {
0051 public:
0052 
0053     explicit Private()
0054       : parent      (nullptr),
0055         state       (PG_LOGOUT),
0056         netMngr     (nullptr),
0057         reply       (nullptr),
0058         loggedIn    (false),
0059         chunkId     (0),
0060         nbOfChunks  (0),
0061         version     (-1),
0062         albumId     (0),
0063         photoId     (0),
0064         iface       (nullptr)
0065     {
0066     }
0067 
0068     QWidget*               parent;
0069     State                  state;
0070     QString                cookie;
0071     QUrl                   url;
0072     QNetworkAccessManager* netMngr;
0073     QNetworkReply*         reply;
0074     bool                   loggedIn;
0075     QByteArray             talker_buffer;
0076     uint                   chunkId;
0077     uint                   nbOfChunks;
0078     int                    version;
0079 
0080     QByteArray             md5sum;
0081     QString                path;
0082     QString                tmpPath;    ///< If set, contains a temporary file which must be deleted
0083     int                    albumId;
0084     int                    photoId;    ///< Filled when the photo already exist
0085     QString                comment;    ///< Synchronized with Piwigo comment
0086     QString                title;      ///< Synchronized with Piwigo name
0087     QString                author;     ///< Synchronized with Piwigo author
0088     QDateTime              date;       ///< Synchronized with Piwigo date
0089     DInfoInterface*        iface;
0090 };
0091 
0092 QString PiwigoTalker::s_authToken = QLatin1String("");
0093 
0094 PiwigoTalker::PiwigoTalker(DInfoInterface* const iface, QWidget* const parent)
0095     : d(new Private)
0096 {
0097     d->parent  = parent;
0098     d->iface   = iface;
0099     d->netMngr = NetworkManager::instance()->getNetworkManager(this);
0100 
0101     connect(d->netMngr, SIGNAL(finished(QNetworkReply*)),
0102             this, SLOT(slotFinished(QNetworkReply*)));
0103 }
0104 
0105 PiwigoTalker::~PiwigoTalker()
0106 {
0107     cancel();
0108     WSToolUtils::removeTemporaryDir("piwigo");
0109 
0110     delete d;
0111 }
0112 
0113 void PiwigoTalker::cancel()
0114 {
0115     deleteTemporaryFile();
0116 
0117     if (d->reply)
0118     {
0119         d->reply->abort();
0120         d->reply = nullptr;
0121     }
0122 }
0123 
0124 QString PiwigoTalker::getAuthToken()
0125 {
0126     return s_authToken;
0127 }
0128 
0129 QByteArray PiwigoTalker::computeMD5Sum(const QString& filepath)
0130 {
0131     QFile file(filepath);
0132 
0133     if (!file.open(QIODevice::ReadOnly))
0134     {
0135         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "File open error:" << filepath;
0136 
0137         return QByteArray();
0138     }
0139 
0140     QByteArray md5sum = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
0141     file.close();
0142 
0143     return md5sum;
0144 }
0145 
0146 bool PiwigoTalker::loggedIn() const
0147 {
0148     return d->loggedIn;
0149 }
0150 
0151 void PiwigoTalker::login(const QUrl& url, const QString& name, const QString& passwd)
0152 {
0153     d->url   = url;
0154     d->state = PG_LOGIN;
0155     d->talker_buffer.resize(0);
0156 
0157     // Add the page to the URL
0158 
0159     if (!d->url.url().endsWith(QLatin1String(".php")))
0160     {
0161         d->url.setPath(d->url.path() + QLatin1Char('/') + QLatin1String("ws.php"));
0162     }
0163 
0164     s_authToken = QLatin1String(QUuid::createUuid().toByteArray().toBase64());
0165 
0166     QStringList qsl;
0167     qsl.append(QLatin1String("password=") + QString::fromUtf8(passwd.toUtf8().toPercentEncoding()));
0168     qsl.append(QLatin1String("method=pwg.session.login"));
0169     qsl.append(QLatin1String("username=") + QString::fromUtf8(name.toUtf8().toPercentEncoding()));
0170     QString dataParameters = qsl.join(QLatin1Char('&'));
0171     QByteArray buffer;
0172     buffer.append(dataParameters.toUtf8());
0173 
0174     QNetworkRequest netRequest(d->url);
0175     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0176     netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0177 
0178     d->reply = d->netMngr->post(netRequest, buffer);
0179 
0180     Q_EMIT signalBusy(true);
0181 }
0182 
0183 void PiwigoTalker::listAlbums()
0184 {
0185     d->state = PG_LISTALBUMS;
0186     d->talker_buffer.resize(0);
0187 
0188     QStringList qsl;
0189     qsl.append(QLatin1String("method=pwg.categories.getList"));
0190     qsl.append(QLatin1String("recursive=true"));
0191     QString dataParameters = qsl.join(QLatin1Char('&'));
0192     QByteArray buffer;
0193     buffer.append(dataParameters.toUtf8());
0194 
0195     QNetworkRequest netRequest(d->url);
0196     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0197     netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0198 
0199     d->reply = d->netMngr->post(netRequest, buffer);
0200 
0201     Q_EMIT signalBusy(true);
0202 }
0203 
0204 bool PiwigoTalker::addPhoto(int   albumId,
0205                             const QString& mediaPath,
0206                             bool  rescale,
0207                             int   maxWidth,
0208                             int   maxHeight,
0209                             int   quality)
0210 {
0211     d->state       = PG_CHECKPHOTOEXIST;
0212     d->talker_buffer.resize(0);
0213 
0214     d->path        = mediaPath;           // By default, d->path contains the original file
0215     d->tmpPath     = QLatin1String("");   // By default, no temporary file (except with rescaling)
0216     d->albumId     = albumId;
0217 
0218     d->md5sum      = computeMD5Sum(mediaPath);
0219 
0220     qCDebug(DIGIKAM_WEBSERVICES_LOG) << mediaPath << " " << d->md5sum.toHex();
0221 
0222     if (mediaPath.endsWith(QLatin1String(".mp4"))  || mediaPath.endsWith(QLatin1String(".MP4"))  ||
0223         mediaPath.endsWith(QLatin1String(".ogg"))  || mediaPath.endsWith(QLatin1String(".OGG"))  ||
0224         mediaPath.endsWith(QLatin1String(".webm")) || mediaPath.endsWith(QLatin1String(".WEBM")))
0225     {
0226         // Video management
0227         // Nothing to do
0228     }
0229     else
0230     {
0231         // Image management
0232 
0233         QImage image = PreviewLoadThread::loadHighQualitySynchronously(mediaPath).copyQImage();
0234 
0235         if (image.isNull())
0236         {
0237             if (!image.load(mediaPath) || image.isNull())
0238             {
0239                 // Invalid image
0240 
0241                 return false;
0242             }
0243         }
0244 
0245         if (!rescale)
0246         {
0247             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Upload the original version: " << d->path;
0248         }
0249         else
0250         {
0251             // Rescale the image
0252 
0253             if ((image.width() > maxWidth) || (image.height() > maxHeight))
0254             {
0255                 image = image.scaled(maxWidth, maxHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0256             }
0257 
0258             d->path = WSToolUtils::makeTemporaryDir("piwigo")
0259                                                     .filePath(QUrl::fromLocalFile(mediaPath).fileName());
0260             d->tmpPath = d->path;
0261             image.save(d->path, "JPEG", quality);
0262 
0263             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Upload a resized version: " << d->path;
0264 
0265             // Restore all metadata with EXIF
0266             // in the resized version
0267 
0268             QScopedPointer<DMetadata> meta(new DMetadata);
0269 
0270             if (meta->load(mediaPath))
0271             {
0272                 meta->setItemDimensions(image.size());
0273                 meta->setItemOrientation(MetaEngine::ORIENTATION_NORMAL);
0274                 meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
0275                 meta->save(d->path, true);
0276             }
0277             else
0278             {
0279                 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Image " << mediaPath << " has no exif data";
0280             }
0281         }
0282     }
0283 
0284     // Metadata management
0285 
0286     // Complete name and comment for summary sending
0287 
0288     QFileInfo fi(mediaPath);
0289     d->title   = fi.completeBaseName();
0290     d->comment = QString();
0291     d->author  = QString();
0292     d->date    = fi.birthTime();
0293 
0294     // Look in the host database
0295 
0296     DItemInfo info(d->iface->itemInfo(QUrl::fromLocalFile(mediaPath)));
0297 
0298     if (!info.title().isEmpty())
0299     {
0300         d->title = info.title();
0301     }
0302 
0303     if (!info.comment().isEmpty())
0304     {
0305         d->comment = info.comment();
0306     }
0307 
0308     if (!info.creators().isEmpty())
0309     {
0310         d->author = info.creators().join(QLatin1String(" / "));
0311     }
0312 
0313     if (!info.dateTime().isNull())
0314     {
0315         d->date = info.dateTime();
0316     }
0317 
0318     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Title: "   << d->title;
0319     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Comment: " << d->comment;
0320     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Author: "  << d->author;
0321     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Date: "    << d->date;
0322 
0323     QStringList qsl;
0324     qsl.append(QLatin1String("method=pwg.images.exist"));
0325     qsl.append(QLatin1String("md5sud->list=") + QLatin1String(d->md5sum.toHex()));
0326     QString dataParameters = qsl.join(QLatin1Char('&'));
0327     QByteArray buffer;
0328     buffer.append(dataParameters.toUtf8());
0329 
0330     QNetworkRequest netRequest(d->url);
0331     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0332     netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0333 
0334     d->reply = d->netMngr->post(netRequest, buffer);
0335 
0336     Q_EMIT signalProgressInfo(i18n("Check if %1 already exists", QUrl(mediaPath).fileName()));
0337 
0338     Q_EMIT signalBusy(true);
0339 
0340     return true;
0341 }
0342 
0343 void PiwigoTalker::slotFinished(QNetworkReply* reply)
0344 {
0345     if (reply != d->reply)
0346     {
0347         return;
0348     }
0349 
0350     d->reply    = nullptr;
0351     State state = d->state; // Can change in the treatment itself, so we cache it
0352 
0353     if (reply->error() != QNetworkReply::NoError)
0354     {
0355         if      (state == PG_LOGIN)
0356         {
0357             Q_EMIT signalLoginFailed(reply->errorString());
0358             qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->errorString();
0359         }
0360         else if (state == PG_GETVERSION)
0361         {
0362             qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->errorString();
0363 
0364             // Version isn't mandatory and errors can be ignored
0365             // As login succeeded, albums can be listed
0366 
0367             listAlbums();
0368         }
0369         else if ((state == PG_CHECKPHOTOEXIST) || (state == PG_GETINFO)       ||
0370                  (state == PG_SETINFO)         || (state == PG_ADDPHOTOCHUNK) ||
0371                  (state == PG_ADDPHOTOSUMMARY))
0372         {
0373             deleteTemporaryFile();
0374             Q_EMIT signalAddPhotoFailed(reply->errorString());
0375         }
0376         else
0377         {
0378             QMessageBox::critical(QApplication::activeWindow(),
0379                                   i18nc("@title:window", "Error"), reply->errorString());
0380         }
0381 
0382         Q_EMIT signalBusy(false);
0383         reply->deleteLater();
0384 
0385         return;
0386     }
0387 
0388     d->talker_buffer.append(reply->readAll());
0389 
0390     switch (state)
0391     {
0392         case (PG_LOGIN):
0393         {
0394             parseResponseLogin(d->talker_buffer);
0395             break;
0396         }
0397 
0398         case (PG_GETVERSION):
0399         {
0400             parseResponseGetVersion(d->talker_buffer);
0401             break;
0402         }
0403 
0404         case (PG_LISTALBUMS):
0405         {
0406             parseResponseListAlbums(d->talker_buffer);
0407             break;
0408         }
0409 
0410         case (PG_CHECKPHOTOEXIST):
0411         {
0412             parseResponseDoesPhotoExist(d->talker_buffer);
0413             break;
0414         }
0415 
0416         case (PG_GETINFO):
0417         {
0418             parseResponseGetInfo(d->talker_buffer);
0419             break;
0420         }
0421 
0422         case (PG_SETINFO):
0423         {
0424             parseResponseSetInfo(d->talker_buffer);
0425             break;
0426         }
0427 
0428         case (PG_ADDPHOTOCHUNK):
0429         {
0430             // Support for Web API >= 2.4
0431             parseResponseAddPhotoChunk(d->talker_buffer);
0432             break;
0433         }
0434 
0435         case (PG_ADDPHOTOSUMMARY):
0436         {
0437             parseResponseAddPhotoSummary(d->talker_buffer);
0438             break;
0439         }
0440 
0441         default:   // PG_LOGOUT
0442         {
0443             break;
0444         }
0445     }
0446 
0447     if ((state == PG_GETVERSION) && d->loggedIn)
0448     {
0449         listAlbums();
0450     }
0451 
0452     Q_EMIT signalBusy(false);
0453     reply->deleteLater();
0454 }
0455 
0456 void PiwigoTalker::parseResponseLogin(const QByteArray& data)
0457 {
0458     QXmlStreamReader ts(data);
0459     QString line;
0460     bool foundResponse = false;
0461     d->loggedIn        = false;
0462 
0463     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseLogin: " << QString::fromUtf8(data);
0464 
0465     while (!ts.atEnd())
0466     {
0467         ts.readNext();
0468 
0469         if (ts.isStartElement())
0470         {
0471             foundResponse = true;
0472 
0473             if ((ts.name() == QLatin1String("rsp")) &&
0474                 (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok")))
0475             {
0476                 d->loggedIn = true;
0477 
0478                 /** Request Version */
0479 
0480                 d->state          = PG_GETVERSION;
0481                 d->talker_buffer.resize(0);
0482                 d->version        = -1;
0483 
0484                 QByteArray buffer = "method=pwg.getVersion";
0485 
0486                 QNetworkRequest netRequest(d->url);
0487                 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0488                 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0489 
0490                 d->reply = d->netMngr->post(netRequest, buffer);
0491 
0492                 Q_EMIT signalBusy(true);
0493 
0494                 return;
0495             }
0496         }
0497     }
0498 
0499     if (!foundResponse)
0500     {
0501         Q_EMIT signalLoginFailed(i18n("Piwigo URL probably incorrect"));
0502 
0503         return;
0504     }
0505 
0506     if (!d->loggedIn)
0507     {
0508         Q_EMIT signalLoginFailed(i18n("Incorrect username or password specified"));
0509     }
0510 }
0511 
0512 void PiwigoTalker::parseResponseGetVersion(const QByteArray& data)
0513 {
0514     QXmlStreamReader ts(data);
0515     QString line;
0516     QRegularExpression verrx(QRegularExpression::anchoredPattern(QLatin1String(".*?(\\d+)\\.(\\d+).*")));
0517     verrx.setPatternOptions(QRegularExpression::DotMatchesEverythingOption);
0518 
0519     bool foundResponse = false;
0520 
0521     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseGetVersion: " << QString::fromUtf8(data);
0522 
0523     while (!ts.atEnd())
0524     {
0525         ts.readNext();
0526 
0527         if (ts.isStartElement())
0528         {
0529             foundResponse = true;
0530 
0531             if ((ts.name() == QLatin1String("rsp")) &&
0532                 (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok")))
0533             {
0534                 QString v                     = ts.readElementText();
0535                 QRegularExpressionMatch match = verrx.match(v);
0536 
0537                 if (match.hasMatch())
0538                 {
0539                     QStringList qsl = match.capturedTexts();
0540                     d->version      = qsl[1].toInt() * 100 + qsl[2].toInt();
0541                     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Version: " << d->version;
0542 
0543                     break;
0544                 }
0545             }
0546         }
0547     }
0548 
0549     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "foundResponse : " << foundResponse;
0550 
0551     if (d->version < PIWIGO_VER_2_4)
0552     {
0553         d->loggedIn = false;
0554         Q_EMIT signalLoginFailed(i18n("Upload to Piwigo version inferior to 2.4 is no longer supported"));
0555 
0556         return;
0557     }
0558 }
0559 
0560 void PiwigoTalker::parseResponseListAlbums(const QByteArray& data)
0561 {
0562     QString str        = QString::fromUtf8(data);
0563     QXmlStreamReader ts(data);
0564     QString line;
0565     bool foundResponse = false;
0566     bool success       = false;
0567 
0568     typedef QList<PiwigoAlbum> PiwigoAlbumList;
0569     PiwigoAlbumList albumList;
0570     PiwigoAlbumList::iterator iter = albumList.begin();
0571 
0572     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListAlbums";
0573 
0574     while (!ts.atEnd())
0575     {
0576         ts.readNext();
0577 
0578         if (ts.isEndElement() && (ts.name() == QLatin1String("categories")))
0579         {
0580             break;
0581         }
0582 
0583         if (ts.isStartElement())
0584         {
0585             if ((ts.name() == QLatin1String("rsp")) &&
0586                 (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok")))
0587             {
0588                 foundResponse = true;
0589             }
0590 
0591             if (ts.name() == QLatin1String("categories"))
0592             {
0593                 success = true;
0594             }
0595 
0596             if (ts.name() == QLatin1String("category"))
0597             {
0598                 PiwigoAlbum album;
0599                 album.m_refNum       = ts.attributes().value(QLatin1String("id")).toString().toInt();
0600                 album.m_parentRefNum = -1;
0601 
0602                 qCDebug(DIGIKAM_WEBSERVICES_LOG) << album.m_refNum << "\n";
0603 
0604                 iter                 = albumList.insert(iter, album);
0605             }
0606 
0607             if (ts.name() == QLatin1String("name"))
0608             {
0609                 (*iter).m_name = ts.readElementText();
0610                 qCDebug(DIGIKAM_WEBSERVICES_LOG) << (*iter).m_name << "\n";
0611             }
0612 
0613             if (ts.name() == QLatin1String("uppercats"))
0614             {
0615                 QString uppercats   = ts.readElementText();
0616                 QStringList catlist = uppercats.split(QLatin1Char(','));
0617 
0618                 if ((catlist.size() > 1) && (catlist.at((uint)catlist.size() - 2).toInt() != (*iter).m_refNum))
0619                 {
0620                     (*iter).m_parentRefNum = catlist.at((uint)catlist.size() - 2).toInt();
0621                     qCDebug(DIGIKAM_WEBSERVICES_LOG) << (*iter).m_parentRefNum << "\n";
0622                 }
0623             }
0624         }
0625     }
0626 
0627     if (!foundResponse)
0628     {
0629         Q_EMIT signalError(i18n("Invalid response received from remote Piwigo"));
0630 
0631         return;
0632     }
0633 
0634     if (!success)
0635     {
0636         Q_EMIT signalError(i18n("Failed to list albums"));
0637 
0638         return;
0639     }
0640 
0641     // We need parent albums to come first for rest of the code to work
0642 
0643     std::sort(albumList.begin(), albumList.end());
0644 
0645     Q_EMIT signalAlbums(albumList);
0646 }
0647 
0648 void PiwigoTalker::parseResponseDoesPhotoExist(const QByteArray& data)
0649 {
0650     QString str        = QString::fromUtf8(data);
0651     QXmlStreamReader ts(data);
0652     QString line;
0653     bool foundResponse = false;
0654     bool success       = false;
0655 
0656     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseDoesPhotoExist: " << QString::fromUtf8(data);
0657 
0658     while (!ts.atEnd())
0659     {
0660         ts.readNext();
0661 
0662         if (ts.name() == QLatin1String("rsp"))
0663         {
0664             foundResponse = true;
0665 
0666             if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
0667             {
0668                 success = true;
0669             }
0670 
0671             // Originally, first versions of Piwigo 2.4.x returned an invalid XML as the element started with a digit
0672             // New versions are corrected (starting with _) : This code works with both versions
0673 
0674             QRegularExpression md5rx(QRegularExpression::anchoredPattern(QLatin1String("_?([a-f0-9]+)>([0-9]+)</.+")));
0675             ts.readNext();
0676             QRegularExpressionMatch match = md5rx.match(QString::fromUtf8(data.mid(ts.characterOffset())));
0677 
0678             if (match.hasMatch())
0679             {
0680                 QStringList qsl1 = match.capturedTexts();
0681 
0682                 if (qsl1[1] == QLatin1String(d->md5sum.toHex()))
0683                 {
0684                     d->photoId = qsl1[2].toInt();
0685                     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "d->photoId: " << d->photoId;
0686 
0687                     Q_EMIT signalProgressInfo(i18n("Photo '%1' already exists.", d->title));
0688 
0689                     d->state   = PG_GETINFO;
0690                     d->talker_buffer.resize(0);
0691 
0692                     QStringList qsl2;
0693                     qsl2.append(QLatin1String("method=pwg.images.getInfo"));
0694                     qsl2.append(QLatin1String("image_id=") + QString::number(d->photoId));
0695                     QString dataParameters = qsl2.join(QLatin1Char('&'));
0696                     QByteArray buffer;
0697                     buffer.append(dataParameters.toUtf8());
0698 
0699                     QNetworkRequest netRequest(d->url);
0700                     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0701                     netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0702 
0703                     d->reply = d->netMngr->post(netRequest, buffer);
0704 
0705                     return;
0706                 }
0707             }
0708         }
0709     }
0710 
0711     if (!foundResponse)
0712     {
0713         Q_EMIT signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
0714 
0715         return;
0716     }
0717 
0718     if (!success)
0719     {
0720         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo"));
0721 
0722         return;
0723     }
0724 
0725     if (d->version >= PIWIGO_VER_2_4)
0726     {
0727         QFileInfo fi(d->path);
0728 
0729         d->state      = PG_ADDPHOTOCHUNK;
0730         d->talker_buffer.resize(0);
0731 
0732         // Compute the number of chunks for the image
0733 
0734         d->nbOfChunks = (fi.size() / CHUNK_MAX_SIZE) + 1;
0735         d->chunkId    = 0;
0736 
0737         addNextChunk();
0738     }
0739     else
0740     {
0741         Q_EMIT signalAddPhotoFailed(i18n("Upload to Piwigo version inferior to 2.4 is no longer supported"));
0742 
0743         return;
0744     }
0745 }
0746 
0747 void PiwigoTalker::parseResponseGetInfo(const QByteArray& data)
0748 {
0749     QString str        = QString::fromUtf8(data);
0750     QXmlStreamReader ts(data);
0751     QString line;
0752     bool foundResponse = false;
0753     bool success       = false;
0754     QList<int> categories;
0755 
0756     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseGetInfo: " << QString::fromUtf8(data);
0757 
0758     while (!ts.atEnd())
0759     {
0760         ts.readNext();
0761 
0762         if (ts.isStartElement())
0763         {
0764             if (ts.name() == QLatin1String("rsp"))
0765             {
0766                 foundResponse = true;
0767 
0768                 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
0769                 {
0770                     success = true;
0771                 }
0772             }
0773 
0774             if (ts.name() == QLatin1String("category"))
0775             {
0776                 if (ts.attributes().hasAttribute(QLatin1String("id")))
0777                 {
0778                     QString id(ts.attributes().value(QLatin1String("id")).toString());
0779                     categories.append(id.toInt());
0780                 }
0781             }
0782         }
0783     }
0784 
0785     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "success : " << success;
0786 
0787     if (!foundResponse)
0788     {
0789         Q_EMIT signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
0790 
0791         return;
0792     }
0793 
0794     if (categories.contains(d->albumId))
0795     {
0796         Q_EMIT signalAddPhotoFailed(i18n("Photo '%1' already exists in this album.", d->title));
0797 
0798         return;
0799     }
0800     else
0801     {
0802         categories.append(d->albumId);
0803     }
0804 
0805     d->state = PG_SETINFO;
0806     d->talker_buffer.resize(0);
0807 
0808     QStringList qsl_cat;
0809 
0810     for (int i = 0 ; i < categories.size() ; ++i)
0811     {
0812         qsl_cat.append(QString::number(categories.at(i)));
0813     }
0814 
0815     QStringList qsl;
0816     qsl.append(QLatin1String("method=pwg.images.setInfo"));
0817     qsl.append(QLatin1String("image_id=") + QString::number(d->photoId));
0818     qsl.append(QLatin1String("categories=") + QString::fromUtf8(qsl_cat.join(QLatin1Char(';')).toUtf8().toPercentEncoding()));
0819     QString dataParameters = qsl.join(QLatin1Char('&'));
0820     QByteArray buffer;
0821     buffer.append(dataParameters.toUtf8());
0822 
0823     QNetworkRequest netRequest(d->url);
0824     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0825     netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0826 
0827     d->reply = d->netMngr->post(netRequest, buffer);
0828 
0829     return;
0830 }
0831 
0832 void PiwigoTalker::parseResponseSetInfo(const QByteArray& data)
0833 {
0834     QString str        = QString::fromUtf8(data);
0835     QXmlStreamReader ts(data);
0836     QString line;
0837     bool foundResponse = false;
0838     bool success       = false;
0839 
0840     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseSetInfo: " << QString::fromUtf8(data);
0841 
0842     while (!ts.atEnd())
0843     {
0844         ts.readNext();
0845 
0846         if (ts.isStartElement())
0847         {
0848             if (ts.name() == QLatin1String("rsp"))
0849             {
0850                 foundResponse = true;
0851 
0852                 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
0853                 {
0854                     success = true;
0855                 }
0856 
0857                 break;
0858             }
0859         }
0860     }
0861 
0862     if (!foundResponse)
0863     {
0864         Q_EMIT signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
0865 
0866         return;
0867     }
0868 
0869     if (!success)
0870     {
0871         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo"));
0872 
0873         return;
0874     }
0875 
0876     deleteTemporaryFile();
0877 
0878     Q_EMIT signalAddPhotoSucceeded();
0879 }
0880 
0881 void PiwigoTalker::addNextChunk()
0882 {
0883     QFile imagefile(d->path);
0884 
0885     if (!imagefile.open(QIODevice::ReadOnly))
0886     {
0887         Q_EMIT signalProgressInfo(i18n("Error : Cannot open photo: %1", QUrl(d->path).fileName()));
0888 
0889         return;
0890     }
0891 
0892     d->chunkId++; // We start with chunk 1
0893 
0894     imagefile.seek((d->chunkId - 1) * CHUNK_MAX_SIZE);
0895 
0896     d->talker_buffer.resize(0);
0897     QStringList qsl;
0898     qsl.append(QLatin1String("method=pwg.images.addChunk"));
0899     qsl.append(QLatin1String("original_sum=") + QLatin1String(d->md5sum.toHex()));
0900     qsl.append(QLatin1String("position=") + QString::number(d->chunkId));
0901     qsl.append(QLatin1String("type=file"));
0902     qsl.append(QLatin1String("data=") + QString::fromUtf8(imagefile.read(CHUNK_MAX_SIZE).toBase64().toPercentEncoding()));
0903     QString dataParameters = qsl.join(QLatin1Char('&'));
0904     QByteArray buffer;
0905     buffer.append(dataParameters.toUtf8());
0906 
0907     imagefile.close();
0908 
0909     QNetworkRequest netRequest(d->url);
0910     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0911     netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0912 
0913     d->reply = d->netMngr->post(netRequest, buffer);
0914 
0915     Q_EMIT signalProgressInfo(i18n("Upload the chunk %1/%2 of %3", d->chunkId, d->nbOfChunks, QUrl(d->path).fileName()));
0916 }
0917 
0918 void PiwigoTalker::parseResponseAddPhotoChunk(const QByteArray& data)
0919 {
0920     QString str        = QString::fromUtf8(data);
0921     QXmlStreamReader ts(data);
0922     QString line;
0923     bool foundResponse = false;
0924     bool success       = false;
0925 
0926     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoChunk: " << QString::fromUtf8(data);
0927 
0928     while (!ts.atEnd())
0929     {
0930         ts.readNext();
0931 
0932         if (ts.isStartElement())
0933         {
0934             if (ts.name() == QLatin1String("rsp"))
0935             {
0936                 foundResponse = true;
0937 
0938                 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
0939                 {
0940                     success = true;
0941                 }
0942 
0943                 break;
0944             }
0945         }
0946     }
0947 
0948     if (!foundResponse || !success)
0949     {
0950         Q_EMIT signalProgressInfo(i18n("Warning : The full size photo cannot be uploaded."));
0951     }
0952 
0953     if (d->chunkId < d->nbOfChunks)
0954     {
0955         addNextChunk();
0956     }
0957     else
0958     {
0959         addPhotoSummary();
0960     }
0961 }
0962 
0963 void PiwigoTalker::addPhotoSummary()
0964 {
0965     d->state = PG_ADDPHOTOSUMMARY;
0966     d->talker_buffer.resize(0);
0967 
0968     QStringList qsl;
0969     qsl.append(QLatin1String("method=pwg.images.add"));
0970     qsl.append(QLatin1String("original_sum=") + QLatin1String(d->md5sum.toHex()));
0971     qsl.append(QLatin1String("original_filename=") + QString::fromUtf8(QUrl(d->path).fileName().toUtf8().toPercentEncoding()));
0972     qsl.append(QLatin1String("name=") + QString::fromUtf8(d->title.toUtf8().toPercentEncoding()));
0973 
0974     if (!d->author.isEmpty())
0975     {
0976         qsl.append(QLatin1String("author=") + QString::fromUtf8(d->author.toUtf8().toPercentEncoding()));
0977     }
0978 
0979     if (!d->comment.isEmpty())
0980     {
0981         qsl.append(QLatin1String("comment=") + QString::fromUtf8(d->comment.toUtf8().toPercentEncoding()));
0982     }
0983 
0984     qsl.append(QLatin1String("categories=") + QString::number(d->albumId));
0985     qsl.append(QLatin1String("file_sum=") + QLatin1String(computeMD5Sum(d->path).toHex()));
0986     qsl.append(QLatin1String("date_creation=") +
0987                QString::fromUtf8(d->date.toString(QLatin1String("yyyy-MM-dd hh:mm:ss")).toUtf8().toPercentEncoding()));
0988 /*
0989     qsl.append("tag_ids="); // TODO Implement this function
0990 */
0991     QString dataParameters = qsl.join(QLatin1Char('&'));
0992     QByteArray buffer;
0993     buffer.append(dataParameters.toUtf8());
0994 
0995     QNetworkRequest netRequest(d->url);
0996     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0997     netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
0998 
0999     d->reply = d->netMngr->post(netRequest, buffer);
1000 
1001     Q_EMIT signalProgressInfo(i18n("Upload the metadata of %1", QUrl(d->path).fileName()));
1002 }
1003 
1004 void PiwigoTalker::parseResponseAddPhotoSummary(const QByteArray& data)
1005 {
1006     QString str        = QString::fromUtf8(data);
1007     QXmlStreamReader ts(data.mid(data.indexOf("<?xml")));
1008     QString line;
1009     bool foundResponse = false;
1010     bool success       = false;
1011 
1012     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoSummary: " << QString::fromUtf8(data);
1013 
1014     while (!ts.atEnd())
1015     {
1016         ts.readNext();
1017 
1018         if (ts.isStartElement())
1019         {
1020             if (ts.name() == QLatin1String("rsp"))
1021             {
1022                 foundResponse = true;
1023 
1024                 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
1025                 {
1026                    success = true;
1027                 }
1028 
1029                 break;
1030             }
1031         }
1032     }
1033 
1034     if (!foundResponse)
1035     {
1036         Q_EMIT signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo (%1)", QString::fromUtf8(data)));
1037 
1038         return;
1039     }
1040 
1041     if (!success)
1042     {
1043         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo"));
1044 
1045         return;
1046     }
1047 
1048     deleteTemporaryFile();
1049 
1050     Q_EMIT signalAddPhotoSucceeded();
1051 }
1052 
1053 void PiwigoTalker::deleteTemporaryFile()
1054 {
1055     if (d->tmpPath.size())
1056     {
1057         QFile(d->tmpPath).remove();
1058         d->tmpPath = QLatin1String("");
1059     }
1060 }
1061 
1062 } // namespace DigikamGenericPiwigoPlugin
1063 
1064 #include "moc_piwigotalker.cpp"