flickrtalker.cpp 39.4 KB
Newer Older
1
2
/* ============================================================
 *
3
 * This file is a part of kipi-plugins project
4
 * http://www.digikam.org
5
6
7
8
 *
 * Date        : 2005-07-07
 * Description : a kipi plugin to export images to Flickr web service
 *
Andi Clemens's avatar
Andi Clemens committed
9
 * Copyright (C) 2005-2009 by Vardhman Jain <vardhman at gmail dot com>
Gilles Caulier's avatar
Gilles Caulier committed
10
 * Copyright (C) 2009-2012 by Gilles Caulier <caulier dot gilles at gmail dot com>
11
 *
12
13
14
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation;
15
 * either version 2, or (at your option) any later version.
16
17
18
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
21
22
23
 * GNU General Public License for more details.
 *
 * ============================================================ */

24
25
#include "flickrtalker.moc"

26
// C++ includes
27
28
29
30
31
32

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <string>

33
// Qt includes
34

Gilles Caulier's avatar
Gilles Caulier committed
35
#include <QByteArray>
Gilles Caulier's avatar
Gilles Caulier committed
36
37
#include <QDomDocument>
#include <QDomElement>
38
#include <QFile>
Gilles Caulier's avatar
Gilles Caulier committed
39
#include <QFileInfo>
40
41
42
#include <QImage>
#include <QMap>
#include <QStringList>
Laurent Montel's avatar
Laurent Montel committed
43
#include <QProgressDialog>
44

45
// KDE includes
46

47
48
#include <kapplication.h>
#include <kcodecs.h>
49
#include <kdebug.h>
50
51
52
53
#include <kio/jobuidelegate.h>
#include <klineedit.h>
#include <klocale.h>
#include <kmessagebox.h>
54
55
#include <kmimetype.h>
#include <kstandarddirs.h>
Gilles Caulier's avatar
Gilles Caulier committed
56
#include <ktoolinvocation.h>
57
#include <kguiitem.h>
Gilles Caulier's avatar
port ++    
Gilles Caulier committed
58

59
// LibKDcraw includes
60

Gilles Caulier's avatar
Gilles Caulier committed
61
#include <libkdcraw/version.h>
62
63
#include <libkdcraw/kdcraw.h>

64
// Local includes
65

Gilles Caulier's avatar
Gilles Caulier committed
66
#include "kpversion.h"
Gilles Caulier's avatar
Gilles Caulier committed
67
#include "kpmetadata.h"
68
#include "mpform.h"
Gilles Caulier's avatar
polish    
Gilles Caulier committed
69
#include "flickritem.h"
70
#include "flickrwindow.h"
71

Gilles Caulier's avatar
Gilles Caulier committed
72
73
using namespace KIPIPlugins;

