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"