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