74
75
76
namespace KIPIFlickrExportPlugin
{

Gilles Caulier's avatar
Gilles Caulier committed
77
FlickrTalker::FlickrTalker(QWidget* const parent, const QString& serviceName)
Gilles Caulier's avatar
polish    
Gilles Caulier committed
78
{
Gilles Caulier's avatar
Gilles Caulier committed
79
80
    m_parent        = parent;
    m_job           = 0;
81
    m_photoSetsList = 0;
82
83

    m_serviceName = serviceName;
Andi Clemens's avatar
Andi Clemens committed
84

85
    if (serviceName == "23")
86
    {
Gilles Caulier's avatar
Gilles Caulier committed
87
88
        m_apiUrl    = QString("http://www.23hq.com/services/rest/");
        m_authUrl   = QString("http://www.23hq.com/services/auth/");
89
        m_uploadUrl = QString("http://www.23hq.com/services/upload/");
90
91
92
93
94
95

        // bshanks: do 23 and flickr really share API keys? or does 23 not need
        // one?
        m_apikey = "49d585bafa0758cb5c58ab67198bf632";
        m_secret = "34b39925e6273ffd";
    }
96
    else if (serviceName == "Zooomr")
97
    {
Gilles Caulier's avatar
Gilles Caulier committed
98
99
        m_apiUrl    = QString("http://api.zooomr.com/services/rest/");
        m_authUrl   = QString("http://www.zooomr.com/services/auth/");
100
101
102
103
        m_uploadUrl = QString("http://upload.zooomr.com/services/upload/");

        m_apikey = "18c8db5ce9ed4e15a7b484136f5080c5";
        m_secret = "1ea4af14e3";
104
105
106
    }
    else
    {
Gilles Caulier's avatar
Gilles Caulier committed
107
108
        m_apiUrl    = QString("http://www.flickr.com/services/rest/");
        m_authUrl   = QString("http://www.flickr.com/services/auth/");
109
        m_uploadUrl = QString("http://api.flickr.com/services/upload/");
110
111
112

        m_apikey = "49d585bafa0758cb5c58ab67198bf632";
        m_secret = "34b39925e6273ffd";
113
    }
Gilles Caulier's avatar
Gilles Caulier committed
114

115
116
117
    /* Initialize selected photo set as empty. */
    m_selectedPhotoSet = FPhotoSet();

Gilles Caulier's avatar
polish    
Gilles Caulier committed
118
119
120
    connect(this, SIGNAL(signalAuthenticate()),
            this, SLOT(slotAuthenticate()));
}
Gilles Caulier's avatar
Gilles Caulier committed
121
122
123
124

FlickrTalker::~FlickrTalker()
{
    if (m_job)
Andi Clemens's avatar
Andi Clemens committed
125
    {
Gilles Caulier's avatar
Gilles Caulier committed
126
        m_job->kill();
Andi Clemens's avatar
Andi Clemens committed
127
128
    }

129
130
    delete m_photoSetsList;

Gilles Caulier's avatar
Gilles Caulier committed
131
132
}

133
/** Compute MD5 signature using url queries keys and values following Flickr notice:
134
135
    http://www.flickr.com/services/api/auth.spec.html
*/
136
QString FlickrTalker::getApiSig(const QString& secret, const KUrl& url)
137
138
139
140
141
142
143
144
{
    QMap<QString, QString> queries = url.queryItems();
    QString compressed(secret);

    // NOTE: iterator QMap iterator will sort alphabetically items based on key values.
    for (QMap<QString, QString>::iterator it = queries.begin() ; it != queries.end(); ++it)
    {
        compressed.append(it.key());
Gilles Caulier's avatar
Gilles Caulier committed
145
        compressed.append(it.value());
146
    }
Andi Clemens's avatar
Andi Clemens committed
147

Gilles Caulier's avatar
Gilles Caulier committed
148
    KMD5 context(compressed.toUtf8());
149
150
151
    return context.hexDigest().data();
}

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// MD5 signature of the request.
/*
QString FlickrTalker::getApiSig(const QString& secret, const QStringList &headers)
{
    QMap<QString, QString> queries = url.queryItems();
    QString compressed(secret);

    // NOTE: iterator QMap iterator will sort alphabetically items based on key values.
    for (QMap<QString, QString>::iterator it = queries.begin() ; it != queries.end(); ++it)
    {
        compressed.append(it.key());
        compressed.append(it.value());
    }

    KMD5 context(compressed.toUtf8());
    return context.hexDigest().data();
}
*/

171
/**get the API sig and send it to the server server should return a frob.
Gilles Caulier's avatar
polish    
Gilles Caulier committed
172
*/
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
173
void FlickrTalker::getFrob()
Gilles Caulier's avatar
Gilles Caulier committed
174
175
176
177
178
179
180
{
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }

181
    KUrl url(m_apiUrl);
182
183
184
185
    url.addQueryItem("method", "flickr.auth.getFrob");
    url.addQueryItem("api_key", m_apikey);
    QString md5 = getApiSig(m_secret, url);
    url.addQueryItem("api_sig", md5);
186
    kDebug() << "Get frob url: " << url;
Gilles Caulier's avatar
Gilles Caulier committed
187

Gilles Caulier's avatar
Gilles Caulier committed
188
    KIO::TransferJob* job = 0;
Andi Clemens's avatar
Andi Clemens committed
189

190
191
192
193
194
195
196
197
198
199
200
201
    if (m_serviceName == "Zooomr")
    {
        // Zooomr redirects the POST at this url to a GET; KIO doesn't follow
        // the redirect.
        job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
    }
    else
    {
        QByteArray tmp;
        job = KIO::http_post(url, tmp, KIO::HideProgressInfo);
        job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded");
    }
202

Laurent Montel's avatar
Laurent Montel committed
203
204
    connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
            this, SLOT(data(KIO::Job*,QByteArray)));
Gilles Caulier's avatar
Gilles Caulier committed
205

Gilles Caulier's avatar
Gilles Caulier committed
206
207
    connect(job, SIGNAL(result(KJob*)),
            this, SLOT(slotResult(KJob*)));
Gilles Caulier's avatar
Gilles Caulier committed
208
209

    m_state = FE_GETFROB;
210
    m_authProgressDlg->setLabelText(i18n("Getting the Frob"));
211
212
    m_authProgressDlg->setMaximum(4);
    m_authProgressDlg->setValue(1);
Gilles Caulier's avatar
Gilles Caulier committed
213
214
    m_job   = job;
    m_buffer.resize(0);
215
    emit signalBusy(true);
Gilles Caulier's avatar
Gilles Caulier committed
216
217
}

Laurent Montel's avatar
Port++    
Laurent Montel committed
218
void FlickrTalker::checkToken(const QString& token)
Gilles Caulier's avatar
Gilles Caulier committed
219
220
221
222
223
224
225
{
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }

226
    KUrl url(m_apiUrl);
227
228
229
230
231
    url.addQueryItem("method", "flickr.auth.checkToken");
    url.addQueryItem("api_key", m_apikey);
    url.addQueryItem("auth_token", token);
    QString md5 = getApiSig(m_secret, url);
    url.addQueryItem("api_sig", md5);
232
    kDebug() << "Check token url: " << url;
Gilles Caulier's avatar
Gilles Caulier committed
233
234
    QByteArray tmp;

Gilles Caulier's avatar
Gilles Caulier committed
235
    KIO::TransferJob* job = 0;
Andi Clemens's avatar
Andi Clemens committed
236

237
238
239
240
241
    if (m_serviceName == "Zooomr")
    {
        // Zooomr redirects the POST at this url to a GET; KIO doesn't follow the
        // redirect
        job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
Gilles Caulier's avatar
polish    
Gilles Caulier committed
242
243
244
    }
    else
    {
245
246
        job = KIO::http_post(url, tmp, KIO::HideProgressInfo);
        job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded");
Gilles Caulier's avatar
polish    
Gilles Caulier committed
247
    }
Gilles Caulier's avatar
Gilles Caulier committed
248

Laurent Montel's avatar
Laurent Montel committed
249
250
    connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
            this, SLOT(data(KIO::Job*,QByteArray)));
Gilles Caulier's avatar
Gilles Caulier committed
251

