ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvPluginManager.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ecvPluginManager.h"
9 
10 #include "ecvApplicationBase.h"
11 
12 // CV_CORE_LIB
13 #include <CVLog.h>
14 
15 // CV_DB_LIB
16 #include <ecvExternalFactory.h>
17 
18 // PLUGINS
19 #include "ecvIOPluginInterface.h"
20 #include "ecvStdPluginInterface.h"
21 
22 // Qt
23 #include <QCoreApplication>
24 #include <QDebug>
25 #include <QDir>
26 #include <QPluginLoader>
27 #include <QSet>
28 #include <QSettings>
29 #include <QStandardPaths>
30 
31 namespace {
32 // This is used to avoid having to make the ccPluginManager constructor public
33 class PrivatePluginManager : public ccPluginManager {};
34 } // namespace
35 
36 Q_GLOBAL_STATIC(PrivatePluginManager, sPluginManager);
37 
38 // Get the plugin's IID from the meta data
39 static QString sPluginIID(QPluginLoader *loader) {
40  const QJsonObject metaObject = loader->metaData();
41 
42  if (metaObject.isEmpty()) {
43  const QString fileName = QFileInfo(loader->fileName()).fileName();
44 
46  QStringLiteral(
47  "\t%1 does not supply meta data in the "
48  "Q_PLUGIN_METADATA (see one of the example plugins).")
49  .arg(fileName));
50 
51  return {};
52  }
53 
54  return metaObject["IID"].toString();
55 }
56 
57 // Check for metadata and error if it's not there
58 // This indicates that a plugin hasn't been converted to the new JSON metadata.
59 static bool sMetaDataValid(QPluginLoader *loader) {
60  const QJsonObject metaObject = loader->metaData();
61 
62  const QString fileName = QFileInfo(loader->fileName()).fileName();
63 
64  if (metaObject.isEmpty() || metaObject["MetaData"].toObject().isEmpty()) {
66  QStringLiteral(
67  "%1 does not supply meta data in the Q_PLUGIN_METADATA "
68  "(see one of the example plugins).")
69  .arg(fileName));
70 
71  return false;
72  } else {
73  const QJsonObject data = metaObject["MetaData"].toObject();
74 
75  // The plugin type is going to be required
76  const QStringList validTypes{"I/O", "Standard"};
77 
78  const QString pluginType = data["type"].toString();
79 
80  if (!validTypes.contains(pluginType)) {
82  QStringLiteral(
83  "%1 does not supply a valid plugin type in its "
84  "info.json.\n\nFound: %2\n\nIt must be one of: %3")
85  .arg(fileName, pluginType, validTypes.join(", ")));
86 
87  return false;
88  }
89  }
90 
91  return true;
92 }
93 
94 ccPluginManager::ccPluginManager(QObject *parent) : QObject(parent) {}
95 
96 ccPluginManager &ccPluginManager::get() { return *sPluginManager; }
97 
98 void ccPluginManager::setPaths(const QStringList &paths) {
99  m_pluginPaths = paths;
100 }
101 
102 QStringList ccPluginManager::pluginPaths() { return m_pluginPaths; }
103 
105  m_pluginList.clear();
106 
107  if (m_pluginPaths.empty()) {
108  qWarning() << "There are no plugin paths set. Maybe missing a call to "
109  "ccPluginManager::setPaths()?";
110  }
111 
112  // "static" plugins
113  const QObjectList pluginInstances = QPluginLoader::staticInstances();
114 
115  for (QObject *plugin : pluginInstances) {
116  ccPluginInterface *ccPlugin = qobject_cast<ccPluginInterface *>(plugin);
117 
118  if (ccPlugin == nullptr) {
119  continue;
120  }
121 
122  CVLog::Print(
123  tr("[Plugin] Found: %1 (STATIC)").arg(ccPlugin->getName()));
124  m_pluginList.push_back(ccPlugin);
125  }
126 
127  // "dynamic" plugins
128  loadFromPathsAndAddToList();
129 
130  // now iterate over plugins and automatically register what we can
131  const auto pluginList = m_pluginList;
132 
133  const QStringList disabledList = disabledPluginIIDs();
134 
135  // now iterate over plugins and automatically register what we can
136  for (ccPluginInterface *plugin : m_pluginList) {
137  if (plugin == nullptr) {
138  Q_ASSERT(false);
139  continue;
140  }
141 
142  // Disable if we are not running on the command line and it's in the
143  // disabled list
144  if (!ecvApp->isCommandLine() && disabledList.contains(plugin->IID())) {
145  CVLog::Print(tr("[Plugin][%1] Disabled").arg(plugin->getName()));
146 
147  continue;
148  }
149 
150  switch (plugin->getType()) {
151  case ECV_STD_PLUGIN: {
152  ccStdPluginInterface *stdPlugin =
153  static_cast<ccStdPluginInterface *>(plugin);
154 
155  // see if this plugin provides an additional factory for objects
156  ccExternalFactory *factory =
157  stdPlugin->getCustomObjectsFactory();
158  if (factory) {
159  // if it's valid, add it to the factories set
162  ->addFactory(factory);
163  }
164  } break;
165 
166  case ECV_IO_FILTER_PLUGIN: // I/O filter
167  {
168  ccIOPluginInterface *ioPlugin =
169  static_cast<ccIOPluginInterface *>(plugin);
170 
171  QStringList ioExtensions;
172 
173  for (auto &filter : ioPlugin->getFilters()) {
174  if (filter) {
175  FileIOFilter::Register(filter);
176 
177  ioExtensions += filter->getDefaultExtension().toUpper();
178  }
179  }
180 
181  if (!ioExtensions.empty()) {
182  ioExtensions.sort();
183 
184  CVLog::Print(tr("[Plugin][%1] New file extensions "
185  "registered: %2")
186  .arg(ioPlugin->getName(),
187  ioExtensions.join(' ')));
188  }
189 
190  } break;
191 
192  default:
193  // nothing to do at this point
194  break;
195  }
196  }
197 }
198 
200 #ifdef QT_DEBUG
201  if (m_pluginList.empty()) {
202  qWarning() << "Plugin list is empty - did you call loadPlugins()?";
203  }
204 #endif
205 
206  return m_pluginList;
207 }
208 
210  bool enabled) {
211  QStringList list = disabledPluginIIDs();
212 
213  const QString &iid = plugin->IID();
214 
215  if (enabled) {
216  list.removeAll(iid);
217  } else {
218  if (!list.contains(iid)) {
219  list.append(iid);
220  }
221  }
222 
223  QSettings settings;
224 
225  settings.beginGroup("Plugins");
226 
227  settings.setValue("Disabled", list);
228 }
229 
231  const QString &iid = plugin->IID();
232 
233  return !disabledPluginIIDs().contains(iid);
234 }
235 
236 void ccPluginManager::loadFromPathsAndAddToList() {
237  const QStringList nameFilters{
238 #if defined(Q_OS_MAC)
239  "*.dylib"
240 #elif defined(Q_OS_WIN)
241  "*.dll"
242 #elif defined(Q_OS_LINUX)
243  "*.so"
244 #else
245 #error Need to specify the dynamic library extension for this OS.
246 #endif
247  };
248 
249  // Map the plugin's IID to its loader so we can unload it if necessary.
250  // This lets us override plugins by path.
251  QMap<QString, QPluginLoader *> pluginIIDToLoaderMap;
252 
253  const auto paths = pluginPaths();
254  for (const QString &path : paths) {
255  CVLog::Print(tr("[Plugin] Searching: %1").arg(path));
256 
257  QDir pluginsDir(path);
258 
259  pluginsDir.setNameFilters(nameFilters);
260 
261  const QStringList fileNames = pluginsDir.entryList();
262 
263  for (const QString &fileName : fileNames) {
264  const QString pluginPath = pluginsDir.absoluteFilePath(fileName);
265 
266  QPluginLoader *loader = new QPluginLoader(pluginPath);
267 
268  const QString pluginIID = sPluginIID(loader);
269 
270  bool metaDataValid = sMetaDataValid(loader);
271 
272  if (!metaDataValid || pluginIID.isNull()) {
274  tr("\t%1 has invalid meta data\t").arg(fileName));
275 
276  delete loader;
277 
278  continue;
279  }
280 
281  QObject *plugin = loader->instance();
282  ccPluginInterface *ccPlugin =
283  qobject_cast<ccPluginInterface *>(plugin);
284 
285  if ((plugin == nullptr) || (ccPlugin == nullptr)) {
286  if (plugin == nullptr) {
288  tr("\t%1 does not seem to be a valid plugin\t(%2)")
289  .arg(fileName, loader->errorString()));
290  } else {
291  CVLog::Warning(tr("\t%1 does not seem to be a valid plugin "
292  "or it is not supported by this version")
293  .arg(fileName));
294  }
295 
296  loader->unload();
297 
298  delete loader;
299 
300  continue;
301  }
302 
303  if (ccPlugin->getName().isEmpty()) {
304  CVLog::Error(tr("\tPlugin %1 has a blank name").arg(fileName));
305 
306  loader->unload();
307 
308  delete loader;
309 
310  continue;
311  }
312 
313  ccPlugin->setIID(pluginIID);
314 
315  QPluginLoader *previousLoader =
316  pluginIIDToLoaderMap.value(pluginIID);
317 
318  // If we have already loaded a plugin with this file name, unload it
319  // and replace the interface in the plugin list
320  if (previousLoader != nullptr) {
321  ccPluginInterface *pluginInterface =
322  qobject_cast<ccPluginInterface *>(
323  previousLoader->instance());
324 
325  // maintain the order of the plugin list
326  const int index = m_pluginList.indexOf(pluginInterface);
327  m_pluginList.replace(index, ccPlugin);
328 
329  previousLoader->unload();
330 
331  delete previousLoader;
332 
333  CVLog::Warning(tr("\t%1 overridden").arg(fileName));
334  } else {
335  m_pluginList.push_back(ccPlugin);
336  }
337 
338  pluginIIDToLoaderMap[pluginIID] = loader;
339 
340  CVLog::Print(tr("\tPlugin found: %1 (%2)")
341  .arg(ccPlugin->getName(), fileName));
342  }
343  }
344 
345  const auto loaders = pluginIIDToLoaderMap.values();
346 
347  for (QPluginLoader *loader : loaders) {
348  delete loader;
349  }
350 }
351 
352 QStringList ccPluginManager::disabledPluginIIDs() const {
353  QSettings settings;
354 
355  settings.beginGroup("Plugins");
356 
357  return settings.value("Disabled").toStringList();
358 }
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static void Register(Shared filter)
Registers a new filter.
virtual QString getName() const override
Returns (short) name (for menu entry, etc.)
static Container::Shared GetUniqueInstance()
I/O filter plugin interface.
virtual FilterList getFilters()
Returns a list of I/O filter instances.
Standard ECV plugin interface.
virtual const QString & IID() const =0
Get the IID of the plugin.
virtual ccExternalFactory * getCustomObjectsFactory() const
Returns the plugin's custom object factory (if any)
virtual QString getName() const =0
Returns (short) name (for menu entry, etc.)
virtual void setIID(const QString &iid)=0
ccPluginManager(QObject *parent=nullptr)
static ccPluginManager & get()
void setPaths(const QStringList &paths)
bool isEnabled(const ccPluginInterface *plugin) const
QStringList pluginPaths()
ccPluginInterfaceList & pluginList()
void setPluginEnabled(const ccPluginInterface *plugin, bool enabled)
Standard ECV plugin interface.
#define ecvApp
Mimic Qt's qApp for easy access to the application instance.
@ ECV_STD_PLUGIN
@ ECV_IO_FILTER_PLUGIN
static QString sPluginIID(QPluginLoader *loader)
static bool sMetaDataValid(QPluginLoader *loader)
Q_GLOBAL_STATIC(PrivatePluginManager, sPluginManager)
QVector< ccPluginInterface * > ccPluginInterfaceList
Simply a list of.
GraphType data
Definition: graph_cut.cc:138
static const std::string path
Definition: PointCloud.cpp:59