1 /************************************************************************
2  *									*
3  *  This file is part of Kooka, a scanning/OCR application using	*
4  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.	*
5  *									*
6  *  Copyright (C) 2018 Jonathan Marten <jjm@keelhaul.me.uk>		*
7  *									*
8  *  Kooka is free software; you can redistribute it and/or modify it	*
9  *  under the terms of the GNU Library General Public License as	*
10  *  published by the Free Software Foundation and appearing in the	*
11  *  file COPYING included in the packaging of this file;  either	*
12  *  version 2 of the License, or (at your option) any later version.	*
13  *									*
14  *  As a special exception, permission is given to link this program	*
15  *  with any version of the KADMOS OCR/ICR engine (a product of		*
16  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting	*
17  *  executable without including the source code for KADMOS in the	*
18  *  source distribution.						*
19  *									*
20  *  This program is distributed in the hope that it will be useful,	*
21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of	*
22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	*
23  *  GNU General Public License for more details.			*
24  *									*
25  *  You should have received a copy of the GNU General Public		*
26  *  License along with this program;  see the file COPYING.  If		*
27  *  not, see <http://www.gnu.org/licenses/>.				*
28  *									*
29  ************************************************************************/
30 
31 #include "pluginmanager.h"
32 
33 #include <qpluginloader.h>
34 #include <qdir.h>
35 #include <qcoreapplication.h>
36 #include <qdebug.h>
37 
38 #include <kservicetypetrader.h>
39 #include <kplugininfo.h>
40 #include <klocalizedstring.h>
41 
42 #include "abstractplugin.h"
43 
44 
45 static PluginManager *sInstance = nullptr;
46 
47 
PluginManager()48 PluginManager::PluginManager()
49 {
50     QStringList pluginPaths = QCoreApplication::libraryPaths();
51     qDebug() << "initial paths" << pluginPaths;
52 
53     // Assume that the first path entry is the standard install location.
54     // Our plugins will be in a subdirectory of that.
55     Q_ASSERT(!pluginPaths.isEmpty());
56     QString installPath = pluginPaths.takeFirst();
57     pluginPaths.prepend(installPath+"/"+QCoreApplication::applicationName());
58 
59     // Also add the plugin build directories, for use when running in place.
60     // In order to get the most up-to-date plugins, these need to have priority
61     // over all other install locations.
62     QDir dir(QCoreApplication::applicationDirPath()+"/../plugins/ocr");
63     const QStringList subdirs = dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
64     foreach (const QString &subdir, subdirs)
65     {
66         const QString path = subdir+"/Makefile";
67         if (dir.exists(path)) pluginPaths.prepend(dir.absoluteFilePath(subdir));
68     }
69 
70     // Put back the standard install location, for locating KParts and
71     // other plugins which may be needed.
72     pluginPaths.append(installPath);
73 
74     qDebug() << "using paths" << pluginPaths;
75     QCoreApplication::setLibraryPaths(pluginPaths);
76 }
77 
78 
self()79 PluginManager *PluginManager::self()
80 {
81     if (sInstance==nullptr)
82     {
83         sInstance = new PluginManager();
84         qDebug() << "allocated global instance";
85     }
86     return (sInstance);
87 }
88 
89 
commentAsRichText(const QString & comment)90 static QString commentAsRichText(const QString &comment)
91 {
92     // The 'comment' returned from KService is a QString which may have KUIT markup.
93     // The conversion from that to a KLocalizedString, then back to a QString, actually
94     // implements the KUIT markup.  The "@info" context ensures that the markup is
95     // converted to rich text (HTML).
96     //
97     // There is no need to specify a translation domain, because the string from the
98     // service desktop file will already have been translated.
99 
100     return (kxi18nc("@info", comment.toLocal8Bit().constData()).toString());
101 }
102 
103 
loadPlugin(PluginManager::PluginType type,const QString & name)104 AbstractPlugin *PluginManager::loadPlugin(PluginManager::PluginType type, const QString &name)
105 {
106     qDebug() << "want type" << type << name;
107 
108     AbstractPlugin *plugin = mLoadedPlugins.value(type);
109     if (plugin!=nullptr)				// a plugin is loaded
110     {
111         qDebug() << "have current" << plugin->pluginInfo()->key;
112         if (name==plugin->pluginInfo()->key)		// wanted plugin is already loaded
113         {
114             qDebug() << "already loaded";
115             return (plugin);
116         }
117 
118         qDebug() << "unloading current";
119         delete plugin;
120         plugin = nullptr;
121     }
122 
123     if (name.isEmpty())					// just want to unload current
124     {
125         mLoadedPlugins[type] = nullptr;			// note that nothing is loaded
126         return (nullptr);				// no more to do
127     }
128 
129     // TODO: plugin type
130     const KService::List list = KServiceTypeTrader::self()->query("Kooka/OcrPlugin",
131                                                                   QString("[DesktopEntryName]=='%1'").arg(name));
132     qDebug() << "query count" << list.count();
133     if (list.isEmpty()) qWarning() << "No plugin services found";
134     else
135     {
136         if (list.count()>1) qWarning() << "Multiple plugin services found, using only the first";
137 							// should not happen, names are unique
138         const KService::Ptr service = list.first();
139         const QString lib = service->library();
140         qDebug() << "  name" << service->name();
141         qDebug() << "  icon" << service->icon();
142         qDebug() << "  library" << lib;
143 
144         KPluginLoader loader(*service);
145         if (loader.factory()==nullptr)
146         {
147             qWarning() << "Cannot load plugin library" << lib << "from service";
148         }
149         else
150         {
151             plugin = loader.factory()->create<AbstractPlugin>();
152             if (plugin!=nullptr)
153             {
154                 qDebug() << "created plugin from library" << lib;
155 
156                 AbstractPluginInfo *info = new AbstractPluginInfo;
157                 info->key = service->desktopEntryName();
158                 info->name = service->name();
159                 info->icon = service->icon();
160                 info->description = commentAsRichText(service->comment());
161 
162                 plugin->mPluginInfo = info;
163             }
164             else qWarning() << "Cannot create plugin from library" << lib;
165         }
166     }
167 
168     mLoadedPlugins[type] = plugin;
169     return (plugin);
170 }
171 
172 
allPlugins(PluginManager::PluginType type) const173 QMap<QString,AbstractPluginInfo> PluginManager::allPlugins(PluginManager::PluginType type) const
174 {
175     qDebug() << "want all of type" << type;
176 
177     QMap<QString,AbstractPluginInfo> plugins;
178 
179     // TODO: plugin type
180     const KService::List list = KServiceTypeTrader::self()->query("Kooka/OcrPlugin");
181     qDebug() << "query count" << list.count();
182     if (list.isEmpty()) qWarning() << "No plugin services found";
183     else
184     {
185         foreach (const KService::Ptr service, qAsConst(list))
186         {
187             qDebug() << "  found" << service->desktopEntryName();
188 
189             struct AbstractPluginInfo info;
190             info.key = service->desktopEntryName();
191             info.name = service->name();
192             info.icon = service->icon();
193             info.description = commentAsRichText(service->comment());
194 
195             plugins[info.key] = info;
196         }
197     }
198 
199     return (plugins);
200 }
201 
202 
currentPlugin(PluginManager::PluginType type) const203 AbstractPlugin *PluginManager::currentPlugin(PluginManager::PluginType type) const
204 {
205     return (mLoadedPlugins.value(type));
206 }
207