1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2018-07-08
7 * Description : Base class for web service talkers.
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 "wstalker.h"
24
25 // Qt includes
26
27 #include <QApplication>
28 #include <QMessageBox>
29 #include <QObject>
30 #include <QDateTime>
31
32 // KDE includes
33
34 #include <klocalizedstring.h>
35
36 // Local includes
37
38 #include "digikam_debug.h"
39 #include "o0globals.h"
40 #include "wstoolutils.h"
41
42 using namespace Digikam;
43
44 namespace Digikam
45 {
46
operator <(const WSAlbum & first,const WSAlbum & second)47 bool operator< (const WSAlbum& first, const WSAlbum& second)
48 {
49 return first.title < second.title;
50 }
51
52 } // namespace Digikam
53
54 namespace DigikamGenericUnifiedPlugin
55 {
56
WSTalker(QWidget * const parent)57 WSTalker::WSTalker(QWidget* const parent)
58 : QObject(parent),
59 m_netMngr(new QNetworkAccessManager(this)),
60 m_reply(0),
61 m_state(WSTalker::DEFAULT),
62 m_settings(0),
63 m_store(0),
64 m_userName(QString()),
65 m_wizard(0)
66 {
67 m_wizard = dynamic_cast<WSWizard*>(parent);
68
69 if (m_wizard != nullptr)
70 {
71 m_settings = m_wizard->oauthSettings();
72 m_store = m_wizard->oauthSettingsStore();
73 m_userName = m_wizard->settings()->userName;
74
75 connect(this, SIGNAL(signalBusy(bool)),
76 m_wizard, SLOT(slotBusy(bool)));
77 }
78 else
79 {
80 m_settings = WSToolUtils::getOauthSettings(parent);
81 m_store = new O0SettingsStore(m_settings, QLatin1String(O2_ENCRYPTION_KEY), this);
82 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parent of talker is not an instance of WSWizard";
83 }
84
85 connect(m_netMngr, SIGNAL(finished(QNetworkReply*)),
86 this, SLOT(slotFinished(QNetworkReply*)));
87 }
88
~WSTalker()89 WSTalker::~WSTalker()
90 {
91 if (m_reply)
92 {
93 m_reply->abort();
94 }
95
96 delete m_reply;
97 delete m_netMngr;
98
99 /* Verify if m_settings is initialized by wstalker constructor or it is already initialized in wizard.
100 * if not by wizard, we have to delete it.
101 */
102 if (!m_wizard)
103 {
104 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "delete m_settings";
105 delete m_settings;
106 delete m_store;
107 }
108 }
109
cancel()110 void WSTalker::cancel()
111 {
112 if (m_reply)
113 {
114 m_reply->abort();
115 m_reply = 0;
116 }
117
118 emit signalBusy(false);
119 }
120
getUserID(const QString & userName)121 QString WSTalker::getUserID(const QString& userName)
122 {
123 QString userID;
124
125 if (!userName.isEmpty())
126 {
127 m_settings->beginGroup(m_store->groupKey());
128 m_settings->beginGroup(QLatin1String("users"));
129 userID = m_settings->value(userName).toString();
130 m_settings->endGroup();
131 m_settings->endGroup();
132 }
133
134 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ID of user " << userName << " : " << userID;
135 return userID;
136 }
137
link()138 void WSTalker::link()
139 {
140 }
141
unlink()142 void WSTalker::unlink()
143 {
144 }
145
linked() const146 bool WSTalker::linked() const
147 {
148 return false;
149 }
150
authenticate()151 void WSTalker::authenticate()
152 {
153 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "username chosen: " << m_userName;
154 bool authenticateValide = loadUserAccount(m_userName);
155
156 /* If user account already exists and doesn't expire yet (authenticateValide == true), linking to his account
157 * Otherwise, unlink() current account and link to new account
158 */
159 if (authenticateValide)
160 {
161 link();
162 }
163 else
164 {
165 reauthenticate();
166 }
167 }
168
reauthenticate()169 void WSTalker::reauthenticate()
170 {
171 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "reauthenticate";
172
173 unlink();
174
175 // Wait until user account is unlinked completely
176 while(linked());
177
178 link();
179 }
180
getUserAccountInfo(const QString & userName)181 QMap<QString, QVariant> WSTalker::getUserAccountInfo(const QString& userName)
182 {
183 QString userID = getUserID(userName);
184
185 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getUserAccountInfo with userID: " << userID;
186
187 QMap<QString, QVariant> map;
188
189 if (userID.isEmpty())
190 {
191 return map;
192 }
193
194 m_settings->beginGroup(m_store->groupKey());
195 m_settings->beginGroup(userID);
196 QStringList keys = m_settings->allKeys();
197
198 foreach (const QString& key, keys)
199 {
200 QVariant value = m_settings->value(key);
201 map.insert(key, value);
202 }
203
204 m_settings->endGroup();
205 m_settings->endGroup();
206
207 return map;
208 }
209
saveUserAccount(const QString & userName,const QString & userID,long long int expire,const QString & accessToken,const QString & refreshToken)210 void WSTalker::saveUserAccount(const QString& userName,
211 const QString& userID,
212 long long int expire,
213 const QString& accessToken,
214 const QString& refreshToken)
215 {
216 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "saveUserAccount with username : " << userName << ", userID: " << userID;
217
218 if (userID.isEmpty())
219 {
220 return;
221 }
222
223 m_settings->beginGroup(m_store->groupKey());
224
225 m_settings->beginGroup(QLatin1String("users"));
226 m_settings->setValue(userName, userID);
227 m_settings->endGroup();
228
229 m_settings->beginGroup(userID);
230 m_settings->setValue(QLatin1String("expiration_time"), expire);
231 m_settings->setValue(QLatin1String("access_token"), accessToken);
232 m_settings->setValue(QLatin1String("refresh_token"), refreshToken);
233 m_settings->endGroup();
234
235 m_settings->endGroup();
236
237 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "current " << QDateTime::currentMSecsSinceEpoch() / 1000;
238 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "expire " << expire;
239 }
240
loadUserAccount(const QString & userName)241 bool WSTalker::loadUserAccount(const QString& userName)
242 {
243 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "loadUserAccount with user name : " << userName;
244
245 /* User logins using new account, return false so that we can unlink() current account,
246 * before link() to new account
247 */
248 if (userName.isEmpty())
249 {
250 return false;
251 }
252
253 QMap<QString, QVariant> map = getUserAccountInfo(userName);
254
255 /*
256 * if getUserAccountInfo(userName) return empty with a non empty userName, there must
257 * be some kind of errors. So, the condition below is a security check, which assures
258 * user to relogin anyway.
259 *
260 * However, if it happens, INSPECTATION is obligated!!!
261 */
262 if (map.isEmpty())
263 {
264 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "WARNING: Something strange happens with getUserAccountInfo";
265 return false;
266 }
267
268 QString expire = map[QLatin1String("expiration_time")].toString();
269 QString accessToken = map[QLatin1String("access_token")].toString();
270 QString refreshToken = map[QLatin1String("refresh_token")].toString();
271
272 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "expired moment : " << expire;
273 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "current time : " << QDateTime::currentMSecsSinceEpoch() / 1000;
274 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "access_token: " << accessToken;
275
276 /* If access token is not expired yet, retrieve all tokens and return true so that we can link()
277 * directly to new account.
278 * Otherwise, return false so that user can relogin.
279 */
280
281 if (expire.toLongLong() > QDateTime::currentMSecsSinceEpoch() / 1000)
282 {
283 resetTalker(expire, accessToken, refreshToken);
284 return true;
285 }
286
287 return false;
288
289 }
290
resetTalker(const QString &,const QString &,const QString &)291 void WSTalker::resetTalker(const QString& /*expire*/, const QString& /*accessToken*/, const QString& /*refreshToken*/)
292 {
293 }
294
getLoggedInUser()295 void WSTalker::getLoggedInUser()
296 {
297 }
298
listAlbums(long long)299 void WSTalker::listAlbums(long long /*userID*/)
300 {
301 }
302
createNewAlbum()303 void WSTalker::createNewAlbum()
304 {
305 }
306
addPhoto(const QString &,const QString &,const QString &)307 void WSTalker::addPhoto(const QString& /*imgPath*/, const QString& /*albumID*/, const QString& /*caption*/)
308 {
309 }
310
removeUserAccount(const QString & userName)311 void WSTalker::removeUserAccount(const QString& userName)
312 {
313 QString userID = getUserID(userName);
314
315 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "removeUserAccount with userID: " << userID;
316
317 if (userID.isEmpty())
318 {
319 return;
320 }
321
322 m_settings->beginGroup(m_store->groupKey());
323
324 m_settings->beginGroup(userID);
325 m_settings->remove(QString());
326 m_settings->endGroup();
327
328 m_settings->beginGroup(QLatin1String("users"));
329 m_settings->remove(userName);
330 m_settings->endGroup();
331
332 m_settings->endGroup();
333 }
334
removeAllAccounts()335 void WSTalker::removeAllAccounts()
336 {
337 m_settings->beginGroup(m_store->groupKey());
338 m_settings->remove(QString());
339 m_settings->endGroup();
340 }
341
sortAlbumsList(QList<WSAlbum> & albumsList)342 void WSTalker::sortAlbumsList(QList<WSAlbum>& albumsList)
343 {
344 std::sort(albumsList.begin(), albumsList.end());
345 }
346
347 /*
348 * saveUserAccount(...) must be called inside this method when it is reimplemented
349 * in derived class, because saveUserAccount(...) can be called only in derived class.
350 */
authenticationDone(int errCode,const QString & errMsg)351 void WSTalker::authenticationDone(int errCode, const QString& errMsg)
352 {
353 if (errCode != 0)
354 {
355 QMessageBox::critical(QApplication::activeWindow(),
356 i18n("Error"),
357 i18n("Code: %1. %2", errCode, errMsg));
358 }
359
360 emit signalBusy(false);
361 }
362
parseResponseGetLoggedInUser(const QByteArray &)363 void WSTalker::parseResponseGetLoggedInUser(const QByteArray& /*data*/)
364 {
365 }
366
parseResponseListAlbums(const QByteArray &)367 void WSTalker::parseResponseListAlbums(const QByteArray& /*data*/)
368 {
369 }
370
parseResponseCreateAlbum(const QByteArray &)371 void WSTalker::parseResponseCreateAlbum(const QByteArray& /*data*/)
372 {
373 }
374
parseResponseAddPhoto(const QByteArray &)375 void WSTalker::parseResponseAddPhoto(const QByteArray& /*data*/)
376 {
377 }
378
slotFinished(QNetworkReply * reply)379 void WSTalker::slotFinished(QNetworkReply* reply)
380 {
381 if (reply != m_reply)
382 {
383 return;
384 }
385
386 m_reply = 0;
387
388 if (reply->error() != QNetworkReply::NoError)
389 {
390 qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->error() << " text :"<< QLatin1String(reply->readAll());
391 authenticationDone(reply->error(), reply->errorString());
392 reply->deleteLater();
393 return;
394 }
395
396 QByteArray buffer = reply->readAll();
397
398 switch (m_state)
399 {
400 case WSTalker::GETUSER :
401 parseResponseGetLoggedInUser(buffer);
402 authenticationDone(0, QLatin1String(""));
403 break;
404 case WSTalker::LISTALBUMS :
405 parseResponseListAlbums(buffer);
406 break;
407 case WSTalker::CREATEALBUM :
408 parseResponseCreateAlbum(buffer);
409 break;
410 case WSTalker::ADDPHOTO :
411 parseResponseAddPhoto(buffer);
412 break;
413 case WSTalker::DEFAULT :
414 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotFinished at state = default";
415 break;
416 }
417
418 reply->deleteLater();
419 }
420
slotOpenBrowser(const QUrl & url)421 void WSTalker::slotOpenBrowser(const QUrl& url)
422 {
423 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser...";
424 emit signalOpenBrowser(url);
425 }
426
slotCloseBrowser()427 void WSTalker::slotCloseBrowser()
428 {
429 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Close Browser...";
430 emit signalCloseBrowser();
431 }
432
slotLinkingFailed()433 void WSTalker::slotLinkingFailed()
434 {
435 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK fail";
436 authenticationDone(-1, QLatin1String("Account link failed."));
437
438 emit signalBusy(false);
439 emit signalAuthenticationComplete(linked());
440 }
441
slotLinkingSucceeded()442 void WSTalker::slotLinkingSucceeded()
443 {
444 if (!linked())
445 {
446 emit signalBusy(false);
447 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK ok";
448
449 return;
450 }
451
452 // Get user account information
453 getLoggedInUser();
454
455 emit signalAuthenticationComplete(linked());
456 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK ok";
457 }
458
slotResponseTokenReceived(const QMap<QString,QString> &)459 void WSTalker::slotResponseTokenReceived(const QMap<QString, QString>& /*rep*/)
460 {
461 }
462
463 } // namespace DigikamGenericUnifiedPlugin
464