1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2014-09-30
7 * Description : a tool to export items to Piwigo web service
8 *
9 * Copyright (C) 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
10 * Copyright (C) 2006 by Colin Guthrie <kde at colin dot guthr dot ie>
11 * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12 * Copyright (C) 2008 by Andrea Diamantini <adjam7 at gmail dot com>
13 * Copyright (C) 2010-2019 by Frederic Coiffier <frederic dot coiffier at free dot com>
14 *
15 * This program is free software; you can redistribute it
16 * and/or modify it under the terms of the GNU General
17 * Public License as published by the Free Software Foundation;
18 * either version 2, or (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * ============================================================ */
26
27 #include "piwigotalker.h"
28
29 // Qt includes
30
31 #include <QByteArray>
32 #include <QImage>
33 #include <QRegExp>
34 #include <QXmlStreamReader>
35 #include <QFileInfo>
36 #include <QMessageBox>
37 #include <QApplication>
38 #include <QCryptographicHash>
39 #include <QUuid>
40
41 // KDE includes
42
43 #include <klocalizedstring.h>
44
45 // Local includes
46
47 #include "dmetadata.h"
48 #include "digikam_debug.h"
49 #include "piwigoitem.h"
50 #include "digikam_version.h"
51 #include "wstoolutils.h"
52 #include "previewloadthread.h"
53
54 namespace DigikamGenericPiwigoPlugin
55 {
56
57 class Q_DECL_HIDDEN PiwigoTalker::Private
58 {
59 public:
60
Private()61 explicit Private()
62 : parent (nullptr),
63 state (GE_LOGOUT),
64 netMngr (nullptr),
65 reply (nullptr),
66 loggedIn (false),
67 chunkId (0),
68 nbOfChunks (0),
69 version (-1),
70 albumId (0),
71 photoId (0),
72 iface (nullptr)
73 {
74 }
75
76 QWidget* parent;
77 State state;
78 QString cookie;
79 QUrl url;
80 QNetworkAccessManager* netMngr;
81 QNetworkReply* reply;
82 bool loggedIn;
83 QByteArray talker_buffer;
84 uint chunkId;
85 uint nbOfChunks;
86 int version;
87
88 QByteArray md5sum;
89 QString path;
90 QString tmpPath; ///< If set, contains a temporary file which must be deleted
91 int albumId;
92 int photoId; ///< Filled when the photo already exist
93 QString comment; ///< Synchronized with Piwigo comment
94 QString title; ///< Synchronized with Piwigo name
95 QString author; ///< Synchronized with Piwigo author
96 QDateTime date; ///< Synchronized with Piwigo date
97 DInfoInterface* iface;
98 };
99
100 QString PiwigoTalker::s_authToken = QLatin1String("");
101
PiwigoTalker(DInfoInterface * const iface,QWidget * const parent)102 PiwigoTalker::PiwigoTalker(DInfoInterface* const iface, QWidget* const parent)
103 : d(new Private)
104 {
105 d->parent = parent;
106 d->iface = iface;
107 d->netMngr = new QNetworkAccessManager(this);
108
109 connect(d->netMngr, SIGNAL(finished(QNetworkReply*)),
110 this, SLOT(slotFinished(QNetworkReply*)));
111 }
112
~PiwigoTalker()113 PiwigoTalker::~PiwigoTalker()
114 {
115 cancel();
116 WSToolUtils::removeTemporaryDir("piwigo");
117
118 delete d;
119 }
120
cancel()121 void PiwigoTalker::cancel()
122 {
123 deleteTemporaryFile();
124
125 if (d->reply)
126 {
127 d->reply->abort();
128 d->reply = nullptr;
129 }
130 }
131
getAuthToken()132 QString PiwigoTalker::getAuthToken()
133 {
134 return s_authToken;
135 }
136
computeMD5Sum(const QString & filepath)137 QByteArray PiwigoTalker::computeMD5Sum(const QString& filepath)
138 {
139 QFile file(filepath);
140
141 if (!file.open(QIODevice::ReadOnly))
142 {
143 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "File open error:" << filepath;
144 return QByteArray();
145 }
146
147 QByteArray md5sum = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
148 file.close();
149
150 return md5sum;
151 }
152
loggedIn() const153 bool PiwigoTalker::loggedIn() const
154 {
155 return d->loggedIn;
156 }
157
login(const QUrl & url,const QString & name,const QString & passwd)158 void PiwigoTalker::login(const QUrl& url, const QString& name, const QString& passwd)
159 {
160 d->url = url;
161 d->state = GE_LOGIN;
162 d->talker_buffer.resize(0);
163
164 // Add the page to the URL
165
166 if (!d->url.url().endsWith(QLatin1String(".php")))
167 {
168 d->url.setPath(d->url.path() + QLatin1Char('/') + QLatin1String("ws.php"));
169 }
170
171 s_authToken = QLatin1String(QUuid::createUuid().toByteArray().toBase64());
172
173 QStringList qsl;
174 qsl.append(QLatin1String("password=") + QString::fromUtf8(passwd.toUtf8().toPercentEncoding()));
175 qsl.append(QLatin1String("method=pwg.session.login"));
176 qsl.append(QLatin1String("username=") + QString::fromUtf8(name.toUtf8().toPercentEncoding()));
177 QString dataParameters = qsl.join(QLatin1Char('&'));
178 QByteArray buffer;
179 buffer.append(dataParameters.toUtf8());
180
181 QNetworkRequest netRequest(d->url);
182 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
183 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
184
185 d->reply = d->netMngr->post(netRequest, buffer);
186
187 emit signalBusy(true);
188 }
189
listAlbums()190 void PiwigoTalker::listAlbums()
191 {
192 d->state = GE_LISTALBUMS;
193 d->talker_buffer.resize(0);
194
195 QStringList qsl;
196 qsl.append(QLatin1String("method=pwg.categories.getList"));
197 qsl.append(QLatin1String("recursive=true"));
198 QString dataParameters = qsl.join(QLatin1Char('&'));
199 QByteArray buffer;
200 buffer.append(dataParameters.toUtf8());
201
202 QNetworkRequest netRequest(d->url);
203 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
204 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
205
206 d->reply = d->netMngr->post(netRequest, buffer);
207
208 emit signalBusy(true);
209 }
210
addPhoto(int albumId,const QString & mediaPath,bool rescale,int maxWidth,int maxHeight,int quality)211 bool PiwigoTalker::addPhoto(int albumId,
212 const QString& mediaPath,
213 bool rescale,
214 int maxWidth,
215 int maxHeight,
216 int quality)
217 {
218 d->state = GE_CHECKPHOTOEXIST;
219 d->talker_buffer.resize(0);
220
221 d->path = mediaPath; // By default, d->path contains the original file
222 d->tmpPath = QLatin1String(""); // By default, no temporary file (except with rescaling)
223 d->albumId = albumId;
224
225 d->md5sum = computeMD5Sum(mediaPath);
226
227 qCDebug(DIGIKAM_WEBSERVICES_LOG) << mediaPath << " " << d->md5sum.toHex();
228
229 if (mediaPath.endsWith(QLatin1String(".mp4")) || mediaPath.endsWith(QLatin1String(".MP4")) ||
230 mediaPath.endsWith(QLatin1String(".ogg")) || mediaPath.endsWith(QLatin1String(".OGG")) ||
231 mediaPath.endsWith(QLatin1String(".webm")) || mediaPath.endsWith(QLatin1String(".WEBM")))
232 {
233 // Video management
234 // Nothing to do
235 }
236 else
237 {
238 // Image management
239
240 QImage image = PreviewLoadThread::loadHighQualitySynchronously(mediaPath).copyQImage();
241
242 if (image.isNull())
243 {
244 image.load(mediaPath);
245 }
246
247 if (image.isNull())
248 {
249 // Invalid image
250 return false;
251 }
252
253 if (!rescale)
254 {
255 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Upload the original version: " << d->path;
256 }
257 else
258 {
259 // Rescale the image
260
261 if ((image.width() > maxWidth) || (image.height() > maxHeight))
262 {
263 image = image.scaled(maxWidth, maxHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
264 }
265
266 d->path = WSToolUtils::makeTemporaryDir("piwigo")
267 .filePath(QUrl::fromLocalFile(mediaPath).fileName());
268 d->tmpPath = d->path;
269 image.save(d->path, "JPEG", quality);
270
271 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Upload a resized version: " << d->path ;
272
273 // Restore all metadata with EXIF
274 // in the resized version
275
276 QScopedPointer<DMetadata> meta(new DMetadata);
277
278 if (meta->load(mediaPath))
279 {
280 meta->setItemDimensions(image.size());
281 meta->setItemOrientation(MetaEngine::ORIENTATION_NORMAL);
282 meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
283 meta->save(d->path, true);
284 }
285 else
286 {
287 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Image " << mediaPath << " has no exif data";
288 }
289 }
290 }
291
292 // Metadata management
293
294 // Complete name and comment for summary sending
295
296 QFileInfo fi(mediaPath);
297 d->title = fi.completeBaseName();
298 d->comment = QString();
299 d->author = QString();
300
301 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
302
303 d->date = fi.birthTime();
304
305 #else
306
307 d->date = fi.created();
308
309 #endif
310
311 // Look in the host database
312
313 DItemInfo info(d->iface->itemInfo(QUrl::fromLocalFile(mediaPath)));
314
315 if (!info.title().isEmpty())
316 {
317 d->title = info.title();
318 }
319
320 if (!info.comment().isEmpty())
321 {
322 d->comment = info.comment();
323 }
324
325 if (!info.creators().isEmpty())
326 {
327 d->author = info.creators().join(QLatin1String(" / "));
328 }
329
330 if (!info.dateTime().isNull())
331 {
332 d->date = info.dateTime();
333 }
334
335 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Title: " << d->title;
336 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Comment: " << d->comment;
337 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Author: " << d->author;
338 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Date: " << d->date;
339
340 QStringList qsl;
341 qsl.append(QLatin1String("method=pwg.images.exist"));
342 qsl.append(QLatin1String("md5sud->list=") + QLatin1String(d->md5sum.toHex()));
343 QString dataParameters = qsl.join(QLatin1Char('&'));
344 QByteArray buffer;
345 buffer.append(dataParameters.toUtf8());
346
347 QNetworkRequest netRequest(d->url);
348 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
349 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
350
351 d->reply = d->netMngr->post(netRequest, buffer);
352
353 emit signalProgressInfo(i18n("Check if %1 already exists", QUrl(mediaPath).fileName()));
354
355 emit signalBusy(true);
356
357 return true;
358 }
359
slotFinished(QNetworkReply * reply)360 void PiwigoTalker::slotFinished(QNetworkReply* reply)
361 {
362 if (reply != d->reply)
363 {
364 return;
365 }
366
367 d->reply = nullptr;
368 State state = d->state; // Can change in the treatment itself, so we cache it
369
370 if (reply->error() != QNetworkReply::NoError)
371 {
372 if (state == GE_LOGIN)
373 {
374 emit signalLoginFailed(reply->errorString());
375 qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->errorString();
376 }
377 else if (state == GE_GETVERSION)
378 {
379 qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->errorString();
380
381 // Version isn't mandatory and errors can be ignored
382 // As login succeeded, albums can be listed
383
384 listAlbums();
385 }
386 else if ((state == GE_CHECKPHOTOEXIST) || (state == GE_GETINFO) ||
387 (state == GE_SETINFO) || (state == GE_ADDPHOTOCHUNK) ||
388 (state == GE_ADDPHOTOSUMMARY))
389 {
390 deleteTemporaryFile();
391 emit signalAddPhotoFailed(reply->errorString());
392 }
393 else
394 {
395 QMessageBox::critical(QApplication::activeWindow(),
396 i18n("Error"), reply->errorString());
397 }
398
399 emit signalBusy(false);
400 reply->deleteLater();
401 return;
402 }
403
404 d->talker_buffer.append(reply->readAll());
405
406 switch (state)
407 {
408 case (GE_LOGIN):
409 parseResponseLogin(d->talker_buffer);
410 break;
411
412 case (GE_GETVERSION):
413 parseResponseGetVersion(d->talker_buffer);
414 break;
415
416 case (GE_LISTALBUMS):
417 parseResponseListAlbums(d->talker_buffer);
418 break;
419
420 case (GE_CHECKPHOTOEXIST):
421 parseResponseDoesPhotoExist(d->talker_buffer);
422 break;
423
424 case (GE_GETINFO):
425 parseResponseGetInfo(d->talker_buffer);
426 break;
427
428 case (GE_SETINFO):
429 parseResponseSetInfo(d->talker_buffer);
430 break;
431
432 case (GE_ADDPHOTOCHUNK):
433 // Support for Web API >= 2.4
434 parseResponseAddPhotoChunk(d->talker_buffer);
435 break;
436
437 case (GE_ADDPHOTOSUMMARY):
438 parseResponseAddPhotoSummary(d->talker_buffer);
439 break;
440
441 default: // GE_LOGOUT
442 break;
443 }
444
445 if ((state == GE_GETVERSION) && d->loggedIn)
446 {
447 listAlbums();
448 }
449
450 emit signalBusy(false);
451 reply->deleteLater();
452 }
453
parseResponseLogin(const QByteArray & data)454 void PiwigoTalker::parseResponseLogin(const QByteArray& data)
455 {
456 QXmlStreamReader ts(data);
457 QString line;
458 bool foundResponse = false;
459 d->loggedIn = false;
460
461 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseLogin: " << QString::fromUtf8(data);
462
463 while (!ts.atEnd())
464 {
465 ts.readNext();
466
467 if (ts.isStartElement())
468 {
469 foundResponse = true;
470
471 if ((ts.name() == QLatin1String("rsp")) &&
472 (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok")))
473 {
474 d->loggedIn = true;
475
476 /** Request Version */
477
478 d->state = GE_GETVERSION;
479 d->talker_buffer.resize(0);
480 d->version = -1;
481
482 QByteArray buffer = "method=pwg.getVersion";
483
484 QNetworkRequest netRequest(d->url);
485 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
486 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
487
488 d->reply = d->netMngr->post(netRequest, buffer);
489
490 emit signalBusy(true);
491
492 return;
493 }
494 }
495 }
496
497 if (!foundResponse)
498 {
499 emit signalLoginFailed(i18n("Piwigo URL probably incorrect"));
500 return;
501 }
502
503 if (!d->loggedIn)
504 {
505 emit signalLoginFailed(i18n("Incorrect username or password specified"));
506 }
507 }
508
parseResponseGetVersion(const QByteArray & data)509 void PiwigoTalker::parseResponseGetVersion(const QByteArray& data)
510 {
511 QXmlStreamReader ts(data);
512 QString line;
513 QRegExp verrx(QLatin1String(".?(\\d+)\\.(\\d+).*"));
514
515 bool foundResponse = false;
516
517 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseGetVersion: " << QString::fromUtf8(data);
518
519 while (!ts.atEnd())
520 {
521 ts.readNext();
522
523 if (ts.isStartElement())
524 {
525 foundResponse = true;
526
527 if ((ts.name() == QLatin1String("rsp")) &&
528 (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok")))
529 {
530 QString v = ts.readElementText();
531
532 if (verrx.exactMatch(v))
533 {
534 QStringList qsl = verrx.capturedTexts();
535 d->version = qsl[1].toInt() * 100 + qsl[2].toInt();
536 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Version: " << d->version;
537 break;
538 }
539 }
540 }
541 }
542
543 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "foundResponse : " << foundResponse;
544
545 if (d->version < PIWIGO_VER_2_4)
546 {
547 d->loggedIn = false;
548 emit signalLoginFailed(i18n("Upload to Piwigo version inferior to 2.4 is no longer supported"));
549 return;
550 }
551 }
552
parseResponseListAlbums(const QByteArray & data)553 void PiwigoTalker::parseResponseListAlbums(const QByteArray& data)
554 {
555 QString str = QString::fromUtf8(data);
556 QXmlStreamReader ts(data);
557 QString line;
558 bool foundResponse = false;
559 bool success = false;
560
561 typedef QList<PiwigoAlbum> PiwigoAlbumList;
562 PiwigoAlbumList albumList;
563 PiwigoAlbumList::iterator iter = albumList.begin();
564
565 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListAlbums";
566
567 while (!ts.atEnd())
568 {
569 ts.readNext();
570
571 if (ts.isEndElement() && (ts.name() == QLatin1String("categories")))
572 {
573 break;
574 }
575
576 if (ts.isStartElement())
577 {
578 if ((ts.name() == QLatin1String("rsp")) &&
579 (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok")))
580 {
581 foundResponse = true;
582 }
583
584 if (ts.name() == QLatin1String("categories"))
585 {
586 success = true;
587 }
588
589 if (ts.name() == QLatin1String("category"))
590 {
591 PiwigoAlbum album;
592 album.m_refNum = ts.attributes().value(QLatin1String("id")).toString().toInt();
593 album.m_parentRefNum = -1;
594
595 qCDebug(DIGIKAM_WEBSERVICES_LOG) << album.m_refNum << "\n";
596
597 iter = albumList.insert(iter, album);
598 }
599
600 if (ts.name() == QLatin1String("name"))
601 {
602 (*iter).m_name = ts.readElementText();
603 qCDebug(DIGIKAM_WEBSERVICES_LOG) << (*iter).m_name << "\n";
604 }
605
606 if (ts.name() == QLatin1String("uppercats"))
607 {
608 QString uppercats = ts.readElementText();
609 QStringList catlist = uppercats.split(QLatin1Char(','));
610
611 if ((catlist.size() > 1) && (catlist.at((uint)catlist.size() - 2).toInt() != (*iter).m_refNum))
612 {
613 (*iter).m_parentRefNum = catlist.at((uint)catlist.size() - 2).toInt();
614 qCDebug(DIGIKAM_WEBSERVICES_LOG) << (*iter).m_parentRefNum << "\n";
615 }
616 }
617 }
618 }
619
620 if (!foundResponse)
621 {
622 emit signalError(i18n("Invalid response received from remote Piwigo"));
623 return;
624 }
625
626 if (!success)
627 {
628 emit signalError(i18n("Failed to list albums"));
629 return;
630 }
631
632 // We need parent albums to come first for rest of the code to work
633
634 std::sort(albumList.begin(), albumList.end());
635
636 emit signalAlbums(albumList);
637 }
638
parseResponseDoesPhotoExist(const QByteArray & data)639 void PiwigoTalker::parseResponseDoesPhotoExist(const QByteArray& data)
640 {
641 QString str = QString::fromUtf8(data);
642 QXmlStreamReader ts(data);
643 QString line;
644 bool foundResponse = false;
645 bool success = false;
646
647 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseDoesPhotoExist: " << QString::fromUtf8(data);
648
649 while (!ts.atEnd())
650 {
651 ts.readNext();
652
653 if (ts.name() == QLatin1String("rsp"))
654 {
655 foundResponse = true;
656
657 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
658 {
659 success = true;
660 }
661
662 // Originally, first versions of Piwigo 2.4.x returned an invalid XML as the element started with a digit
663 // New versions are corrected (starting with _) : This code works with both versions
664
665 QRegExp md5rx(QLatin1String("_?([a-f0-9]+)>([0-9]+)</.+"));
666
667 ts.readNext();
668
669 if (md5rx.exactMatch(QString::fromUtf8(data.mid(ts.characterOffset()))))
670 {
671 QStringList qsl1 = md5rx.capturedTexts();
672
673 if (qsl1[1] == QLatin1String(d->md5sum.toHex()))
674 {
675 d->photoId = qsl1[2].toInt();
676 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "d->photoId: " << d->photoId;
677
678 emit signalProgressInfo(i18n("Photo '%1' already exists.", d->title));
679
680 d->state = GE_GETINFO;
681 d->talker_buffer.resize(0);
682
683 QStringList qsl2;
684 qsl2.append(QLatin1String("method=pwg.images.getInfo"));
685 qsl2.append(QLatin1String("image_id=") + QString::number(d->photoId));
686 QString dataParameters = qsl2.join(QLatin1Char('&'));
687 QByteArray buffer;
688 buffer.append(dataParameters.toUtf8());
689
690 QNetworkRequest netRequest(d->url);
691 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
692 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
693
694 d->reply = d->netMngr->post(netRequest, buffer);
695
696 return;
697 }
698 }
699 }
700 }
701
702 if (!foundResponse)
703 {
704 emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
705 return;
706 }
707
708 if (!success)
709 {
710 emit signalAddPhotoFailed(i18n("Failed to upload photo"));
711 return;
712 }
713
714 if (d->version >= PIWIGO_VER_2_4)
715 {
716 QFileInfo fi(d->path);
717
718 d->state = GE_ADDPHOTOCHUNK;
719 d->talker_buffer.resize(0);
720
721 // Compute the number of chunks for the image
722
723 d->nbOfChunks = (fi.size() / CHUNK_MAX_SIZE) + 1;
724 d->chunkId = 0;
725
726 addNextChunk();
727 }
728 else
729 {
730 emit signalAddPhotoFailed(i18n("Upload to Piwigo version inferior to 2.4 is no longer supported"));
731 return;
732 }
733 }
734
parseResponseGetInfo(const QByteArray & data)735 void PiwigoTalker::parseResponseGetInfo(const QByteArray& data)
736 {
737 QString str = QString::fromUtf8(data);
738 QXmlStreamReader ts(data);
739 QString line;
740 bool foundResponse = false;
741 bool success = false;
742 QList<int> categories;
743
744 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseGetInfo: " << QString::fromUtf8(data);
745
746 while (!ts.atEnd())
747 {
748 ts.readNext();
749
750 if (ts.isStartElement())
751 {
752 if (ts.name() == QLatin1String("rsp"))
753 {
754 foundResponse = true;
755
756 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
757 {
758 success = true;
759 }
760 }
761
762 if (ts.name() == QLatin1String("category"))
763 {
764 if (ts.attributes().hasAttribute(QLatin1String("id")))
765 {
766 QString id(ts.attributes().value(QLatin1String("id")).toString());
767 categories.append(id.toInt());
768 }
769 }
770 }
771 }
772
773 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "success : " << success;
774
775 if (!foundResponse)
776 {
777 emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
778 return;
779 }
780
781 if (categories.contains(d->albumId))
782 {
783 emit signalAddPhotoFailed(i18n("Photo '%1' already exists in this album.", d->title));
784 return;
785 }
786 else
787 {
788 categories.append(d->albumId);
789 }
790
791 d->state = GE_SETINFO;
792 d->talker_buffer.resize(0);
793
794 QStringList qsl_cat;
795
796 for (int i = 0 ; i < categories.size() ; ++i)
797 {
798 qsl_cat.append(QString::number(categories.at(i)));
799 }
800
801 QStringList qsl;
802 qsl.append(QLatin1String("method=pwg.images.setInfo"));
803 qsl.append(QLatin1String("image_id=") + QString::number(d->photoId));
804 qsl.append(QLatin1String("categories=") + QString::fromUtf8(qsl_cat.join(QLatin1Char(';')).toUtf8().toPercentEncoding()));
805 QString dataParameters = qsl.join(QLatin1Char('&'));
806 QByteArray buffer;
807 buffer.append(dataParameters.toUtf8());
808
809 QNetworkRequest netRequest(d->url);
810 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
811 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
812
813 d->reply = d->netMngr->post(netRequest, buffer);
814
815 return;
816 }
817
parseResponseSetInfo(const QByteArray & data)818 void PiwigoTalker::parseResponseSetInfo(const QByteArray& data)
819 {
820 QString str = QString::fromUtf8(data);
821 QXmlStreamReader ts(data);
822 QString line;
823 bool foundResponse = false;
824 bool success = false;
825
826 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseSetInfo: " << QString::fromUtf8(data);
827
828 while (!ts.atEnd())
829 {
830 ts.readNext();
831
832 if (ts.isStartElement())
833 {
834 if (ts.name() == QLatin1String("rsp"))
835 {
836 foundResponse = true;
837
838 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
839 {
840 success = true;
841 }
842
843 break;
844 }
845 }
846 }
847
848 if (!foundResponse)
849 {
850 emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
851
852 return;
853 }
854
855 if (!success)
856 {
857 emit signalAddPhotoFailed(i18n("Failed to upload photo"));
858
859 return;
860 }
861
862 deleteTemporaryFile();
863
864 emit signalAddPhotoSucceeded();
865 }
866
addNextChunk()867 void PiwigoTalker::addNextChunk()
868 {
869 QFile imagefile(d->path);
870
871 if (!imagefile.open(QIODevice::ReadOnly))
872 {
873 emit signalProgressInfo(i18n("Error : Cannot open photo: %1", QUrl(d->path).fileName()));
874 return;
875 }
876
877 d->chunkId++; // We start with chunk 1
878
879 imagefile.seek((d->chunkId - 1) * CHUNK_MAX_SIZE);
880
881 d->talker_buffer.resize(0);
882 QStringList qsl;
883 qsl.append(QLatin1String("method=pwg.images.addChunk"));
884 qsl.append(QLatin1String("original_sum=") + QLatin1String(d->md5sum.toHex()));
885 qsl.append(QLatin1String("position=") + QString::number(d->chunkId));
886 qsl.append(QLatin1String("type=file"));
887 qsl.append(QLatin1String("data=") + QString::fromUtf8(imagefile.read(CHUNK_MAX_SIZE).toBase64().toPercentEncoding()));
888 QString dataParameters = qsl.join(QLatin1Char('&'));
889 QByteArray buffer;
890 buffer.append(dataParameters.toUtf8());
891
892 imagefile.close();
893
894 QNetworkRequest netRequest(d->url);
895 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
896 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
897
898 d->reply = d->netMngr->post(netRequest, buffer);
899
900 emit signalProgressInfo(i18n("Upload the chunk %1/%2 of %3", d->chunkId, d->nbOfChunks, QUrl(d->path).fileName()));
901 }
902
parseResponseAddPhotoChunk(const QByteArray & data)903 void PiwigoTalker::parseResponseAddPhotoChunk(const QByteArray& data)
904 {
905 QString str = QString::fromUtf8(data);
906 QXmlStreamReader ts(data);
907 QString line;
908 bool foundResponse = false;
909 bool success = false;
910
911 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoChunk: " << QString::fromUtf8(data);
912
913 while (!ts.atEnd())
914 {
915 ts.readNext();
916
917 if (ts.isStartElement())
918 {
919 if (ts.name() == QLatin1String("rsp"))
920 {
921 foundResponse = true;
922
923 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
924 {
925 success = true;
926 }
927
928 break;
929 }
930 }
931 }
932
933 if (!foundResponse || !success)
934 {
935 emit signalProgressInfo(i18n("Warning : The full size photo cannot be uploaded."));
936 }
937
938 if (d->chunkId < d->nbOfChunks)
939 {
940 addNextChunk();
941 }
942 else
943 {
944 addPhotoSummary();
945 }
946 }
947
addPhotoSummary()948 void PiwigoTalker::addPhotoSummary()
949 {
950 d->state = GE_ADDPHOTOSUMMARY;
951 d->talker_buffer.resize(0);
952
953 QStringList qsl;
954 qsl.append(QLatin1String("method=pwg.images.add"));
955 qsl.append(QLatin1String("original_sum=") + QLatin1String(d->md5sum.toHex()));
956 qsl.append(QLatin1String("original_filename=") + QString::fromUtf8(QUrl(d->path).fileName().toUtf8().toPercentEncoding()));
957 qsl.append(QLatin1String("name=") + QString::fromUtf8(d->title.toUtf8().toPercentEncoding()));
958
959 if (!d->author.isEmpty())
960 {
961 qsl.append(QLatin1String("author=") + QString::fromUtf8(d->author.toUtf8().toPercentEncoding()));
962 }
963
964 if (!d->comment.isEmpty())
965 {
966 qsl.append(QLatin1String("comment=") + QString::fromUtf8(d->comment.toUtf8().toPercentEncoding()));
967 }
968
969 qsl.append(QLatin1String("categories=") + QString::number(d->albumId));
970 qsl.append(QLatin1String("file_sum=") + QLatin1String(computeMD5Sum(d->path).toHex()));
971 qsl.append(QLatin1String("date_creation=") +
972 QString::fromUtf8(d->date.toString(QLatin1String("yyyy-MM-dd hh:mm:ss")).toUtf8().toPercentEncoding()));
973
974 //qsl.append("tag_ids="); // TODO Implement this function
975
976 QString dataParameters = qsl.join(QLatin1Char('&'));
977 QByteArray buffer;
978 buffer.append(dataParameters.toUtf8());
979
980 QNetworkRequest netRequest(d->url);
981 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
982 netRequest.setRawHeader("Authorization", s_authToken.toLatin1());
983
984 d->reply = d->netMngr->post(netRequest, buffer);
985
986 emit signalProgressInfo(i18n("Upload the metadata of %1", QUrl(d->path).fileName()));
987 }
988
parseResponseAddPhotoSummary(const QByteArray & data)989 void PiwigoTalker::parseResponseAddPhotoSummary(const QByteArray& data)
990 {
991 QString str = QString::fromUtf8(data);
992 QXmlStreamReader ts(data.mid(data.indexOf("<?xml")));
993 QString line;
994 bool foundResponse = false;
995 bool success = false;
996
997 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoSummary: " << QString::fromUtf8(data);
998
999 while (!ts.atEnd())
1000 {
1001 ts.readNext();
1002
1003 if (ts.isStartElement())
1004 {
1005 if (ts.name() == QLatin1String("rsp"))
1006 {
1007 foundResponse = true;
1008
1009 if (ts.attributes().value(QLatin1String("stat")) == QLatin1String("ok"))
1010 {
1011 success = true;
1012 }
1013
1014 break;
1015 }
1016 }
1017 }
1018
1019 if (!foundResponse)
1020 {
1021 emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo (%1)", QString::fromUtf8(data)));
1022
1023 return;
1024 }
1025
1026 if (!success)
1027 {
1028 emit signalAddPhotoFailed(i18n("Failed to upload photo"));
1029
1030 return;
1031 }
1032
1033 deleteTemporaryFile();
1034
1035 emit signalAddPhotoSucceeded();
1036 }
1037
deleteTemporaryFile()1038 void PiwigoTalker::deleteTemporaryFile()
1039 {
1040 if (d->tmpPath.size())
1041 {
1042 QFile(d->tmpPath).remove();
1043 d->tmpPath = QLatin1String("");
1044 }
1045 }
1046
1047 } // namespace DigikamGenericPiwigoPlugin
1048