Gilles Caulier's avatar
Gilles Caulier committed
252
253
    connect(job, SIGNAL(result(KJob*)),
            this, SLOT(slotResult(KJob*)));
Gilles Caulier's avatar
Gilles Caulier committed
254
255

    m_state = FE_CHECKTOKEN;
256
    m_authProgressDlg->setLabelText(i18n("Checking if previous token is still valid"));
257
258
    m_authProgressDlg->setMaximum(4);
    m_authProgressDlg->setValue(1);
Gilles Caulier's avatar
Gilles Caulier committed
259
260
    m_job   = job;
    m_buffer.resize(0);
Andi Clemens's avatar
Andi Clemens committed
261
    emit signalBusy(true);
Gilles Caulier's avatar
Gilles Caulier committed
262
263
264
265
266
267
268
269
270
271
}

void FlickrTalker::slotAuthenticate()
{
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }

272
    KUrl url(m_authUrl);
273
274
275
276
277
    url.addQueryItem("api_key", m_apikey);
    url.addQueryItem("frob", m_frob);
    url.addQueryItem("perms", "write");
    QString md5 = getApiSig(m_secret, url);
    url.addQueryItem("api_sig", md5);
278
    kDebug() << "Authenticate url: " << url;
279

Laurent Montel's avatar
Laurent Montel committed
280
    KToolInvocation::invokeBrowser(url.url());
281
    int valueOk = KMessageBox::questionYesNo(kapp->activeWindow(),
Andi Clemens's avatar
Andi Clemens committed
282
283
284
285
286
                                             i18n("Please follow the instructions in the browser window, then "
                                                  "return to press corresponding button."),
                                             i18n("%1 Service Web Authorization", m_serviceName),
                                             KGuiItem(i18n("I am authenticated"), "dialog-ok"),
                                             KGuiItem(i18n("I am not authenticated"), "dialog-cancel"));
Gilles Caulier's avatar
Gilles Caulier committed
287

Andi Clemens's avatar
Andi Clemens committed
288
    if (valueOk == KMessageBox::Yes)
Gilles Caulier's avatar
Gilles Caulier committed
289
    {
290
        getToken();
291
        m_authProgressDlg->setLabelText(i18n("Authenticating the User on web"));
292
293
        m_authProgressDlg->setMaximum(4);
        m_authProgressDlg->setValue(2);
Gilles Caulier's avatar
polish    
Gilles Caulier committed
294
        emit signalBusy(false);
Gilles Caulier's avatar
Gilles Caulier committed
295
    }
Laurent Montel's avatar
Port++    
Laurent Montel committed
296
    else
Gilles Caulier's avatar
Gilles Caulier committed
297
    {
298
        kDebug() << "User didn't proceed with getToken Authorization, cannot proceed further, aborting";
Gilles Caulier's avatar
polish    
Gilles Caulier committed
299
        cancel();
Gilles Caulier's avatar
Gilles Caulier committed
300
301
302
303
304
305
306
307
308
309
310
    }
}

void FlickrTalker::getToken()
{
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }

311
    KUrl url(m_apiUrl);
312
313
314
315
316
    url.addQueryItem("api_key", m_apikey);
    url.addQueryItem("method", "flickr.auth.getToken");
    url.addQueryItem("frob", m_frob);
    QString md5 = getApiSig(m_secret, url);
    url.addQueryItem("api_sig", md5);
317
    kDebug() << "Get token url: " << url;
318

Gilles Caulier's avatar
Gilles Caulier committed
319
    KIO::TransferJob* job = 0;
Andi Clemens's avatar
Andi Clemens committed
320

321
322
323
324
325
326
327
328
329
330
331
332
    if (m_serviceName == "Zooomr")
    {
        // Zooomr redirects the POST at this url to a GET; KIO doesn't follow
        // the redirect.
        job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
    }
    else
    {
        QByteArray tmp;
        job = KIO::http_post(url, tmp, KIO::HideProgressInfo);
        job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded");
    }
Gilles Caulier's avatar
Gilles Caulier committed
333

Laurent Montel's avatar
Laurent Montel committed
334
335
    connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
            this, SLOT(data(KIO::Job*,QByteArray)));
Gilles Caulier's avatar
Gilles Caulier committed
336

Gilles Caulier's avatar
Gilles Caulier committed
337
338
    connect(job, SIGNAL(result(KJob*)),
            this, SLOT(slotResult(KJob*)));
Gilles Caulier's avatar
Gilles Caulier committed
339
340
341
342

    m_state = FE_GETTOKEN;
    m_job   = job;
    m_buffer.resize(0);
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
343
    emit signalBusy(true);
344
    m_authProgressDlg->setLabelText(i18n("Getting the Token from the server"));
345
346
    m_authProgressDlg->setMaximum(4);
    m_authProgressDlg->setValue(3);
Gilles Caulier's avatar
Gilles Caulier committed
347
}
Gilles Caulier's avatar
polish    
Gilles Caulier committed
348

