source: extensions/digikam_export/piwigoexport/piwigotalker.cpp @ 4874

Last change on this file since 4874 was 4874, checked in by fcoiffie, 14 years ago

[digikam_export] Removal of all useless and commented code

  • Temporary files are removed
  • Warnings correction
  • Selected 'thumbnail size' is stored in the configuration file
File size: 18.9 KB
Line 
1/* ============================================================
2*
3* This file is a part of kipi-plugins project
4* http://www.kipi-plugins.org
5*
6* Date        : 2010-01-07
7* Description : a plugin to export to a remote Piwigo server.
8*
9* Copyright (C) 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
10* Copyright (C) 2006 by Colin Guthrie <kde@colin.guthr.ie>
11* Copyright (C) 2006-2009 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 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#include "piwigotalker.moc"
29
30// Qt includes
31
32#include <QByteArray>
33#include <QImage>
34#include <QRegExp>
35#include <QXmlStreamReader>
36#include <QFileInfo>
37#include <QCryptographicHash>
38
39// KDE includes
40
41#include <kdebug.h>
42#include <kio/job.h>
43#include <kio/jobuidelegate.h>
44#include <klocale.h>
45#include <kstandarddirs.h>
46
47// LibKExiv2 includes
48
49#include <libkexiv2/kexiv2.h>
50
51// LibKDcraw includes
52
53#include <libkdcraw/version.h>
54#include <libkdcraw/kdcraw.h>
55
56// Local includes
57
58#include "piwigoitem.h"
59#include "pluginsversion.h"
60
61namespace KIPIPiwigoExportPlugin
62{
63
64QString PiwigoTalker::s_authToken   = "";
65
66PiwigoTalker::PiwigoTalker(QWidget* parent)
67        : m_parent(parent),  m_job(0),  m_loggedIn(false)
68{
69}
70
71PiwigoTalker::~PiwigoTalker()
72{
73    if (m_job)
74        m_job->kill();
75}
76
77QByteArray PiwigoTalker::computeMD5Sum(const QString &filepath)
78{
79    QFile file(filepath);
80
81    file.open(QIODevice::ReadOnly);
82    QByteArray md5sum = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
83    file.close();
84
85    return md5sum;
86}
87
88bool PiwigoTalker::loggedIn() const
89{
90    return m_loggedIn;
91}
92
93void PiwigoTalker::login(const KUrl& url, const QString& name, const QString& passwd)
94{
95    m_job   = 0;
96    m_url   = url;
97    m_state = GE_LOGIN;
98    m_talker_buffer.resize(0);
99
100    QString auth = name + QString(":") + passwd;
101    s_authToken = "Basic " + auth.toUtf8().toBase64();
102
103    QStringList qsl;
104    qsl.append("password=" + passwd);
105    qsl.append("method=pwg.session.login");
106    qsl.append("username=" + name);
107    QString dataParameters = qsl.join("&");
108    QByteArray buffer;
109    buffer.append(dataParameters.toUtf8());
110    m_job = KIO::http_post(m_url, buffer, KIO::HideProgressInfo);
111    m_job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded" );
112    m_job->addMetaData("customHTTPHeader", "Authorization: " + s_authToken );
113
114    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
115            this, SLOT(slotTalkerData(KIO::Job*, const QByteArray&)));
116
117    connect(m_job, SIGNAL(result(KJob *)),
118            this, SLOT(slotResult(KJob *)));
119
120    emit signalBusy(true);
121}
122
123void PiwigoTalker::listAlbums()
124{
125    m_job   = 0;
126    m_state = GE_LISTALBUMS;
127    m_talker_buffer.resize(0);
128
129    QStringList qsl;
130    qsl.append("method=pwg.categories.getList");
131    qsl.append("recursive=true");
132    QString dataParameters = qsl.join("&");
133    QByteArray buffer;
134    buffer.append(dataParameters.toUtf8());
135
136    m_job = KIO::http_post(m_url, buffer, KIO::HideProgressInfo);
137    m_job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded" );
138    m_job->addMetaData("customHTTPHeader", "Authorization: " + s_authToken );
139
140    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
141            this, SLOT(slotTalkerData(KIO::Job*, const QByteArray&)));
142
143    connect(m_job, SIGNAL(result(KJob *)),
144            this, SLOT(slotResult(KJob *)));
145
146    emit signalBusy(true);
147}
148
149bool PiwigoTalker::addPhoto(int albumId,
150                            const QString& photoPath,
151                            const QString& caption,
152                            bool  captionIsTitle,
153                            bool  captionIsDescription,
154                            bool  rescale,
155                            int   maxDim,
156                            int   thumbDim)
157{
158    m_job        = 0;
159    m_state      = GE_CHECKPHOTOEXIST;
160    m_talker_buffer.resize(0);
161
162    m_path = photoPath;
163    m_albumId = albumId;
164    m_md5sum = computeMD5Sum(photoPath);
165
166    kDebug() << photoPath << " " << m_md5sum.toHex();
167
168    QImage image;
169
170    // Check if RAW file.
171    QString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
172    QFileInfo fi(photoPath);
173
174    if (rawFilesExt.toUpper().contains( fi.suffix().toUpper() ))
175        KDcrawIface::KDcraw::loadDcrawPreview(image, photoPath);
176    else
177        image.load(photoPath);
178
179    if (!image.isNull()) {
180        QImage thumbnail = image.scaled(thumbDim, thumbDim, Qt::KeepAspectRatio, Qt::SmoothTransformation);
181        m_thumbpath = KStandardDirs::locateLocal("tmp", "thumb-" + KUrl(photoPath).fileName());
182        thumbnail.save(m_thumbpath, "JPEG", 80);
183        kDebug() << "Thumbnail to temp file: " << m_thumbpath ;
184
185
186        // image file - see if we need to rescale it
187        if (rescale && (image.width() > maxDim || image.height() > maxDim)) {
188            image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation);
189        }
190
191        m_path = KStandardDirs::locateLocal("tmp", KUrl(photoPath).fileName());
192        image.save(m_path, "JPEG", 80);
193        kDebug() << "Resizing and saving to temp file: " << m_path ;
194
195        // Complete name and comment for summary sending
196        m_comment = m_name = caption;
197        m_date = fi.created();
198
199        // Restore all metadata.
200        KExiv2Iface::KExiv2 exiv2Iface;
201
202        if (exiv2Iface.load(photoPath)) {
203            exiv2Iface.setImageProgramId(QString("Kipi-plugins"), QString(kipiplugins_version));
204            exiv2Iface.setImageDimensions(image.size());
205            exiv2Iface.save(m_path);
206            kDebug() << "Comment : " << exiv2Iface.getExifComment();
207            if (exiv2Iface.getExifComment().length()) {
208                if (captionIsTitle) m_name = exiv2Iface.getExifComment();
209                if (captionIsDescription) m_comment = exiv2Iface.getExifComment();
210            }
211            m_date = exiv2Iface.getImageDateTime();
212        } else {
213            kWarning() << "Image " << photoPath << " has no exif data";
214        }
215
216    } else {
217        // Invalid image
218        return false;
219    }
220
221    QStringList qsl;
222    qsl.append("method=pwg.images.exist");
223    qsl.append("md5sum_list=" + m_md5sum.toHex());
224    QString dataParameters = qsl.join("&");
225    QByteArray buffer;
226    buffer.append(dataParameters.toUtf8());
227
228    m_job = KIO::http_post(m_url, buffer, KIO::HideProgressInfo);
229    m_job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded" );
230    m_job->addMetaData("customHTTPHeader", "Authorization: " + s_authToken );
231
232    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
233            this, SLOT(slotTalkerData(KIO::Job*, const QByteArray&)));
234
235    connect(m_job, SIGNAL(result(KJob *)),
236            this, SLOT(slotResult(KJob *)));
237
238    emit signalBusy(true);
239
240    return true;
241}
242
243void PiwigoTalker::cancel()
244{
245    if (m_job) {
246        m_job->kill();
247        m_job = 0;
248    }
249}
250
251void PiwigoTalker::slotTalkerData(KIO::Job*, const QByteArray& data)
252{
253    if (data.isEmpty())
254        return;
255
256    int oldSize = m_talker_buffer.size();
257    m_talker_buffer.resize(oldSize + data.size());
258    memcpy(m_talker_buffer.data() + oldSize, data.data(), data.size());
259}
260
261void PiwigoTalker::slotResult(KJob *job)
262{
263    KIO::Job *tempjob = static_cast<KIO::Job*>(job);
264
265    if (tempjob->error()) {
266        if (m_state == GE_LOGIN) {
267            emit signalLoginFailed(tempjob->errorString());
268            kDebug() << tempjob->errorString();
269        } else {
270            if (m_state == GE_CHECKPHOTOEXIST || m_state == GE_ADDPHOTO || m_state == GE_ADDTHUMB || m_state == GE_ADDPHOTOSUMMARY) {
271                emit signalAddPhotoFailed(tempjob->errorString());
272            } else {
273                tempjob->ui()->setWindow(m_parent);
274                tempjob->ui()->showErrorMessage();
275            }
276        }
277        emit signalBusy(false);
278        return;
279    }
280
281    switch (m_state) {
282    case(GE_LOGIN):
283        parseResponseLogin(m_talker_buffer);
284        break;
285    case(GE_LISTALBUMS):
286        parseResponseListAlbums(m_talker_buffer);
287        break;
288    case(GE_CHECKPHOTOEXIST):
289        parseResponseDoesPhotoExist(m_talker_buffer);
290        break;
291    case(GE_ADDPHOTO):
292        parseResponseAddPhoto(m_talker_buffer);
293        break;
294    case(GE_ADDTHUMB):
295        parseResponseAddThumbnail(m_talker_buffer);
296        break;
297    case(GE_ADDPHOTOSUMMARY):
298        parseResponseAddPhotoSummary(m_talker_buffer);
299        break;
300    }
301
302    tempjob->kill();
303    m_job = 0;
304
305    if (m_state == GE_LOGIN && m_loggedIn) {
306        listAlbums();
307    }
308    emit signalBusy(false);
309}
310
311
312void PiwigoTalker::parseResponseLogin(const QByteArray &data)
313{
314    QXmlStreamReader ts(data);
315    QString line;
316    bool foundResponse = false;
317    m_loggedIn         = false;
318
319    kDebug() << "parseResponseLogin: " << QString(data);
320
321    while (!ts.atEnd()) {
322        ts.readNext();
323
324        if (ts.isStartElement()) {
325            foundResponse = true;
326            if (ts.name() == "rsp" && ts.attributes().value("stat") == "ok") {
327                m_loggedIn = true;
328            }
329        }
330    }
331
332    if (!foundResponse) {
333        emit signalLoginFailed(i18n("Piwigo URL probably incorrect"));
334        return;
335    }
336
337    if (!m_loggedIn) {
338        emit signalLoginFailed(i18n("Incorrect username or password specified"));
339    }
340}
341
342void PiwigoTalker::parseResponseListAlbums(const QByteArray& data)
343{
344    QString str = QString::fromUtf8(data);
345    QXmlStreamReader ts(data);
346    QString line;
347    bool foundResponse = false;
348    bool success       = false;
349
350    typedef QList<GAlbum> GAlbumList;
351    GAlbumList albumList;
352    GAlbumList::iterator iter = albumList.begin();
353
354    kDebug() << "parseResponseListAlbums: " << QString(data);
355
356    while (!ts.atEnd()) {
357        ts.readNext();
358
359        if (ts.isEndElement() && ts.name() == "categories")
360            break;
361
362        if (ts.isStartElement()) {
363            if (ts.name() == "rsp" && ts.attributes().value("stat") == "ok") {
364                foundResponse = true;
365            }
366            if (ts.name() == "categories") {
367                success = true;
368            }
369            if (ts.name() == "category") {
370                GAlbum album;
371                album.ref_num = ts.attributes().value("id").toString().toInt();
372                album.parent_ref_num = -1;
373                album.baseurl = ts.attributes().value("url").toString();
374
375                kDebug() << album.ref_num << " " << album.baseurl << "\n";
376
377                iter = albumList.insert(iter, album);
378            }
379            if (ts.name() == "name") {
380                (*iter).name = ts.readElementText();
381                kDebug() << (*iter).name << "\n";
382            }
383            if (ts.name() == "uppercats") {
384                QString uppercats = ts.readElementText();
385                QStringList catlist = uppercats.split(',');
386                if (catlist.size() > 1 && catlist.at(catlist.size() - 2).toInt() != (*iter).ref_num) {
387                    (*iter).parent_ref_num = catlist.at(catlist.size() - 2).toInt();
388                    kDebug() << (*iter).parent_ref_num << "\n";
389                }
390            }
391        }
392    }
393
394    if (!foundResponse) {
395        emit signalError(i18n("Invalid response received from remote Piwigo"));
396        return;
397    }
398
399    if (!success) {
400        emit signalError(i18n("Failed to list albums"));
401        return;
402    }
403
404    // We need parent albums to come first for rest of the code to work
405    qSort(albumList);
406
407    emit signalAlbums(albumList);
408}
409
410void PiwigoTalker::parseResponseDoesPhotoExist(const QByteArray& data)
411{
412    QString str = QString::fromUtf8(data);
413    QXmlStreamReader ts(data);
414    QString line;
415    bool foundResponse = false;
416    bool success       = false;
417
418    kDebug() << "parseResponseDoesPhotoExist: " << QString(data);
419
420    while (!ts.atEnd()) {
421        ts.readNext();
422
423        if (ts.isStartElement()) {
424            if (ts.name() == "rsp") {
425                foundResponse = true;
426                if (ts.attributes().value("stat") == "ok") success = true;
427                break;
428            }
429        }
430    }
431
432    if (!foundResponse) {
433        emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
434        return;
435    }
436
437    if (!success) {
438        emit signalAddPhotoFailed(i18n("Failed to upload photo"));
439        return;
440    }
441
442    m_state = GE_ADDPHOTO;
443
444    QFile imagefile(m_path);
445    imagefile.open(QIODevice::ReadOnly);
446
447    QStringList qsl;
448    qsl.append("method=pwg.images.addChunk");
449    qsl.append("original_sum=" + m_md5sum.toHex());
450    qsl.append("position=1");
451    qsl.append("type=file");
452    qsl.append("data=" + imagefile.readAll().toBase64().toPercentEncoding());
453    QString dataParameters = qsl.join("&");
454    QByteArray buffer;
455    buffer.append(dataParameters.toUtf8());
456
457    imagefile.close();
458
459    m_job = KIO::http_post(m_url, buffer, KIO::HideProgressInfo);
460    m_job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded" );
461    m_job->addMetaData("customHTTPHeader", "Authorization: " + s_authToken );
462
463    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
464            this, SLOT(slotTalkerData(KIO::Job*, const QByteArray&)));
465
466    connect(m_job, SIGNAL(result(KJob *)),
467            this, SLOT(slotResult(KJob *)));
468}
469
470
471void PiwigoTalker::parseResponseAddPhoto(const QByteArray& data)
472{
473    QString str = QString::fromUtf8(data);
474    QXmlStreamReader ts(data);
475    QString line;
476    bool foundResponse = false;
477    bool success       = false;
478
479    kDebug() << "parseResponseAddPhoto: " << QString(data);
480
481    while (!ts.atEnd()) {
482        ts.readNext();
483
484        if (ts.isStartElement()) {
485            if (ts.name() == "rsp") {
486                foundResponse = true;
487                if (ts.attributes().value("stat") == "ok") success = true;
488                break;
489            }
490        }
491    }
492
493    if (!foundResponse) {
494        emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
495        return;
496    }
497
498    if (!success) {
499        emit signalAddPhotoFailed(i18n("Failed to upload photo"));
500        return;
501    }
502
503    m_state = GE_ADDTHUMB;
504
505    QFile imagefile(m_thumbpath);
506    imagefile.open(QIODevice::ReadOnly);
507
508    QStringList qsl;
509    qsl.append("method=pwg.images.addChunk");
510    qsl.append("original_sum=" + m_md5sum.toHex());
511    qsl.append("position=1");
512    qsl.append("type=thumb");
513    qsl.append("data=" + imagefile.readAll().toBase64().toPercentEncoding());
514    QString dataParameters = qsl.join("&");
515    QByteArray buffer;
516    buffer.append(dataParameters.toUtf8());
517
518    imagefile.close();
519
520    m_job = KIO::http_post(m_url, buffer, KIO::HideProgressInfo);
521    m_job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded" );
522    m_job->addMetaData("customHTTPHeader", "Authorization: " + s_authToken );
523
524    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
525            this, SLOT(slotTalkerData(KIO::Job*, const QByteArray&)));
526
527    connect(m_job, SIGNAL(result(KJob *)),
528            this, SLOT(slotResult(KJob *)));
529
530}
531
532void PiwigoTalker::parseResponseAddThumbnail(const QByteArray& data)
533{
534    QString str = QString::fromUtf8(data);
535    QXmlStreamReader ts(data);
536    QString line;
537    bool foundResponse = false;
538    bool success       = false;
539
540    kDebug() << "parseResponseAddThumbnail: " << QString(data);
541
542    while (!ts.atEnd()) {
543        ts.readNext();
544
545        if (ts.isStartElement()) {
546            if (ts.name() == "rsp") {
547                foundResponse = true;
548                if (ts.attributes().value("stat") == "ok") success = true;
549                break;
550            }
551        }
552    }
553
554    if (!foundResponse) {
555        emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
556        return;
557    }
558
559    if (!success) {
560        emit signalAddPhotoFailed(i18n("Failed to upload photo"));
561        return;
562    }
563
564    m_state = GE_ADDPHOTOSUMMARY;
565
566    QFile imagefile(m_thumbpath);
567    imagefile.open(QIODevice::ReadOnly);
568
569    QStringList qsl;
570    qsl.append("method=pwg.images.add");
571    qsl.append("original_sum=" + m_md5sum.toHex());
572    qsl.append("name=" + m_name.toUtf8().toPercentEncoding());
573    qsl.append("author="); // TODO Retrieve author name from EXIF/IPTC tags
574    qsl.append("categories=" + QString::number(m_albumId));
575    qsl.append("file_sum=" + computeMD5Sum(m_path).toHex());
576    qsl.append("thumbnail_sum=" + computeMD5Sum(m_thumbpath).toHex());
577    qsl.append("date_creation=" + m_date.toString("yyyy:MM:dd").toUtf8().toPercentEncoding());
578    qsl.append("tag_ids=");
579    qsl.append("comment=" + m_comment.toUtf8().toPercentEncoding());
580    QString dataParameters = qsl.join("&");
581    QByteArray buffer;
582    buffer.append(dataParameters.toUtf8());
583
584    imagefile.close();
585
586    m_job = KIO::http_post(m_url, buffer, KIO::HideProgressInfo);
587    m_job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded" );
588    m_job->addMetaData("customHTTPHeader", "Authorization: " + s_authToken );
589
590    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
591            this, SLOT(slotTalkerData(KIO::Job*, const QByteArray&)));
592
593    connect(m_job, SIGNAL(result(KJob *)),
594            this, SLOT(slotResult(KJob *)));
595}
596
597void PiwigoTalker::parseResponseAddPhotoSummary(const QByteArray& data)
598{
599    QString str = QString::fromUtf8(data);
600    QXmlStreamReader ts(data);
601    QString line;
602    bool foundResponse = false;
603    bool success       = false;
604
605    kDebug() << "parseResponseAddPhotoSummary: " << QString(data);
606
607    while (!ts.atEnd()) {
608        ts.readNext();
609
610        if (ts.isStartElement()) {
611            if (ts.name() == "rsp") {
612                foundResponse = true;
613                if (ts.attributes().value("stat") == "ok") success = true;
614                break;
615            }
616        }
617    }
618
619    if (!foundResponse) {
620        emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
621        return;
622    }
623
624    if (!success) {
625        emit signalAddPhotoFailed(i18n("Failed to upload photo"));
626        return;
627    }
628
629    if (!foundResponse) {
630        emit signalAddPhotoFailed(i18n("Invalid response received from remote Piwigo"));
631        return;
632    }
633
634    if (m_path.size())
635        QFile(m_path).remove();
636    if (m_thumbpath.size())
637        QFile(m_thumbpath).remove();
638
639    m_path = "";
640    m_thumbpath = "";
641
642    if (!success) {
643        emit signalAddPhotoFailed(i18n("Failed to upload photo"));
644    } else {
645        emit signalAddPhotoSucceeded();
646    }
647}
648} // namespace KIPIPiwigoExportPlugin
Note: See TracBrowser for help on using the repository browser.