File indexing completed on 2025-01-05 03:53:45

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2018-07-08
0007  * Description : Base class for web service talkers.
0008  *
0009  * SPDX-FileCopyrightText: 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "wstalker.h"
0016 
0017 // Qt includes
0018 
0019 #include <QApplication>
0020 #include <QMessageBox>
0021 #include <QObject>
0022 #include <QDateTime>
0023 
0024 // KDE includes
0025 
0026 #include <klocalizedstring.h>
0027 
0028 // Local includes
0029 
0030 #include "digikam_debug.h"
0031 #include "o0globals.h"
0032 #include "wstoolutils.h"
0033 
0034 using namespace Digikam;
0035 
0036 namespace Digikam
0037 {
0038 
0039 bool operator< (const WSAlbum& first, const WSAlbum& second)
0040 {
0041     return first.title < second.title;
0042 }
0043 
0044 } // namespace Digikam
0045 
0046 namespace DigikamGenericUnifiedPlugin
0047 {
0048 
0049 WSTalker::WSTalker(QWidget* const parent)
0050     : QObject(parent),
0051       m_netMngr(new QNetworkAccessManager(this)),
0052       m_reply(0),
0053       m_state(WSTalker::DEFAULT),
0054       m_settings(0),
0055       m_store(0),
0056       m_userName(QString()),
0057       m_wizard(0)
0058 {
0059     m_wizard = dynamic_cast<WSWizard*>(parent);
0060 
0061     if (m_wizard != nullptr)
0062     {
0063         m_settings = m_wizard->oauthSettings();
0064         m_store    = m_wizard->oauthSettingsStore();
0065         m_userName = m_wizard->settings()->userName;
0066 
0067         connect(this, SIGNAL(signalBusy(bool)),
0068                 m_wizard, SLOT(slotBusy(bool)));
0069     }
0070     else
0071     {
0072         m_settings  = WSToolUtils::getOauthSettings(parent);
0073         m_store     = new O0SettingsStore(m_settings, QLatin1String(O2_ENCRYPTION_KEY), this);
0074         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parent of talker is not an instance of WSWizard";
0075     }
0076 
0077     connect(m_netMngr, SIGNAL(finished(QNetworkReply*)),
0078             this, SLOT(slotFinished(QNetworkReply*)));
0079 }
0080 
0081 WSTalker::~WSTalker()
0082 {
0083     if (m_reply)
0084     {
0085         m_reply->abort();
0086     }
0087 
0088     delete m_reply;
0089     delete m_netMngr;
0090 
0091     /* Verify if m_settings is initialized by wstalker constructor or it is already initialized in wizard.
0092      * if not by wizard, we have to delete it.
0093      */
0094     if (!m_wizard)
0095     {
0096         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "delete m_settings";
0097         delete m_settings;
0098         delete m_store;
0099     }
0100 }
0101 
0102 void WSTalker::cancel()
0103 {
0104     if (m_reply)
0105     {
0106         m_reply->abort();
0107         m_reply = 0;
0108     }
0109 
0110     Q_EMIT signalBusy(false);
0111 }
0112 
0113 QString WSTalker::getUserID(const QString& userName)
0114 {
0115     QString userID;
0116 
0117     if (!userName.isEmpty())
0118     {
0119         m_settings->beginGroup(m_store->groupKey());
0120         m_settings->beginGroup(QLatin1String("users"));
0121         userID = m_settings->value(userName).toString();
0122         m_settings->endGroup();
0123         m_settings->endGroup();
0124     }
0125 
0126     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ID of user " << userName << " : " << userID;
0127     return userID;
0128 }
0129 
0130 void WSTalker::link()
0131 {
0132 }
0133 
0134 void WSTalker::unlink()
0135 {
0136 }
0137 
0138 bool WSTalker::linked() const
0139 {
0140     return false;
0141 }
0142 
0143 void WSTalker::authenticate()
0144 {
0145     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "username chosen: " << m_userName;
0146     bool authenticateValide = loadUserAccount(m_userName);
0147 
0148     /* If user account already exists and doesn't expire yet (authenticateValide == true), linking to his account
0149      * Otherwise, unlink() current account and link to new account
0150      */
0151     if (authenticateValide)
0152     {
0153         link();
0154     }
0155     else
0156     {
0157         reauthenticate();
0158     }
0159 }
0160 
0161 void WSTalker::reauthenticate()
0162 {
0163     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "reauthenticate";
0164 
0165     unlink();
0166 
0167     // Wait until user account is unlinked completely
0168     while(linked());
0169 
0170     link();
0171 }
0172 
0173 QMap<QString, QVariant> WSTalker::getUserAccountInfo(const QString& userName)
0174 {
0175     QString userID = getUserID(userName);
0176 
0177     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getUserAccountInfo with userID: " << userID;
0178 
0179     QMap<QString, QVariant> map;
0180 
0181     if (userID.isEmpty())
0182     {
0183         return map;
0184     }
0185 
0186     m_settings->beginGroup(m_store->groupKey());
0187     m_settings->beginGroup(userID);
0188     QStringList keys = m_settings->allKeys();
0189 
0190     Q_FOREACH (const QString& key, keys)
0191     {
0192         QVariant value = m_settings->value(key);
0193         map.insert(key, value);
0194     }
0195 
0196     m_settings->endGroup();
0197     m_settings->endGroup();
0198 
0199     return map;
0200 }
0201 
0202 void WSTalker::saveUserAccount(const QString& userName,
0203                                const QString& userID,
0204                                long long int expire,
0205                                const QString& accessToken,
0206                                const QString& refreshToken)
0207 {
0208     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "saveUserAccount with username : " << userName << ", userID: " << userID;
0209 
0210     if (userID.isEmpty())
0211     {
0212         return;
0213     }
0214 
0215     m_settings->beginGroup(m_store->groupKey());
0216 
0217     m_settings->beginGroup(QLatin1String("users"));
0218     m_settings->setValue(userName, userID);
0219     m_settings->endGroup();
0220 
0221     m_settings->beginGroup(userID);
0222     m_settings->setValue(QLatin1String("expiration_time"), expire);
0223     m_settings->setValue(QLatin1String("access_token"),    accessToken);
0224     m_settings->setValue(QLatin1String("refresh_token"),   refreshToken);
0225     m_settings->endGroup();
0226 
0227     m_settings->endGroup();
0228 
0229     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "current " << QDateTime::currentMSecsSinceEpoch() / 1000;
0230     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "expire " << expire;
0231 }
0232 
0233 bool WSTalker::loadUserAccount(const QString& userName)
0234 {
0235     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "loadUserAccount with user name : " << userName;
0236 
0237     /* User logins using new account, return false so that we can unlink() current account,
0238      * before link() to new account
0239      */
0240     if (userName.isEmpty())
0241     {
0242         return false;
0243     }
0244 
0245     QMap<QString, QVariant> map = getUserAccountInfo(userName);
0246 
0247     /*
0248      * if getUserAccountInfo(userName) return empty with a non empty userName, there must
0249      * be some kind of errors. So, the condition below is a security check, which assures
0250      * user to relogin anyway.
0251      *
0252      * However, if it happens, INSPECTATION is obligated!!!
0253      */
0254     if (map.isEmpty())
0255     {
0256         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "WARNING: Something strange happens with getUserAccountInfo";
0257         return false;
0258     }
0259 
0260     QString expire        = map[QLatin1String("expiration_time")].toString();
0261     QString accessToken   = map[QLatin1String("access_token")].toString();
0262     QString refreshToken  = map[QLatin1String("refresh_token")].toString();
0263 
0264     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "expired moment : " << expire;
0265     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "current time : " << QDateTime::currentMSecsSinceEpoch() / 1000;
0266     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "access_token: " << accessToken;
0267 
0268     /* If access token is not expired yet, retrieve all tokens and return true so that we can link()
0269      * directly to new account.
0270      * Otherwise, return false so that user can relogin.
0271      */
0272 
0273     if (expire.toLongLong() > QDateTime::currentMSecsSinceEpoch() / 1000)
0274     {
0275         resetTalker(expire, accessToken, refreshToken);
0276         return true;
0277     }
0278 
0279     return false;
0280 
0281 }
0282 
0283 void WSTalker::resetTalker(const QString& /*expire*/, const QString& /*accessToken*/, const QString& /*refreshToken*/)
0284 {
0285 }
0286 
0287 void WSTalker::getLoggedInUser()
0288 {
0289 }
0290 
0291 void WSTalker::listAlbums(long long /*userID*/)
0292 {
0293 }
0294 
0295 void WSTalker::createNewAlbum()
0296 {
0297 }
0298 
0299 void WSTalker::addPhoto(const QString& /*imgPath*/, const QString& /*albumID*/, const QString& /*caption*/)
0300 {
0301 }
0302 
0303 void WSTalker::removeUserAccount(const QString& userName)
0304 {
0305     QString userID = getUserID(userName);
0306 
0307     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "removeUserAccount with userID: " << userID;
0308 
0309     if (userID.isEmpty())
0310     {
0311        return;
0312     }
0313 
0314     m_settings->beginGroup(m_store->groupKey());
0315 
0316     m_settings->beginGroup(userID);
0317     m_settings->remove(QString());
0318     m_settings->endGroup();
0319 
0320     m_settings->beginGroup(QLatin1String("users"));
0321     m_settings->remove(userName);
0322     m_settings->endGroup();
0323 
0324     m_settings->endGroup();
0325 }
0326 
0327 void WSTalker::removeAllAccounts()
0328 {
0329     m_settings->beginGroup(m_store->groupKey());
0330     m_settings->remove(QString());
0331     m_settings->endGroup();
0332 }
0333 
0334 void WSTalker::sortAlbumsList(QList<WSAlbum>& albumsList)
0335 {
0336     std::sort(albumsList.begin(), albumsList.end());
0337 }
0338 
0339 /*
0340  * saveUserAccount(...) must be called inside this method when it is reimplemented
0341  * in derived class, because saveUserAccount(...) can be called only in derived class.
0342  */
0343 void WSTalker::authenticationDone(int errCode, const QString& errMsg)
0344 {
0345     if (errCode != 0)
0346     {
0347         QMessageBox::critical(QApplication::activeWindow(),
0348                               i18nc("@title:window", "Error"),
0349                               i18n("Code: %1. %2", errCode, errMsg));
0350     }
0351 
0352     Q_EMIT signalBusy(false);
0353 }
0354 
0355 void WSTalker::parseResponseGetLoggedInUser(const QByteArray& /*data*/)
0356 {
0357 }
0358 
0359 void WSTalker::parseResponseListAlbums(const QByteArray& /*data*/)
0360 {
0361 }
0362 
0363 void WSTalker::parseResponseCreateAlbum(const QByteArray& /*data*/)
0364 {
0365 }
0366 
0367 void WSTalker::parseResponseAddPhoto(const QByteArray& /*data*/)
0368 {
0369 }
0370 
0371 void WSTalker::slotFinished(QNetworkReply* reply)
0372 {
0373     if (reply != m_reply)
0374     {
0375         return;
0376     }
0377 
0378     m_reply = 0;
0379 
0380     if (reply->error() != QNetworkReply::NoError)
0381     {
0382         qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->error() << " text :"<< QLatin1String(reply->readAll());
0383         authenticationDone(reply->error(), reply->errorString());
0384         reply->deleteLater();
0385         return;
0386     }
0387 
0388     QByteArray buffer = reply->readAll();
0389 
0390     switch (m_state)
0391     {
0392         case WSTalker::GETUSER :
0393             parseResponseGetLoggedInUser(buffer);
0394             authenticationDone(0, QLatin1String(""));
0395             break;
0396         case WSTalker::LISTALBUMS :
0397             parseResponseListAlbums(buffer);
0398             break;
0399         case WSTalker::CREATEALBUM :
0400             parseResponseCreateAlbum(buffer);
0401             break;
0402         case WSTalker::ADDPHOTO :
0403             parseResponseAddPhoto(buffer);
0404             break;
0405         case WSTalker::DEFAULT :
0406             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotFinished at state = default";
0407             break;
0408     }
0409 
0410     reply->deleteLater();
0411 }
0412 
0413 void WSTalker::slotOpenBrowser(const QUrl& url)
0414 {
0415     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser...";
0416     Q_EMIT signalOpenBrowser(url);
0417 }
0418 
0419 void WSTalker::slotCloseBrowser()
0420 {
0421     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Close Browser...";
0422     Q_EMIT signalCloseBrowser();
0423 }
0424 
0425 void WSTalker::slotLinkingFailed()
0426 {
0427     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK fail";
0428     authenticationDone(-1, QLatin1String("Account link failed."));
0429 
0430     Q_EMIT signalBusy(false);
0431     Q_EMIT signalAuthenticationComplete(linked());
0432 }
0433 
0434 void WSTalker::slotLinkingSucceeded()
0435 {
0436     if (!linked())
0437     {
0438         Q_EMIT signalBusy(false);
0439         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK ok";
0440 
0441         return;
0442     }
0443 
0444     // Get user account information
0445     getLoggedInUser();
0446 
0447     Q_EMIT signalAuthenticationComplete(linked());
0448     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK ok";
0449 }
0450 
0451 void WSTalker::slotResponseTokenReceived(const QMap<QString, QString>& /*rep*/)
0452 {
0453 }
0454 
0455 } // namespace DigikamGenericUnifiedPlugin
0456 
0457 #include "moc_wstalker.cpp"