Gilles Caulier's avatar
Gilles Caulier committed
349
350
void FlickrTalker::listPhotoSets()
{
351
    kDebug() << "List photoset invoked";
352
    KUrl url(m_apiUrl);
353
    url.addQueryItem("auth_token", m_token);
354
355
356
357
    url.addQueryItem("api_key", m_apikey);
    url.addQueryItem("method", "flickr.photosets.getList");
    QString md5 = getApiSig(m_secret, url);
    url.addQueryItem("api_sig", md5);
358
    kDebug() << "List photoset URL" << url;
Gilles Caulier's avatar
polish    
Gilles Caulier committed
359
    QByteArray tmp;
Gilles Caulier's avatar
Gilles Caulier committed
360
    KIO::TransferJob* job = 0;
Andi Clemens's avatar
Andi Clemens committed
361

362
363
364
365
366
367
368
369
370
371
372
    if (m_serviceName == "Zooomr")
    {
        // Zooomr redirects the POST at this url to a GET; KIO doesn't follow
        // the redirect.
        job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
    }
    else
    {
        job = KIO::http_post(url, tmp, KIO::HideProgressInfo);
        job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded");
    }
Gilles Caulier's avatar
Gilles Caulier committed
373

Laurent Montel's avatar
Laurent Montel committed
374
375
    connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
            this, SLOT(data(KIO::Job*,QByteArray)));
Gilles Caulier's avatar
Gilles Caulier committed
376

Laurent Montel's avatar
Laurent Montel committed
377
378
    connect(job, SIGNAL(result(KJob*)),
            this, SLOT(slotResult(KJob*)));
Gilles Caulier's avatar
Gilles Caulier committed
379
380
381
382

    m_state = FE_LISTPHOTOSETS;
    m_job   = job;
    m_buffer.resize(0);
383
    emit signalBusy(true);
Gilles Caulier's avatar
Gilles Caulier committed
384
385
}

386
void FlickrTalker::getPhotoProperty(const QString& method, const QStringList& argList)
Gilles Caulier's avatar
Gilles Caulier committed
387
388
389
390
391
392
393
{
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }

394
    KUrl url(m_apiUrl);
395
396
397
398
    url.addQueryItem("api_key", m_apikey);
    url.addQueryItem("method", method);
    url.addQueryItem("frob", m_frob);

Andi Clemens's avatar
Andi Clemens committed
399
    for (QStringList::const_iterator it = argList.constBegin(); it != argList.constEnd(); ++it)
400
    {
401
        QStringList str = (*it).split('=', QString::SkipEmptyParts);
402
403
404
405
406
        url.addQueryItem(str[0], str[1]);
    }

    QString md5 = getApiSig(m_secret, url);
    url.addQueryItem("api_sig", md5);
407
    kDebug() << "Get photo property url: " << url;
Gilles Caulier's avatar
polish    
Gilles Caulier committed
408
    QByteArray tmp;
Gilles Caulier's avatar
Gilles Caulier committed
409
    KIO::TransferJob* job = 0;
Andi Clemens's avatar
Andi Clemens committed
410

411
412
413
414
415
416
417
418
419
420
421
    if (m_serviceName == "Zooomr")
    {
        // Zooomr redirects the POST at this url to a GET; KIO doesn't follow
        // the redirect.
        job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
    }
    else
    {
        job = KIO::http_post(url, tmp, KIO::HideProgressInfo);
        job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded");
    }
Gilles Caulier's avatar
Gilles Caulier committed
422

Laurent Montel's avatar
Laurent Montel committed
423
424
    connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
            this, SLOT(data(KIO::Job*,QByteArray)));
Gilles Caulier's avatar
Gilles Caulier committed
425

Laurent Montel's avatar
Laurent Montel committed
426
427
    connect(job, SIGNAL(result(KJob*)),
            this, SLOT(slotResult(KJob*)));
Gilles Caulier's avatar
Gilles Caulier committed
428
429
430
431

    m_state = FE_GETPHOTOPROPERTY;
    m_job   = job;
    m_buffer.resize(0);
Andi Clemens's avatar
Andi Clemens committed
432
    emit signalBusy(true);
Gilles Caulier's avatar
polish    
Gilles Caulier committed
433

Andi Clemens's avatar
Andi Clemens committed
434
435
    //  m_authProgressDlg->setLabelText("Getting the Token from the server");
    //  m_authProgressDlg->setProgress(3,4);
Gilles Caulier's avatar
Gilles Caulier committed
436
}
Gilles Caulier's avatar
port ++    
Gilles Caulier committed
437

438
void FlickrTalker::listPhotos(const QString& /*albumName*/)
Gilles Caulier's avatar
Gilles Caulier committed
439
{
Gilles Caulier's avatar
polish    
Gilles Caulier committed
440
    // TODO
Gilles Caulier's avatar
Gilles Caulier committed
441
442
}

Andi Clemens's avatar
Andi Clemens committed
443
void FlickrTalker::createPhotoSet(const QString& /*albumName*/, const QString& albumTitle,
Gilles Caulier's avatar
Gilles Caulier committed
444
                                  const QString& albumDescription, const QString& primaryPhotoId)
Gilles Caulier's avatar
Gilles Caulier committed
445
{
446
447
448
449
450
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }
Gilles Caulier's avatar
Gilles Caulier committed
451

452
    kDebug() << "create photoset invoked";
453
    KUrl url(m_apiUrl);
454
455
456
457
458
459
460
461
    url.addQueryItem("auth_token", m_token);
    url.addQueryItem("api_key", m_apikey);
    url.addQueryItem("method", "flickr.photosets.create");
    url.addQueryItem("title", albumTitle);
    url.addQueryItem("description", albumDescription);
    url.addQueryItem("primary_photo_id", primaryPhotoId);
    QString md5 = getApiSig(m_secret, url);
    url.addQueryItem("api_sig", md5);
462
    kDebug() << "List photo sets url: " << url;
463
    QByteArray tmp;
Gilles Caulier's avatar
Gilles Caulier committed
464
    KIO::TransferJob* job = 0;
Andi Clemens's avatar
Andi Clemens committed
465

