pluginloader.cpp
1 //------------------------------------------------------------------------------
2 // pluginloader.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2011 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "pluginloader.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "ini/inisection.h"
27 #include "ini/inivariable.h"
28 #include "log.h"
29 #include "plugins/engineplugin.h"
30 #include "serverapi/masterclient.h"
31 #include "strings.hpp"
32 #include <cassert>
33 #include <QDir>
34 #include <QLibrary>
35 #include <QScopedPointer>
36 
38 DClass<PluginLoader::Plugin>
39 {
40 public:
41  EnginePlugin *info;
42  QString file;
43  QScopedPointer<QLibrary> library;
44 
45  static bool isForbiddenPlugin(QString file)
46  {
47  return QFileInfo(file).fileName().toLower().contains("vavoom");
48  }
49 };
50 
51 DPointeredNoCopy(PluginLoader::Plugin)
52 
53 PluginLoader::Plugin::Plugin(unsigned int type, QString file)
54 {
55  Q_UNUSED(type); // THIS SHOULD BE REVIEWED. TYPE SEEMS USELESS.
56  d->file = file;
57  d->info = nullptr;
59  {
60  gLog << QObject::tr("Skipping loading of forbidden plugin: %1").arg(file);
61  return;
62  }
63 
64  // Load the library
65  d->library.reset(new QLibrary(file));
66  if (d->library->load())
67  {
68  auto doomSeekerABI = (unsigned int (*)())(d->library->resolve("doomSeekerABI"));
69  if (!doomSeekerABI || doomSeekerABI() != DOOMSEEKER_ABI_VERSION)
70  {
71  // Unsupported version
72  QString reason;
73  if (doomSeekerABI != nullptr)
74  {
75  reason = QObject::tr(
76  "plugin ABI version mismatch; plugin: %1, Doomseeker: %2").arg(
77  doomSeekerABI()).arg(DOOMSEEKER_ABI_VERSION);
78  }
79  else
80  {
81  reason = QObject::tr("plugin doesn't report its ABI version");
82  }
83  gLog << QObject::tr("Cannot load plugin %1, reason: %2.").arg(file, reason);
84  unload();
85  return;
86  }
87 
88  auto doomSeekerInit = (EnginePlugin * (*)())(d->library->resolve("doomSeekerInit"));
89  if (doomSeekerInit == nullptr)
90  { // This is not a valid plugin.
91  unload();
92  return;
93  }
94 
95  d->info = doomSeekerInit();
96  if (!info()->data()->valid)
97  {
98  unload();
99  return;
100  }
101 
102  gLog << QObject::tr("Loaded plugin: \"%1\"!").arg(info()->data()->name);
103  d->info->start();
104  }
105  else
106  {
107  gLog << QObject::tr("Failed to open plugin: %1").arg(file);
108  gLog << QString("Last error was: %1").arg(d->library->errorString());
109  }
110 }
111 
112 PluginLoader::Plugin::~Plugin()
113 {
114  unload();
115 }
116 
117 void *PluginLoader::Plugin::function(const char *func) const
118 {
119  return (void *) d->library->resolve(func);
120 }
121 
123 {
124  return d->info;
125 }
126 
127 void PluginLoader::Plugin::initConfig()
128 {
129  if (isValid())
130  {
131  IniSection cfgSection = gConfig.iniSectionForPlugin(info()->data()->name);
132  info()->setConfig(cfgSection);
133  }
134 }
135 
136 bool PluginLoader::Plugin::isValid() const
137 {
138  return d->library != nullptr && d->library->isLoaded();
139 }
140 
141 void PluginLoader::Plugin::unload()
142 {
143  d->library.reset();
144 }
145 
147 DClass<PluginLoader>
148 {
149 public:
150  unsigned int type;
151  QString pluginsDirectory;
152  QList<PluginLoader::Plugin *> plugins;
153 };
154 
155 DPointeredNoCopy(PluginLoader)
156 
157 PluginLoader *PluginLoader::staticInstance = nullptr;
158 
159 PluginLoader::PluginLoader(unsigned int type, const QStringList &directories)
160 {
161  d->type = type;
162  for (const QString &dir : directories)
163  {
164  d->pluginsDirectory = dir;
165  if (filesInDir())
166  {
167  break;
168  }
169  }
170  if (numPlugins() == 0) // No plugins?!
171  {
172  gLog << QObject::tr("Failed to locate plugins.");
173  }
174 }
175 
176 PluginLoader::~PluginLoader()
177 {
178  qDeleteAll(d->plugins);
179 }
180 
182 {
183  qDeleteAll(d->plugins);
184  d->plugins.clear();
185 }
186 
188 {
189  if (staticInstance != nullptr)
190  {
191  delete staticInstance;
192  staticInstance = nullptr;
193  }
194 }
195 
196 bool PluginLoader::filesInDir()
197 {
198  gLog << QString("Attempting to load plugins from directory: %1").arg(d->pluginsDirectory);
199  QDir dir(d->pluginsDirectory);
200  if (!dir.exists())
201  {
202  return false;
203  }
204  #ifdef Q_OS_WIN32
205  QStringList windowsNamesFilter;
206  windowsNamesFilter << "*.dll";
207  dir.setNameFilters(windowsNamesFilter);
208  #endif
209  for (const QString &entry : dir.entryList(QDir::Files))
210  {
211  QString pluginFilePath = Strings::combinePaths(d->pluginsDirectory, entry);
212  Plugin *plugin = new Plugin(d->type, pluginFilePath);
213  if (plugin->isValid())
214  d->plugins << plugin;
215  else
216  delete plugin;
217  }
218  return numPlugins() != 0;
219 }
220 
221 EnginePlugin *PluginLoader::info(int pluginIndex) const
222 {
223  const Plugin *p = plugin(pluginIndex);
224  if (p != nullptr)
225  {
226  return p->info();
227  }
228  return nullptr;
229 }
230 
231 void PluginLoader::init(const QStringList &directories)
232 {
233  if (staticInstance != nullptr)
234  {
235  qDebug() << "Attempting to re-init PluginLoader";
236  assert(false);
237  return;
238  }
239  staticInstance = new PluginLoader(MAKEID('E', 'N', 'G', 'N'), directories);
240 }
241 
243 {
244  for (Plugin *plugin : d->plugins)
245  {
246  plugin->initConfig();
247  }
248 }
249 
251 {
252  assert(staticInstance != nullptr);
253  return staticInstance;
254 }
255 
256 unsigned int PluginLoader::numPlugins() const
257 {
258  return d->plugins.size();
259 }
260 
261 const QList<PluginLoader::Plugin *> &PluginLoader::plugins() const
262 {
263  return d->plugins;
264 }
265 
266 const PluginLoader::Plugin *PluginLoader::plugin(unsigned int index) const
267 {
268  return d->plugins[index];
269 }
270 
271 int PluginLoader::pluginIndexFromName(const QString &name) const
272 {
273  // Why the mangling?
274  // Ever since version 0.8.1b there was a bug that removed all spacebars
275  // from plugin names. This bug is fixed in a commit made on 2013-11-01,
276  // but the fix breaks at least some parts of configuration for plugins
277  // that have spacebars in their names. For example, all server
278  // configurations for Chocolate Doom won't load anymore. To prevent that,
279  // we need to treat spacebars as non-existent here. Simply put:
280  // "Chocolate Doom" == "ChocolateDoom"
281  QString mangledName = QString(name).replace(" ", "");
282  for (int i = 0; i < d->plugins.size(); ++i)
283  {
284  QString mangledCandidate = QString(d->plugins[i]->info()->data()->name).replace(" ", "");
285  if (mangledName.compare(mangledCandidate) == 0)
286  {
287  return i;
288  }
289  }
290 
291  return -1;
292 }
293 
294 void PluginLoader::resetPluginsDirectory(const QString &pluginsDirectory)
295 {
296  d->pluginsDirectory = pluginsDirectory;
297  clearPlugins();
298  filesInDir();
299 }
300 
301 const PluginLoader::Plugin *PluginLoader::operator[] (unsigned int index) const
302 {
303  return d->plugins[index];
304 }