1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2018-07-03
7 * Description : Web Service authentication container.
8 *
9 * Copyright (C) 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
10 *
11 * This program is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General
13 * Public License as published by the Free Software Foundation;
14 * either version 2, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * ============================================================ */
22
23 #include "wsauthentication.h"
24
25 // Qt includes
26
27 #include <QApplication>
28 #include <QMap>
29 #include <QMessageBox>
30 #include <QFile>
31 #include <QFileInfo>
32 #include <QScopedPointer>
33
34 // KDE includes
35
36 #include <klocalizedstring.h>
37
38 // Local includes
39
40 #include "digikam_debug.h"
41 #include "digikam_version.h"
42 #include "dmetadata.h"
43 #include "previewloadthread.h"
44 #include "wstoolutils.h"
45 #include "wstalker.h"
46 #include "dbtalker.h"
47 #include "fbtalker.h"
48 #include "fbnewalbumdlg.h"
49 #include "flickrtalker.h"
50 #include "gptalker.h"
51 #include "gdtalker.h"
52 #include "imgurtalker.h"
53 #include "smugtalker.h"
54
55 namespace DigikamGenericUnifiedPlugin
56 {
57
58 class Q_DECL_HIDDEN WSAuthentication::Private
59 {
60 public:
61
Private()62 explicit Private()
63 : wizard(0),
64 iface(0),
65 talker(0),
66 ws(WSSettings::WebService::FLICKR),
67 albumDlg(0),
68 imagesCount(0),
69 imagesTotal(0)
70 {
71 }
72
73 WSWizard* wizard;
74 DInfoInterface* iface;
75
76 WSTalker* talker;
77
78 WSSettings::WebService ws;
79 QString serviceName;
80
81 WSNewAlbumDialog* albumDlg;
82 QString currentAlbumId;
83 WSAlbum baseAlbum;
84
85 QStringList tmpPath;
86 QString tmpDir;
87 unsigned int imagesCount;
88 unsigned int imagesTotal;
89 QMap<QString, QString> imagesCaption;
90
91 QList<QUrl> transferQueue;
92 };
93
WSAuthentication(QWidget * const parent,DInfoInterface * const iface)94 WSAuthentication::WSAuthentication(QWidget* const parent, DInfoInterface* const iface)
95 : d(new Private())
96 {
97 d->wizard = dynamic_cast<WSWizard*>(parent);
98
99 if (d->wizard)
100 {
101 d->iface = d->wizard->iface();
102 }
103 else
104 {
105 d->iface = iface;
106 }
107
108 /* --------------------
109 * Temporary path to store images before uploading
110 */
111
112 d->tmpPath.clear();
113 d->tmpDir = WSToolUtils::makeTemporaryDir(d->serviceName.toUtf8().data()).absolutePath() + QLatin1Char('/');
114 }
115
~WSAuthentication()116 WSAuthentication::~WSAuthentication()
117 {
118 slotCancel();
119 delete d;
120 }
121
createTalker(WSSettings::WebService ws,const QString & serviceName)122 void WSAuthentication::createTalker(WSSettings::WebService ws, const QString& serviceName)
123 {
124 d->ws = ws;
125 d->serviceName = serviceName;
126 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "create " << serviceName << "talker";
127
128 switch (ws)
129 {
130 case WSSettings::WebService::FLICKR:
131 //d->talker = new DigikamGenericFlickrPlugin::FlickrTalker(d->wizard, serviceName, d->iface);
132 break;
133 case WSSettings::WebService::DROPBOX:
134 //d->talker = new DigikamGenericDropBoxPlugin::DBTalker(d->wizard);
135 break;
136 case WSSettings::WebService::IMGUR:
137 //d->talker = new DigikamGenericImgUrPlugin::ImgurTalker(d->wizard);
138 break;
139 case WSSettings::WebService::FACEBOOK:
140 //d->albumDlg = new DigikamGenericFaceBookPlugin::FbNewAlbumDlg(d->wizard, d->serviceName);
141 //d->talker = new DigikamGenericFaceBookPlugin::FbTalker(d->wizard, d->albumDlg);
142 break;
143 case WSSettings::WebService::SMUGMUG:
144 //d->talker = new DigikamGenericSmugMugPlugin::SmugTalker(d->iface, d->wizard);
145 break;
146 case WSSettings::WebService::GDRIVE:
147 //d->talker = new DigikamGenericGoogleServicesPlugin::GDTalker(d->wizard);
148 break;
149 case WSSettings::WebService::GPHOTO:
150 //d->talker = new DigikamGenericGoogleServicesPlugin::GPTalker(d->wizard);
151 break;
152 }
153
154 connect(d->talker, SIGNAL(signalOpenBrowser(QUrl)),
155 this, SIGNAL(signalOpenBrowser(QUrl)));
156
157 connect(d->talker, SIGNAL(signalCloseBrowser()),
158 this, SIGNAL(signalCloseBrowser()));
159
160 connect(d->talker, SIGNAL(signalAuthenticationComplete(bool)),
161 this, SIGNAL(signalAuthenticationComplete(bool)));
162
163 connect(this, SIGNAL(signalResponseTokenReceived(QMap<QString,QString>)),
164 d->talker, SLOT(slotResponseTokenReceived(QMap<QString,QString>)));
165
166 connect(d->talker, SIGNAL(signalCreateAlbumDone(int,QString,QString)),
167 this, SIGNAL(signalCreateAlbumDone(int,QString,QString)));
168
169 connect(d->talker, SIGNAL(signalListAlbumsDone(int,QString,QList<WSAlbum>)),
170 this, SLOT(slotListAlbumsDone(int,QString,QList<WSAlbum>)));
171
172 connect(d->talker, SIGNAL(signalAddPhotoDone(int,QString)),
173 this, SLOT(slotAddPhotoDone(int,QString)));
174 }
175
cancelTalker()176 void WSAuthentication::cancelTalker()
177 {
178 if (d->talker)
179 {
180 d->talker->cancel();
181 }
182 }
183
webserviceName()184 QString WSAuthentication::webserviceName()
185 {
186 return d->serviceName;
187 }
188
authenticate()189 void WSAuthentication::authenticate()
190 {
191 d->talker->authenticate();
192 }
193
reauthenticate()194 void WSAuthentication::reauthenticate()
195 {
196 d->talker->reauthenticate();
197 }
198
authenticated() const199 bool WSAuthentication::authenticated() const
200 {
201 return d->talker->linked();
202 }
203
parseTreeFromListAlbums(const QList<WSAlbum> & albumsList,QMap<QString,AlbumSimplified> & albumTree,QStringList & rootAlbums)204 void WSAuthentication::parseTreeFromListAlbums(const QList <WSAlbum>& albumsList,
205 QMap<QString, AlbumSimplified>& albumTree,
206 QStringList& rootAlbums)
207 {
208 foreach (const WSAlbum& album, albumsList)
209 {
210 if (albumTree.contains(album.id))
211 {
212 albumTree[album.id].title = album.title;
213 albumTree[album.id].uploadable = album.uploadable;
214 }
215 else
216 {
217 AlbumSimplified item(album.title, album.uploadable);
218 albumTree[album.id] = item;
219 }
220
221 if (album.isRoot)
222 {
223 rootAlbums << album.id;
224 }
225 else
226 {
227 if (albumTree.contains(album.parentID))
228 {
229 albumTree[album.parentID].childrenIDs << album.id;
230 }
231 else
232 {
233 AlbumSimplified parentAlbum;
234 parentAlbum.childrenIDs << album.id;
235 albumTree[album.parentID] = parentAlbum;
236 }
237 }
238 }
239 }
240
getImageCaption(const QString & fileName)241 QString WSAuthentication::getImageCaption(const QString& fileName)
242 {
243 DItemInfo info(d->iface->itemInfo(QUrl::fromLocalFile(fileName)));
244
245 // If webservice doesn't support image titles, include it in descriptions if needed.
246
247 QStringList descriptions = QStringList() << info.title() << info.comment();
248 descriptions.removeAll(QLatin1String(""));
249
250 return descriptions.join(QLatin1String("\n\n"));
251 }
252
prepareForUpload()253 void WSAuthentication::prepareForUpload()
254 {
255 d->transferQueue = d->wizard->settings()->inputImages;
256 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "prepareForUpload invoked";
257
258 if (d->transferQueue.isEmpty())
259 {
260 emit signalMessage(QLatin1String("transferQueue is empty"), true);
261 return;
262 }
263
264 d->currentAlbumId = d->wizard->settings()->currentAlbumId;
265 d->imagesTotal = d->transferQueue.count();
266 d->imagesCount = 0;
267 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "upload request got album id from widget: " << d->currentAlbumId;
268
269 if (d->wizard->settings()->imagesChangeProp)
270 {
271 foreach (const QUrl& imgUrl, d->transferQueue)
272 {
273 QString imgPath = imgUrl.toLocalFile();
274 QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage();
275
276 if (image.isNull())
277 {
278 image.load(imgPath);
279 }
280
281 if (image.isNull())
282 {
283 emit d->talker->signalAddPhotoDone(666, i18n("Cannot open image at %1\n", imgPath));
284 return;
285 }
286
287 // get temporary file name
288 d->tmpPath << d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + d->wizard->settings()->format();
289
290 // rescale image if requested
291 int maxDim = d->wizard->settings()->imageSize;
292
293 if (image.width() > maxDim || image.height() > maxDim)
294 {
295 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing to " << maxDim;
296 image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio,
297 Qt::SmoothTransformation);
298 }
299
300 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Saving to temp file: " << d->tmpPath.last();
301 image.save(d->tmpPath.last(), "JPEG", d->wizard->settings()->imageCompression);
302
303 // copy meta data to temporary image and get caption for image
304 QScopedPointer<DMetadata> meta(new DMetadata);
305 QString caption = QLatin1String("");
306
307 if (meta->load(imgPath))
308 {
309 meta->setItemDimensions(image.size());
310 meta->setItemOrientation(MetaEngine::ORIENTATION_NORMAL);
311 meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
312 meta->save(d->tmpPath.last(), true);
313 caption = getImageCaption(imgPath);
314 }
315
316 d->imagesCaption[imgPath] = caption;
317 }
318 }
319 }
320
numberItemsUpload()321 unsigned int WSAuthentication::numberItemsUpload()
322 {
323 return d->imagesTotal;
324 }
325
uploadNextPhoto()326 void WSAuthentication::uploadNextPhoto()
327 {
328 if (d->transferQueue.isEmpty())
329 {
330 emit signalDone();
331 return;
332 }
333
334 /*
335 * This comparison is a little bit complicated and may seem unnecessary, but it will be useful later
336 * when we will be able to choose to change or not image properties for EACH image.
337 */
338 QString imgPath = d->transferQueue.first().toLocalFile();
339 QString tmpPath = d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + d->wizard->settings()->format();
340
341 if (!d->tmpPath.isEmpty() && tmpPath == d->tmpPath.first())
342 {
343 d->talker->addPhoto(tmpPath, d->currentAlbumId, d->imagesCaption[imgPath]);
344 d->tmpPath.removeFirst();
345 }
346 else
347 {
348 d->talker->addPhoto(imgPath, d->currentAlbumId, d->imagesCaption[imgPath]);
349 }
350 }
351
startTransfer()352 void WSAuthentication::startTransfer()
353 {
354 uploadNextPhoto();
355 }
356
slotCancel()357 void WSAuthentication::slotCancel()
358 {
359 // First we cancel talker
360 cancelTalker();
361
362 // Then the folder containing all temporary photos to upload will be removed after all.
363 QDir tmpDir(d->tmpDir);
364 if (tmpDir.exists())
365 {
366 tmpDir.removeRecursively();
367 }
368
369 emit signalProgress(0);
370 }
371
slotNewAlbumRequest()372 void WSAuthentication::slotNewAlbumRequest()
373 {
374 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot create New Album";
375
376 if (d->albumDlg->exec() == QDialog::Accepted)
377 {
378 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Calling New Album method";
379 d->talker->createNewAlbum();
380 }
381 }
382
slotListAlbumsRequest()383 void WSAuthentication::slotListAlbumsRequest()
384 {
385 d->talker->listAlbums();
386 }
387
slotListAlbumsDone(int errCode,const QString & errMsg,const QList<WSAlbum> & albumsList)388 void WSAuthentication::slotListAlbumsDone(int errCode, const QString& errMsg, const QList<WSAlbum>& albumsList)
389 {
390 QString albumDebug = QLatin1String("");
391
392 foreach (const WSAlbum &album, albumsList)
393 {
394 albumDebug.append(QString::fromLatin1("%1: %2\n").arg(album.id).arg(album.title));
395 }
396
397 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received albums (errCode = " << errCode << ", errMsg = "
398 << errMsg << "): " << albumDebug;
399
400 if (errCode != 0)
401 {
402 QMessageBox::critical(QApplication::activeWindow(),
403 i18n("%1 Call Failed", d->serviceName),
404 i18n("Code: %1. %2", errCode, errMsg));
405 return;
406 }
407
408 QMap<QString, AlbumSimplified> albumTree;
409 QStringList rootAlbums;
410 parseTreeFromListAlbums(albumsList, albumTree, rootAlbums);
411
412 emit signalListAlbumsDone(albumTree, rootAlbums, QLatin1String(""));
413 }
414
slotAddPhotoDone(int errCode,const QString & errMsg)415 void WSAuthentication::slotAddPhotoDone(int errCode, const QString& errMsg)
416 {
417 if (errCode == 0)
418 {
419 emit signalMessage(QDir::toNativeSeparators(d->transferQueue.first().toLocalFile()), false);
420 d->transferQueue.removeFirst();
421
422 d->imagesCount++;
423 emit signalProgress(d->imagesCount);
424 }
425 else
426 {
427 if (QMessageBox::question(d->wizard, i18n("Uploading Failed"),
428 i18n("Failed to upload photo: %1\n"
429 "Do you want to continue?", errMsg))
430 != QMessageBox::Yes)
431 {
432 d->transferQueue.clear();
433 return;
434 }
435 }
436
437 uploadNextPhoto();
438 }
439
440 } // namespace DigikamGenericUnifiedPlugin
441