466
467
468
469
470
471
472
473
474
475
476
477
478
    if (m_serviceName == "Zooomr")
    {
        // Zooomr redirects the POST at this url to a GET; KIO doesn't follow
        // the redirect (although this function should never get called when
        // using Zooomr).
        job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
    }
    else
    {
        job = KIO::http_post(url, tmp, KIO::HideProgressInfo);
        job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded");
    }

479

Laurent Montel's avatar
Laurent Montel committed
480
481
    connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
            this, SLOT(data(KIO::Job*,QByteArray)));
482

Laurent Montel's avatar
Laurent Montel committed
483
484
    connect(job, SIGNAL(result(KJob*)),
            this, SLOT(slotResult(KJob*)));
485
486
487
488
489
490
491

    m_state = FE_CREATEPHOTOSET;
    m_job   = job;
    m_buffer.resize(0);
    emit signalBusy(true);
}

492
493
void FlickrTalker::addPhotoToPhotoSet(const QString& photoId,
                                      const QString& photoSetId)
Gilles Caulier's avatar
Gilles Caulier committed
494
{
495
    if (m_job)
496
497
498
499
    {
        m_job->kill();
        m_job = 0;
    }
Gilles Caulier's avatar
Gilles Caulier committed
500

501
    kDebug() << "addPhotoToPhotoSet invoked";
502
    KUrl url(m_apiUrl);
503

504
505
506
507
    /* If the photoset id starts with the special string "UNDEFINED_", it means
     * it doesn't exist yet on Flickr and needs to be created. Note that it's
     * not necessary to subsequently add the photo to the photo set, as this
     * is done in the set creation call to Flickr. */
508
    if (photoSetId.startsWith(QLatin1String("UNDEFINED_")))
509
    {
510
        createPhotoSet("", m_selectedPhotoSet.title, m_selectedPhotoSet.description, photoId);
511
512
513
    }
    else
    {
514
        url.addQueryItem("auth_token", m_token);
Gilles Caulier's avatar
Gilles Caulier committed
515

516
        url.addQueryItem("photoset_id", photoSetId);
517

518
        url.addQueryItem("api_key", m_apikey);
519

520
        url.addQueryItem("method", "flickr.photosets.addPhoto");
521

522
        url.addQueryItem("photo_id", photoId);
523

524
525
        QString md5 = getApiSig(m_secret, url);
        url.addQueryItem("api_sig", md5);
526

527
        QByteArray tmp;
528
        kDebug() << "Add photo to Photo set url: " << url;
529
530
        KIO::TransferJob* job = KIO::http_post(url, tmp, KIO::HideProgressInfo);
        job->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded");
531

Laurent Montel's avatar
Laurent Montel committed
532
533
        connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
                this, SLOT(data(KIO::Job*,QByteArray)));
534

Laurent Montel's avatar
Laurent Montel committed
535
536
        connect(job, SIGNAL(result(KJob*)),
                this, SLOT(slotResult(KJob*)));
537

538
539
540
541
542
        m_state = FE_ADDPHOTOTOPHOTOSET;
        m_job   = job;
        m_buffer.resize(0);
        emit signalBusy(true);
    }
Gilles Caulier's avatar
Gilles Caulier committed
543
544
}

Gilles Caulier's avatar
polish    
Gilles Caulier committed
545
bool FlickrTalker::addPhoto(const QString& photoPath, const FPhotoInfo& info,
546
                            bool sendOriginal, bool rescale, int maxDim, int imageQuality)
Gilles Caulier's avatar
Gilles Caulier committed
547
548
549
550
551
552
553
{
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }

554
    KUrl    url(m_uploadUrl);
555
556
557

    // We dont' want to modify url as such, we just used the KURL object for storing the query items.
    KUrl  url2("");
Gilles Caulier's avatar
Gilles Caulier committed
558
    QString path = photoPath;
559
560
    MPForm  form;

561
562
    form.addPair("auth_token", m_token, "text/plain");
    url2.addQueryItem("auth_token", m_token);
Gilles Caulier's avatar
Gilles Caulier committed
563

564
565
    form.addPair("api_key", m_apikey, "text/plain");
    url2.addQueryItem("api_key", m_apikey);
Gilles Caulier's avatar
Gilles Caulier committed
566

567
    QString ispublic = (info.is_public == 1) ? "1" : "0";
568
569
    form.addPair("is_public", ispublic, "text/plain");
    url2.addQueryItem("is_public", ispublic);
Gilles Caulier's avatar
Gilles Caulier committed
570

571
    QString isfamily = (info.is_family == 1) ? "1" : "0";
572
573
    form.addPair("is_family", isfamily, "text/plain");
    url2.addQueryItem("is_family", isfamily);
Gilles Caulier's avatar
Gilles Caulier committed
574

575
    QString isfriend = (info.is_friend == 1) ? "1" : "0";
576
577
    form.addPair("is_friend", isfriend, "text/plain");
    url2.addQueryItem("is_friend", isfriend);
Gilles Caulier's avatar
Gilles Caulier committed
578

579
580
581
582
583
584
585
586
    QString safetyLevel = QString::number(static_cast<int>(info.safety_level));
    form.addPair("safety_level", safetyLevel, "text/plain");
    url2.addQueryItem("safety_level", safetyLevel);

    QString contentType = QString::number(static_cast<int>(info.content_type));
    form.addPair("content_type", contentType, "text/plain");
    url2.addQueryItem("content_type", contentType);

587
    QString tags = "\"" + info.tags.join("\" \"") + "\"";
