File indexing completed on 2024-04-21 15:12:05

0001 /************************************************************************
0002  *                                  *
0003  *  This file is part of Kooka, a scanning/OCR application using    *
0004  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.  *
0005  *                                  *
0006  *  Copyright (C) 2018 Jonathan Marten <jjm@keelhaul.me.uk>     *
0007  *                                  *
0008  *  Kooka is free software; you can redistribute it and/or modify it    *
0009  *  under the terms of the GNU Library General Public License as    *
0010  *  published by the Free Software Foundation and appearing in the  *
0011  *  file COPYING included in the packaging of this file;  either    *
0012  *  version 2 of the License, or (at your option) any later version.    *
0013  *                                  *
0014  *  As a special exception, permission is given to link this program    *
0015  *  with any version of the KADMOS OCR/ICR engine (a product of     *
0016  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting  *
0017  *  executable without including the source code for KADMOS in the  *
0018  *  source distribution.                        *
0019  *                                  *
0020  *  This program is distributed in the hope that it will be useful, *
0021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  *
0022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *
0023  *  GNU General Public License for more details.            *
0024  *                                  *
0025  *  You should have received a copy of the GNU General Public       *
0026  *  License along with this program;  see the file COPYING.  If     *
0027  *  not, see <http://www.gnu.org/licenses/>.                *
0028  *                                  *
0029  ************************************************************************/
0030 
0031 #include "pluginmanager.h"
0032 
0033 #include <kpluginmetadata.h>
0034 #include <kpluginfactory.h>
0035 #include <klocalizedstring.h>
0036 
0037 #include "abstractplugin.h"
0038 #include "kooka_logging.h"
0039 
0040 #include <qcoreapplication.h>
0041 
0042 
0043 static PluginManager *sInstance = nullptr;
0044 
0045 
0046 PluginManager::PluginManager()
0047 {
0048     // There is an anomaly between KPluginMetaData::findPlugins() which
0049     // is used by allPlugins(), and the KPluginMetaData(const QString &file)
0050     // constructor which is used by loadPlugin().  KPluginMetaData::findPlugins()
0051     // uses KPluginMetaDataPrivate::forEachPlugin() which prepends the
0052     // application directory (containing the current executable) to
0053     // QCoreApplication::libraryPaths(), giving built but uninstalled plugins
0054     // priority over installed ones.  The KPluginMetaData(const QString &file)
0055     // constructor does not do this and passes the specified file name directly
0056     // to QPluginLoader, so using QCoreApplication::libraryPaths() unchanged.
0057     // The result is that allPlugins() will enumerate uninstalled development
0058     // plugins but loadPlugin() will not necessarily load from the same location.
0059     //
0060     // To work around this, ensure that the application directory is at the
0061     // front of QCoreApplication::libraryPaths().
0062 
0063     QStringList pluginPaths = QCoreApplication::libraryPaths();
0064     qCDebug(KOOKA_LOG) << "initial paths" << pluginPaths;
0065     Q_ASSERT(!pluginPaths.isEmpty());
0066 
0067     QString appDirPath = QCoreApplication::applicationDirPath();
0068     pluginPaths.removeAll(appDirPath);
0069     pluginPaths.prepend(appDirPath);
0070 
0071     qCDebug(KOOKA_LOG) << "using paths" << pluginPaths;
0072     QCoreApplication::setLibraryPaths(pluginPaths);
0073 }
0074 
0075 
0076 PluginManager *PluginManager::self()
0077 {
0078     if (sInstance==nullptr)
0079     {
0080         sInstance = new PluginManager();
0081         qCDebug(KOOKA_LOG) << "allocated global instance";
0082     }
0083     return (sInstance);
0084 }
0085 
0086 
0087 static QString commentAsRichText(const QString &comment)
0088 {
0089     // The 'comment' returned from KService is a QString which may have KUIT markup.
0090     // The conversion from that to a KLocalizedString, then back to a QString, actually
0091     // implements the KUIT markup.  The "@info" context ensures that the markup is
0092     // converted to rich text (HTML).
0093     //
0094     // There is no need to specify a translation domain, because the string from the
0095     // service desktop file will already have been translated.
0096 
0097     return (kxi18nc("@info", comment.toLocal8Bit().constData()).toString());
0098 }
0099 
0100 
0101 static QString pluginTypeString(PluginManager::PluginType type)
0102 {
0103     switch (type)
0104     {
0105 case PluginManager::OcrPlugin:          return ("ocr");
0106 case PluginManager::DestinationPlugin:      return ("destination");
0107     }
0108     Q_UNREACHABLE();
0109     return QString();
0110 }
0111 
0112 
0113 AbstractPlugin *PluginManager::loadPlugin(PluginManager::PluginType type, const QString &name)
0114 {
0115     qCDebug(KOOKA_LOG) << "want type" << type << name;
0116 
0117     AbstractPlugin *plugin = mLoadedPlugins.value(type);
0118     if (plugin!=nullptr)                // a plugin is loaded
0119     {
0120         if (name==plugin->pluginInfo()->key)        // wanted plugin is already loaded
0121         {
0122             qCDebug(KOOKA_LOG) << "already loaded";
0123             return (plugin);
0124         }
0125 
0126         qCDebug(KOOKA_LOG) << "unloading current" << plugin->pluginInfo()->key;
0127         delete plugin;
0128         plugin = nullptr;
0129     }
0130 
0131     if (name.isEmpty())                 // just want to unload current
0132     {
0133         mLoadedPlugins[type] = nullptr;         // note that nothing is loaded
0134         return (nullptr);               // no more to do
0135     }
0136 
0137     KPluginMetaData md(QStringLiteral("kooka_") + pluginTypeString(type) + QStringLiteral("/") + name);
0138     plugin = KPluginFactory::instantiatePlugin<AbstractPlugin>(md).plugin;
0139 
0140     if (plugin!=nullptr)
0141     {
0142         qCDebug(KOOKA_LOG) << "created plugin from library" << md.fileName();
0143 
0144         AbstractPluginInfo *info = new AbstractPluginInfo;
0145         info->key = md.pluginId();
0146         info->name = md.name();
0147         info->icon = md.iconName();
0148         info->description = commentAsRichText(md.description());
0149 
0150         plugin->mPluginInfo = info;
0151     }
0152     else qCWarning(KOOKA_LOG) << "Cannot create plugin from library" << md.fileName();
0153 
0154     mLoadedPlugins[type] = plugin;
0155     return (plugin);
0156 }
0157 
0158 
0159 QMap<QString,AbstractPluginInfo> PluginManager::allPlugins(PluginManager::PluginType type) const
0160 {
0161     qCDebug(KOOKA_LOG) << "want all of type" << type;
0162 
0163     QMap<QString,AbstractPluginInfo> plugins;
0164 
0165     const QVector<KPluginMetaData> list = KPluginMetaData::findPlugins(QStringLiteral("kooka_") + pluginTypeString(type));
0166 
0167     qCDebug(KOOKA_LOG) << "query count" << list.count();
0168     if (list.isEmpty()) qCWarning(KOOKA_LOG) << "No plugin services found";
0169     else
0170     {
0171         for (const KPluginMetaData &service : list)
0172         {
0173             qCDebug(KOOKA_LOG) << "  found" << service.pluginId();
0174 
0175             struct AbstractPluginInfo info;
0176             info.key = service.pluginId();
0177             info.name = service.name();
0178             info.icon = service.iconName();
0179             info.description = commentAsRichText(service.description());
0180 
0181             plugins[info.key] = info;
0182         }
0183     }
0184 
0185     return (plugins);
0186 }
0187 
0188 
0189 AbstractPlugin *PluginManager::currentPlugin(PluginManager::PluginType type) const
0190 {
0191     return (mLoadedPlugins.value(type));
0192 }