File indexing completed on 2024-05-19 05:07:26

0001 /*
0002     SPDX-FileCopyrightText: 2013-2018 Christian Dávid <christian-david@web.de>
0003     SPDX-FileCopyrightText: 2019-2022 Thomas Baumgart <tbaumgart@kde.org>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "onlinejobadministration.h"
0008 
0009 // ----------------------------------------------------------------------------
0010 // Std Includes
0011 
0012 #include <memory>
0013 
0014 // ----------------------------------------------------------------------------
0015 // QT Includes
0016 
0017 #include <QDebug>
0018 #include <QJsonArray>
0019 #include <QList>
0020 #include <QPluginLoader>
0021 #include <QXmlStreamReader>
0022 
0023 // ----------------------------------------------------------------------------
0024 // KDE Includes
0025 #include <KJsonUtils>
0026 #include <KPluginFactory>
0027 #include <KPluginMetaData>
0028 
0029 // ----------------------------------------------------------------------------
0030 // Project Includes
0031 
0032 #include "mymoneyaccount.h"
0033 #include "mymoneyfile.h"
0034 #include "mymoneykeyvaluecontainer.h"
0035 #include "onlinejobsmodel.h"
0036 #include "onlinepluginextended.h"
0037 #include "onlinetasks/unavailabletask/tasks/unavailabletask.h"
0038 #include "onlinetasks/interfaces/tasks/credittransfer.h"
0039 #include "tasks/onlinetask.h"
0040 
0041 onlineJobAdministration::onlineJobAdministration(QObject *parent)
0042     : QObject(parent)
0043     , m_onlinePlugins(nullptr)
0044     , m_inRegistration(false)
0045 {
0046 }
0047 
0048 onlineJobAdministration::~onlineJobAdministration()
0049 {
0050     clearCaches();
0051 }
0052 
0053 onlineJobAdministration* onlineJobAdministration::instance()
0054 {
0055     static onlineJobAdministration m_instance;
0056     return &m_instance;
0057 }
0058 
0059 void onlineJobAdministration::clearCaches()
0060 {
0061     qDeleteAll(m_onlineTasks);
0062     m_onlineTasks.clear();
0063     qDeleteAll(m_onlineTaskConverter);
0064     m_onlineTaskConverter.clear();
0065 }
0066 
0067 KMyMoneyPlugin::OnlinePluginExtended* onlineJobAdministration::getOnlinePlugin(const QString& accountId) const
0068 {
0069     MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId);
0070 
0071     QMap<QString, KMyMoneyPlugin::OnlinePluginExtended*>::const_iterator it_p;
0072     it_p = m_onlinePlugins->constFind(acc.onlineBankingSettings().value("provider").toLower());
0073 
0074     if (it_p != m_onlinePlugins->constEnd()) {
0075         // plugin found, use it
0076         return *it_p;
0077     }
0078     return 0;
0079 }
0080 
0081 void onlineJobAdministration::setOnlinePlugins(QMap<QString, KMyMoneyPlugin::OnlinePluginExtended*>& plugins)
0082 {
0083     m_onlinePlugins = &plugins;
0084     updateActions();
0085 }
0086 
0087 void onlineJobAdministration::updateActions()
0088 {
0089     Q_EMIT canSendAnyTaskChanged(canSendAnyTask());
0090     Q_EMIT canSendCreditTransferChanged(canSendCreditTransfer());
0091 }
0092 
0093 QStringList onlineJobAdministration::availableOnlineTasks()
0094 {
0095     const auto plugins = KPluginMetaData::findPlugins("kmymoney_plugins/onlinetasks", [](const KPluginMetaData& data) {
0096         return !(data.rawData()["KMyMoney"].toObject()["OnlineTask"].isNull());
0097     });
0098 
0099     QStringList list;
0100     for (const KPluginMetaData& plugin : qAsConst(plugins)) {
0101         QJsonValue array = plugin.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Iids"];
0102         if (array.isArray())
0103             list.append(array.toVariant().toStringList());
0104     }
0105     return list;
0106 }
0107 
0108 /**
0109  * @internal The real work is done here.
0110  */
0111 bool onlineJobAdministration::isJobSupported(const QString& accountId, const QString& name) const
0112 {
0113     if (!m_onlinePlugins)
0114         return false;
0115     for (KMyMoneyPlugin::OnlinePluginExtended* plugin : qAsConst(*m_onlinePlugins)) {
0116         if (plugin->availableJobs(accountId).contains(name))
0117             return true;
0118     }
0119     return false;
0120 }
0121 
0122 bool onlineJobAdministration::isJobSupported(const QString& accountId, const QStringList& names) const
0123 {
0124     for (const auto& name : names) {
0125         if (isJobSupported(accountId, name))
0126             return true;
0127     }
0128     return false;
0129 }
0130 
0131 bool onlineJobAdministration::isAnyJobSupported(const QString& accountId) const
0132 {
0133     if (accountId.isEmpty())
0134         return false;
0135 
0136     if (!m_onlinePlugins)
0137         return false;
0138 
0139     for (KMyMoneyPlugin::OnlinePluginExtended* plugin : qAsConst(*m_onlinePlugins)) {
0140         if (!(plugin->availableJobs(accountId).isEmpty()))
0141             return true;
0142     }
0143     return false;
0144 }
0145 
0146 onlineJob onlineJobAdministration::createOnlineJob(const QString& name, const QString& id) const
0147 {
0148     return (onlineJob(createOnlineTask(name), id));
0149 }
0150 
0151 onlineTask* onlineJobAdministration::createOnlineTask(const QString& name) const
0152 {
0153     const onlineTask* task = rootOnlineTask(name);
0154     if (task)
0155         return task->clone();
0156     return nullptr;
0157 }
0158 
0159 onlineTask* onlineJobAdministration::createOnlineTaskByXml(QXmlStreamReader* reader, const QString& iid) const
0160 {
0161     onlineTask* task = rootOnlineTask(iid);
0162     if (task) {
0163         return task->createFromXml(reader);
0164     }
0165     qWarning("In the file is a onlineTask for which I could not find the plugin ('%s')", qPrintable(iid));
0166     // the taskTemplate object is only used to get access
0167     // to the createFromXml() method which returns
0168     // a pointer to the actual newly created object.
0169     unavailableTask taskTemplate(QString{});
0170     task = taskTemplate.createFromXml(reader);
0171     return task;
0172 }
0173 
0174 /**
0175  * @internal Using KPluginFactory to create the plugins seemed to be good idea. The drawback is that it does not support to create non QObjects directly.
0176  * This made this function way longer than needed and adds many checks.
0177  *
0178  * @fixme Delete created tasks
0179  */
0180 onlineTask* onlineJobAdministration::rootOnlineTask(const QString& name) const
0181 {
0182     auto plugins = KPluginMetaData::findPlugins("kmymoney_plugins/onlinetasks", [&name](const KPluginMetaData& data) {
0183         QJsonValue array = data.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Iids"];
0184         if (array.isArray())
0185             return (array.toVariant().toStringList().contains(name));
0186         return false;
0187     });
0188 
0189     if (plugins.isEmpty())
0190         return nullptr;
0191 
0192     if (plugins.length() != 1)
0193         qWarning() << "Multiple plugins which offer the online task \"" << name << "\" were found. Loading a random one.";
0194 
0195     auto pluginResult = KPluginFactory::instantiatePlugin<KMyMoneyPlugin::onlineTaskFactory>(plugins.first(), onlineJobAdministration::instance());
0196     if (!pluginResult) {
0197         qWarning() << "Could not load plugin for online task " << name << ", file name " << plugins.first().fileName() << ".";
0198         return nullptr;
0199     }
0200     auto taskFactory = pluginResult.plugin;
0201 
0202     // Finally create task
0203     onlineTask* task = taskFactory->createOnlineTask(name);
0204     if (task)
0205         // Add to our cache as this is still used in several places
0206         onlineJobAdministration::instance()->registerOnlineTask(taskFactory->createOnlineTask(name));
0207 
0208     return task;
0209 }
0210 
0211 onlineTaskConverter::convertType onlineJobAdministration::canConvert(const QString& originalTaskIid, const QString& convertTaskIid) const
0212 {
0213     return canConvert(originalTaskIid, QStringList(convertTaskIid));
0214 }
0215 
0216 onlineTaskConverter::convertType onlineJobAdministration::canConvert(const QString& originalTaskIid, const QStringList& convertTaskIids) const
0217 {
0218     Q_ASSERT(false);
0219     //! @todo Make alive
0220     onlineTaskConverter::convertType bestConvertType = onlineTaskConverter::convertImpossible;
0221 #if 0
0222     for (QString destinationName : destinationNames) {
0223         onlineTask::convertType type = canConvert(original, destinationName);
0224         if (type == onlineTask::convertionLossy)
0225             bestConvertType = onlineTask::convertionLossy;
0226         else if (type == onlineTask::convertionLoseless)
0227             return onlineTask::convertionLoseless;
0228     }
0229 #else
0230     Q_UNUSED(originalTaskIid);
0231     Q_UNUSED(convertTaskIids);
0232 #endif
0233     return bestConvertType;
0234 }
0235 
0236 /**
0237  * @todo if more than one converter offers the convert, use best
0238  */
0239 onlineJob onlineJobAdministration::convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation, const QString& onlineJobId) const
0240 {
0241     onlineJob newJob;
0242 
0243     const QList<onlineTaskConverter*> converterList = m_onlineTaskConverter.values(convertTaskIid);
0244     for (const onlineTaskConverter* converter : converterList) {
0245         if (converter->convertibleTasks().contains(original.taskIid())) {
0246             onlineTask* task = converter->convert(*original.task(), convertType, userInformation);
0247             Q_ASSERT_X(convertType != onlineTaskConverter::convertImpossible || task != 0, qPrintable("converter for " + converter->convertedTask()), "Converter returned convertType 'impossible' but return was not null_ptr.");
0248             if (task != 0) {
0249                 newJob = onlineJob(task, onlineJobId);
0250                 break;
0251             }
0252         }
0253     }
0254 
0255     return newJob;
0256 }
0257 
0258 onlineJob onlineJobAdministration::convertBest(const onlineJob& original, const QStringList& convertTaskIids, onlineTaskConverter::convertType& convertType, QString& userInformation) const
0259 {
0260     return convertBest(original, convertTaskIids, convertType, userInformation, original.id());
0261 }
0262 
0263 onlineJob onlineJobAdministration::convertBest(const onlineJob& original, const QStringList& convertTaskIids, onlineTaskConverter::convertType& bestConvertType, QString& bestUserInformation, const QString& onlineJobId) const
0264 {
0265     onlineJob bestConvert;
0266     bestConvertType = onlineTaskConverter::convertImpossible;
0267     bestUserInformation = QString();
0268 
0269     for (const auto& taskIid : convertTaskIids) {
0270         // Try convert
0271         onlineTaskConverter::convertType convertType = onlineTaskConverter::convertImpossible;
0272         QString userInformation;
0273         onlineJob convertJob = convert(original, taskIid, convertType, userInformation, onlineJobId);
0274 
0275         // Check if it was successful
0276         if (bestConvertType < convertType) {
0277             bestConvert = convertJob;
0278             bestUserInformation = userInformation;
0279             bestConvertType = convertType;
0280             if (convertType == onlineTaskConverter::convertionLoseless)
0281                 break;
0282         }
0283     }
0284 
0285     return bestConvert;
0286 }
0287 
0288 void onlineJobAdministration::registerAllOnlineTasks()
0289 {
0290     // avoid recursive entrance
0291     if (m_inRegistration)
0292         return;
0293 
0294     m_inRegistration = true;
0295     const QStringList availableTasks = availableOnlineTasks();
0296     for (const auto& name : availableTasks) {
0297         onlineTask* const task = rootOnlineTask(name);
0298         Q_UNUSED(task);
0299     }
0300     m_inRegistration = false;
0301 }
0302 
0303 void onlineJobAdministration::registerOnlineTask(onlineTask *const task)
0304 {
0305     if (Q_UNLIKELY(task == 0))
0306         return;
0307 
0308     const bool sendAnyTask = canSendAnyTask();
0309     const bool sendCreditTransfer = canSendCreditTransfer();
0310 
0311     m_onlineTasks.insert(task->taskName(), task);
0312 
0313     if (sendAnyTask != canSendAnyTask())
0314         Q_EMIT canSendAnyTaskChanged(!sendAnyTask);
0315     if (sendCreditTransfer != canSendCreditTransfer())
0316         Q_EMIT canSendCreditTransferChanged(!sendCreditTransfer);
0317 }
0318 
0319 void onlineJobAdministration::registerOnlineTaskConverter(onlineTaskConverter* const converter)
0320 {
0321     if (Q_UNLIKELY(converter == 0))
0322         return;
0323 
0324     m_onlineTaskConverter.insert(converter->convertedTask(), converter);
0325     qDebug() << "onlineTaskConverter available" << converter->convertedTask() << converter->convertibleTasks();
0326 }
0327 
0328 onlineJobAdministration::onlineJobEditOffers onlineJobAdministration::onlineJobEdits()
0329 {
0330     auto plugins = KPluginMetaData::findPlugins("kmymoney_plugins/onlinetasks", [](const KPluginMetaData& data) {
0331         return !(data.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Editors"].isNull());
0332     });
0333 
0334     onlineJobAdministration::onlineJobEditOffers list;
0335     list.reserve(plugins.size());
0336     for (const KPluginMetaData& data : qAsConst(plugins)) {
0337         QJsonArray editorsArray = data.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Editors"].toArray();
0338         for (const QJsonValue& entry : qAsConst(editorsArray)) {
0339             if (!entry.toObject()["OnlineTaskIds"].isNull()) {
0340                 list.append(onlineJobAdministration::onlineJobEditOffer{data.fileName(), KJsonUtils::readTranslatedString(entry.toObject(), "Name")});
0341             }
0342         }
0343     }
0344     return list;
0345 }
0346 
0347 IonlineTaskSettings::ptr onlineJobAdministration::taskSettings(const QString& taskName, const QString& accountId) const
0348 {
0349     KMyMoneyPlugin::OnlinePluginExtended* plugin = getOnlinePlugin(accountId);
0350     if (plugin != 0)
0351         return (plugin->settings(accountId, taskName));
0352     return IonlineTaskSettings::ptr();
0353 }
0354 
0355 bool onlineJobAdministration::canSendAnyTask()
0356 {
0357     if (!m_onlinePlugins)
0358         return false;
0359 
0360     if (m_onlineTasks.isEmpty()) {
0361         registerAllOnlineTasks();
0362     }
0363 
0364     // Check if any plugin supports a loaded online task
0365     /// @todo optimize this loop to move the accounts to the outer loop
0366     for (KMyMoneyPlugin::OnlinePluginExtended* plugin : qAsConst(*m_onlinePlugins)) {
0367         QList<MyMoneyAccount> accounts;
0368         MyMoneyFile::instance()->accountList(accounts, QStringList(), true);
0369         for (const auto& account : qAsConst(accounts)) {
0370             if (account.hasOnlineMapping()) {
0371                 const auto availableJobs = plugin->availableJobs(account.id());
0372                 for (const auto& onlineTaskIid : qAsConst(availableJobs)) {
0373                     if (m_onlineTasks.contains(onlineTaskIid)) {
0374                         return true;
0375                     }
0376                 }
0377             }
0378         }
0379     }
0380     return false;
0381 }
0382 
0383 bool onlineJobAdministration::canSendCreditTransfer()
0384 {
0385     if (!m_onlinePlugins)
0386         return false;
0387 
0388     if (m_onlineTasks.isEmpty()) {
0389         registerAllOnlineTasks();
0390     }
0391 
0392     QList<MyMoneyAccount> accounts;
0393     MyMoneyFile::instance()->accountList(accounts, QStringList(), true);
0394     for (const auto& account : qAsConst(accounts)) {
0395         if (account.hasOnlineMapping()) {
0396             for (const onlineTask* task : qAsConst(m_onlineTasks)) {
0397                 // Check if a online task has the correct type
0398                 if (dynamic_cast<const creditTransfer*>(task) != 0) {
0399                     for (KMyMoneyPlugin::OnlinePluginExtended* plugin : qAsConst(*m_onlinePlugins)) {
0400                         if (plugin->availableJobs(account.id()).contains(task->taskName()))
0401                             return true;
0402                     }
0403                 }
0404             }
0405         }
0406     }
0407     return false;
0408 }
0409 
0410 bool onlineJobAdministration::canEditOnlineJob(const QString& jobId)
0411 {
0412     const auto idx = MyMoneyFile::instance()->onlineJobsModel()->indexById(jobId);
0413     if (idx.isValid()) {
0414         const auto taskIid = idx.data(eMyMoney::Model::OnlineJobTaskIidRole).toString();
0415         return (!taskIid.isEmpty() && m_onlineTasks.contains(taskIid));
0416     }
0417     return false;
0418 }
0419 
0420 void onlineJobAdministration::updateOnlineTaskProperties()
0421 {
0422     Q_EMIT canSendAnyTaskChanged(canSendAnyTask());
0423     Q_EMIT canSendCreditTransferChanged(canSendCreditTransfer());
0424 }