Andi Clemens's avatar
Andi Clemens committed
588
589

    if (tags.length() > 0)
Gilles Caulier's avatar
polish    
Gilles Caulier committed
590
    {
591
592
        form.addPair("tags", tags, "text/plain");
        url2.addQueryItem("tags", tags);
Gilles Caulier's avatar
Gilles Caulier committed
593
594
595
596
    }

    if (!info.title.isEmpty())
    {
597
598
        form.addPair("title", info.title, "text/plain");
        url2.addQueryItem("title", info.title);
Gilles Caulier's avatar
Gilles Caulier committed
599
600
601
602
    }

    if (!info.description.isEmpty())
    {
603
604
        form.addPair("description", info.description, "text/plain");
        url2.addQueryItem("description", info.description);
Gilles Caulier's avatar
Gilles Caulier committed
605
606
    }

607
608
    QString md5 = getApiSig(m_secret, url2);
    form.addPair("api_sig", md5, "text/plain");
609
610
611
    QImage image;

    // Check if RAW file.
Gilles Caulier's avatar
Gilles Caulier committed
612
    if (KPMetadata::isRawFile(photoPath))
Andi Clemens's avatar
Andi Clemens committed
613
    {
614
        KDcrawIface::KDcraw::loadDcrawPreview(image, photoPath);
Andi Clemens's avatar
Andi Clemens committed
615
    }
616
    else
Andi Clemens's avatar
Andi Clemens committed
617
    {
618
        image.load(photoPath);
Andi Clemens's avatar
Andi Clemens committed
619
    }
620

Gilles Caulier's avatar
Gilles Caulier committed
621
622
    if (!image.isNull())
    {
623
        path = KStandardDirs::locateLocal("tmp", QFileInfo(photoPath).baseName().trimmed() + ".jpg");
624

625
626
627
628
629
630
631
632
633
        if (sendOriginal)
        {
            QFile imgFile(photoPath);
            imgFile.copy(path);
        }
        else
        {
            if (rescale && (image.width() > maxDim || image.height() > maxDim))
                image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio,
Andi Clemens's avatar
Andi Clemens committed
634
635
                                     Qt::SmoothTransformation);

636
637
            image.save(path, "JPEG", imageQuality);
        }
Andi Clemens's avatar
Andi Clemens committed
638

639
640
        // Restore all metadata.

Gilles Caulier's avatar
Gilles Caulier committed
641
        KPMetadata meta;
642

Gilles Caulier's avatar
Gilles Caulier committed
643
        if (meta.load(photoPath))
644
        {
Gilles Caulier's avatar
Gilles Caulier committed
645
            meta.setImageDimensions(image.size());
646
647
648

            // NOTE: see B.K.O #153207: Flickr use IPTC keywords to create Tags in web interface
            //       As IPTC do not support UTF-8, we need to remove it.
Gilles Caulier's avatar
Gilles Caulier committed
649
            meta.removeIptcTag("Iptc.Application2.Keywords", false);
650

Gilles Caulier's avatar
Gilles Caulier committed
651
652
            meta.setImageProgramId(QString("Kipi-plugins"), QString(kipiplugins_version));
            meta.save(path);
Gilles Caulier's avatar
Gilles Caulier committed
653
        }
654
655
        else
        {
656
            kWarning() << "(flickrExport::Image doesn't have metadata)";
657
658
        }

659
        kDebug() << "Resizing and saving to temp file: " << path;
Gilles Caulier's avatar
Gilles Caulier committed
660
661
662
    }

    if (!form.addFile("photo", path))
Andi Clemens's avatar
Andi Clemens committed
663
    {
Gilles Caulier's avatar
Gilles Caulier committed
664
        return false;
Andi Clemens's avatar
Andi Clemens committed
665
    }
Gilles Caulier's avatar
Gilles Caulier committed
666
667
668

    form.finish();

669
    KIO::TransferJob* job = KIO::http_post(url, form.formData(), KIO::HideProgressInfo);
Gilles Caulier's avatar
Gilles Caulier committed
670
671
    job->addMetaData("content-type", form.contentType());

Laurent Montel's avatar
Laurent Montel committed
672
673
    connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
            this, SLOT(data(KIO::Job*,QByteArray)));
Gilles Caulier's avatar
Gilles Caulier committed
674

Laurent Montel's avatar
Laurent Montel committed
675
676
    connect(job, SIGNAL(result(KJob*)),
            this, SLOT(slotResult(KJob*)));
Gilles Caulier's avatar
Gilles Caulier committed
677
678
679
680

    m_state = FE_ADDPHOTO;
    m_job   = job;
    m_buffer.resize(0);
681
    emit signalBusy(true);
Gilles Caulier's avatar
Gilles Caulier committed
682
683
684
    return true;
}

Laurent Montel's avatar
Laurent Montel committed
685
QString FlickrTalker::getUserName() const
Gilles Caulier's avatar
Gilles Caulier committed
686
687
688
689
{
    return m_username;
}

Laurent Montel's avatar
Laurent Montel committed
690
QString FlickrTalker::getUserId() const
Gilles Caulier's avatar
Gilles Caulier committed
691
692
693
694
695
696
697
698
699
700
701
702
{
    return m_userId;
}

void FlickrTalker::cancel()
{
    if (m_job)
    {
        m_job->kill();
        m_job = 0;
    }

Laurent Montel's avatar
Port++    
Laurent Montel committed
703
    if (m_authProgressDlg && !m_authProgressDlg->isHidden())
Andi Clemens's avatar
Andi Clemens committed
704
    {
705
        m_authProgressDlg->hide();
Andi Clemens's avatar
Andi Clemens committed
706
    }
Gilles Caulier's avatar
polish    
Gilles Caulier committed
707
}
Gilles Caulier's avatar
Gilles Caulier committed
708
709
710
711

