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