File indexing completed on 2024-05-12 16:42:43

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