void FlickrTalker::data(KIO::Job*, const QByteArray& data)
{
    if (data.isEmpty())
Andi Clemens's avatar
Andi Clemens committed
712
    {
Gilles Caulier's avatar
Gilles Caulier committed
713
        return;
Andi Clemens's avatar
Andi Clemens committed
714
    }
Gilles Caulier's avatar
Gilles Caulier committed
715
716
717

    int oldSize = m_buffer.size();
    m_buffer.resize(m_buffer.size() + data.size());
Andi Clemens's avatar
Andi Clemens committed
718
    memcpy(m_buffer.data() + oldSize, data.data(), data.size());
Gilles Caulier's avatar
Gilles Caulier committed
719
}
720

Gilles Caulier's avatar
fix API    
Gilles Caulier committed
721
void FlickrTalker::slotError(const QString& error)
Gilles Caulier's avatar
Gilles Caulier committed
722
723
{
    QString transError;
Laurent Montel's avatar
Fix it    
Laurent Montel committed
724
    int errorNo = error.toInt();
Gilles Caulier's avatar
polish    
Gilles Caulier committed
725

Gilles Caulier's avatar
Gilles Caulier committed
726
727
    switch (errorNo)
    {
Laurent Montel's avatar
Port++    
Laurent Montel committed
728
        case 2:
Gilles Caulier's avatar
Gilles Caulier committed
729
730
            transError = i18n("No photo specified");
            break;
Andi Clemens's avatar
Andi Clemens committed
731

Laurent Montel's avatar
Port++    
Laurent Montel committed
732
        case 3:
Gilles Caulier's avatar
Gilles Caulier committed
733
734
            transError = i18n("General upload failure");
            break;
Andi Clemens's avatar
Andi Clemens committed
735

Laurent Montel's avatar
Port++    
Laurent Montel committed
736
        case 4:
Gilles Caulier's avatar
Gilles Caulier committed
737
738
            transError = i18n("Filesize was zero");
            break;
Andi Clemens's avatar
Andi Clemens committed
739

Laurent Montel's avatar
Port++    
Laurent Montel committed
740
        case 5:
741
            transError = i18n("Filetype was not recognized");
Gilles Caulier's avatar
Gilles Caulier committed
742
            break;
Andi Clemens's avatar
Andi Clemens committed
743

Gilles Caulier's avatar
Gilles Caulier committed
744
745
746
        case 6:
            transError = i18n("User exceeded upload limit");
            break;
Andi Clemens's avatar
Andi Clemens committed
747

Gilles Caulier's avatar
Gilles Caulier committed
748
749
750
        case 96:
            transError = i18n("Invalid signature");
            break;
Andi Clemens's avatar
Andi Clemens committed
751

Gilles Caulier's avatar
Gilles Caulier committed
752
        case 97:
Laurent Montel's avatar
Port++    
Laurent Montel committed
753
            transError = i18n("Missing signature");
Gilles Caulier's avatar
Gilles Caulier committed
754
            break;
Andi Clemens's avatar
Andi Clemens committed
755

Gilles Caulier's avatar
Gilles Caulier committed
756
        case 98:
Laurent Montel's avatar
Port++    
Laurent Montel committed
757
            transError = i18n("Login Failed / Invalid auth token");
Gilles Caulier's avatar
Gilles Caulier committed
758
            break;
Andi Clemens's avatar
Andi Clemens committed
759

Laurent Montel's avatar
Port++    
Laurent Montel committed
760
761
        case 100:
            transError = i18n("Invalid API Key");
Gilles Caulier's avatar
Gilles Caulier committed
762
            break;
Andi Clemens's avatar
Andi Clemens committed
763

Gilles Caulier's avatar
Gilles Caulier committed
764
765
        case 105:
            transError = i18n("Service currently unavailable");
Gilles Caulier's avatar
polish    
Gilles Caulier committed
766
            break;
Andi Clemens's avatar
Andi Clemens committed
767

Gilles Caulier's avatar
Gilles Caulier committed
768
769
        case 108:
            transError = i18n("Invalid Frob");
Gilles Caulier's avatar
polish    
Gilles Caulier committed
770
            break;
Andi Clemens's avatar
Andi Clemens committed
771

Laurent Montel's avatar
Port++    
Laurent Montel committed
772
773
        case 111:
            transError = i18n("Format \"xxx\" not found");
Gilles Caulier's avatar
Gilles Caulier committed
774
            break;
Andi Clemens's avatar
Andi Clemens committed
775

Gilles Caulier's avatar
polish    
Gilles Caulier committed
776
        case 112:
Laurent Montel's avatar
Port++    
Laurent Montel committed
777
            transError = i18n("Method \"xxx\" not found");
Gilles Caulier's avatar
Gilles Caulier committed
778
            break;
Andi Clemens's avatar
Andi Clemens committed
779

Gilles Caulier's avatar
polish    
Gilles Caulier committed
780
        case 114:
Gilles Caulier's avatar
Gilles Caulier committed
781
782
            transError = i18n("Invalid SOAP envelope");
            break;
Andi Clemens's avatar
Andi Clemens committed
783

Gilles Caulier's avatar
polish    
Gilles Caulier committed
784
        case 115:
Gilles Caulier's avatar
Gilles Caulier committed
785
786
            transError = i18n("Invalid XML-RPC Method Call");
            break;
Andi Clemens's avatar
Andi Clemens committed
787

Gilles Caulier's avatar
polish    
Gilles Caulier committed
788
        case 116:
Laurent Montel's avatar
Port++    
Laurent Montel committed
789
            transError = i18n("The POST method is now required for all setters");
Gilles Caulier's avatar
Gilles Caulier committed
790
            break;
Andi Clemens's avatar
Andi Clemens committed
791

Gilles Caulier's avatar
Gilles Caulier committed
792
793
794
795
796
        default:
            transError = i18n("Unknown error");
            break;
    };

797
    KMessageBox::error(kapp->activeWindow(),
Andi Clemens's avatar
Andi Clemens committed
798
                       i18n("Error Occurred: %1\nCannot proceed any further.", transError));
Gilles Caulier's avatar
Gilles Caulier committed
799
800
}

Andi Clemens's avatar
Andi Clemens committed
801
void FlickrTalker::slotResult(KJob* kjob)
Gilles Caulier's avatar
Gilles Caulier committed
802
803
{
    m_job = 0;
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
804
    emit signalBusy(false);
Gilles Caulier's avatar
Gilles Caulier committed
805
    KIO::Job* job = static_cast<KIO::Job*>(kjob);
Gilles Caulier's avatar
Gilles Caulier committed
806

Gilles Caulier's avatar
fix API    
Gilles Caulier committed
807
    if (job->error())
Gilles Caulier's avatar
Gilles Caulier committed
808
    {
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
809
        if (m_state == FE_ADDPHOTO)
Gilles Caulier's avatar
Gilles Caulier committed
810
811
812
        {
            emit signalAddPhotoFailed(job->errorString());
        }
Gilles Caulier's avatar
Gilles Caulier committed
813
        else
Gilles Caulier's avatar
Gilles Caulier committed
814
815
816
817
        {
            job->ui()->setWindow(m_parent);
            job->ui()->showErrorMessage();
        }
Andi Clemens's avatar
Andi Clemens committed
818

Gilles Caulier's avatar
Gilles Caulier committed
819
820
821
        return;
    }

Andi Clemens's avatar
Andi Clemens committed
822
    switch (m_state)
Gilles Caulier's avatar
Gilles Caulier committed
823
    {
Andi Clemens's avatar
Andi Clemens committed
824
        case (FE_LOGIN):
Gilles Caulier's avatar
Gilles Caulier committed
825
826
            //parseResponseLogin(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
827
828

        case (FE_LISTPHOTOSETS):
Gilles Caulier's avatar
Gilles Caulier committed
829
830
            parseResponseListPhotoSets(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
831
832

        case (FE_GETFROB):
Gilles Caulier's avatar
Gilles Caulier committed
833
834
            parseResponseGetFrob(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
835
836

        case (FE_GETTOKEN):
Gilles Caulier's avatar
Gilles Caulier committed
837
838
            parseResponseGetToken(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
839
840

        case (FE_CHECKTOKEN):
Gilles Caulier's avatar
Gilles Caulier committed
841
842
            parseResponseCheckToken(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
843
844

        case (FE_GETAUTHORIZED):
Gilles Caulier's avatar
Gilles Caulier committed
845
846
            //parseResponseGetToken(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
847
848

        case (FE_LISTPHOTOS):
Gilles Caulier's avatar
Gilles Caulier committed
849
850
            parseResponseListPhotos(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
851
852

        case (FE_GETPHOTOPROPERTY):
Gilles Caulier's avatar
Gilles Caulier committed
853
854
            parseResponsePhotoProperty(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
855
856

        case (FE_ADDPHOTO):
Gilles Caulier's avatar
Gilles Caulier committed
857
858
            parseResponseAddPhoto(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
859
860

        case (FE_ADDPHOTOTOPHOTOSET):
861
862
            parseResponseAddPhotoToPhotoSet(m_buffer);
            break;
Andi Clemens's avatar
Andi Clemens committed
863
864

        case (FE_CREATEPHOTOSET):
865
866
            parseResponseCreatePhotoSet(m_buffer);
            break;
Gilles Caulier's avatar
Gilles Caulier committed
867
868
869
    }
}

Gilles Caulier's avatar
fix API    
Gilles Caulier committed
870
void FlickrTalker::parseResponseGetFrob(const QByteArray& data)
Gilles Caulier's avatar
Gilles Caulier committed
871
872
873
{
    bool success = false;
    QString errorString;
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
874
875
    QDomDocument doc("mydocument");

Laurent Montel's avatar
Port++    
Laurent Montel committed
876
    if (!doc.setContent(data))
Gilles Caulier's avatar
Gilles Caulier committed
877
878
879
880
881
    {
        return;
    }

    QDomElement docElem = doc.documentElement();
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
882
    QDomNode node       = docElem.firstChild();
Gilles Caulier's avatar
polish    
Gilles Caulier committed
883

Andi Clemens's avatar
Andi Clemens committed
884
    while (!node.isNull())
Gilles Caulier's avatar
Gilles Caulier committed
885
    {
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
886
        if (node.isElement() && node.nodeName() == "frob")
887
        {
Gilles Caulier's avatar
polish    
Gilles Caulier committed
888
            QDomElement e = node.toElement();    // try to convert the node to an element.
889
            kDebug() << "Frob is" << e.text();
Gilles Caulier's avatar
fix API    
Gilles Caulier committed
890
891
            m_frob        = e.text();            // this is what is obtained from data.
            success       = true;