kwave  18.07.70
PluginManager.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  PluginManager.cpp - manager class for Kwave's plugins
3  -------------------
4  begin : Sun Aug 27 2000
5  copyright : (C) 2000 by Thomas Eschenbacher
6  email : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <errno.h>
21 #include <unistd.h>
22 
23 #include <QApplication>
24 #include <QDir>
25 #include <QLatin1Char>
26 #include <QLibrary>
27 #include <QLibraryInfo>
28 #include <QMutableListIterator>
29 #include <QtGlobal>
30 #include <QVariantList>
31 
32 #include <KConfig>
33 #include <KConfigGroup>
34 #include <KLocalizedString>
35 #include <KMainWindow>
36 #include <KPluginInfo>
37 #include <KPluginFactory>
38 #include <KPluginTrader>
39 #include <KSharedConfig>
40 
41 #include "libkwave/MessageBox.h"
45 #include "libkwave/Plugin.h"
46 #include "libkwave/PluginManager.h"
47 #include "libkwave/SignalManager.h"
48 #include "libkwave/Utils.h"
49 #include "libkwave/Writer.h"
53 
54 //***************************************************************************
55 // static initializers
56 
57 QMap<QString, Kwave::PluginManager::PluginModule>
59 
61 
62 //***************************************************************************
64  Kwave::SignalManager &signal_manager)
65  :m_plugin_instances(),
66  m_running_plugins(),
67  m_parent_widget(parent),
68  m_signal_manager(signal_manager),
69  m_view_manager(Q_NULLPTR)
70 {
71 }
72 
73 //***************************************************************************
75 {
76  // inform all plugins and client windows that we close now
77  emit sigClosed();
78 
79  // wait until all plugins are really closed
80  this->sync();
81 
82  // give all plugins that still are loaded the chance to do some cleanups
83  // or unregistration tasks. Ideally this should also trigger a "release"
84  // of these remaining plugins, so that afterwards we have no more
85  // plugin instances left.
86  while (!m_plugin_instances.isEmpty()) {
88  Q_ASSERT(p);
89  if (p) p->unload();
90  }
91 
92  // this should make the cleanup handlers run (deferred delete)
93  QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
94 
95  // release all loaded modules
96  foreach (const QString &name, m_plugin_modules.keys()) {
98  p.m_use_count--;
99 
100 // qDebug("PluginManager: releasing module '%s' [refcnt=%d]",
101 // DBG(name), p.m_use_count);
102  if (p.m_use_count == 0) {
103  // take out the pointer to the loadable module
104  KPluginFactory *factory = p.m_factory;
105  p.m_factory = Q_NULLPTR;
106 
107  // remove the module from the list
108  m_plugin_modules.remove(name);
109 
110  // now the handle of the shared object can be released too
111  if (factory) delete factory;
112  } else {
113  // still in use
114  }
115  }
116 
117  // we are no longer the active instance
118  if (m_active_instance == this)
119  m_active_instance = Q_NULLPTR;
120 }
121 
122 //***************************************************************************
124 {
125  // Try to load all plugins. This has to be called only once per
126  // instance of the main window!
127  // NOTE: this also gives each plugin the chance to stay in memory
128  // if necessary (e.g. for codecs)
129  foreach (const QString &name, m_plugin_modules.keys()) {
131  if (plugin) {
132 // qDebug("PluginManager::loadAllPlugins(): plugin '%s'",
133 // DBG(plugin->name()));
134 
135  // get the last settings and call the "load" function
136  // now the plugin is present and loaded
137  QStringList last_params = defaultParams(name);
138  plugin->load(last_params);
139 
140  // reduce use count again, we loaded the plugin only to give
141  // it a chance to register some service if necessary (e.g. a
142  // codec)
143  // Most plugins fall back to use count zero and will be
144  // deleted again.
145  plugin->release();
146  } else {
147  // loading failed => remove it from the list
148  qWarning("PluginManager::loadAllPlugins(): removing '%s' "
149  "from list", DBG(name));
150  m_plugin_modules.remove(name);
151  }
152  }
153 
154  return !m_plugin_modules.isEmpty();
155 }
156 
157 //***************************************************************************
159 {
160  // check: this must be called from the GUI thread only!
161  Q_ASSERT(this->thread() == QThread::currentThread());
162  Q_ASSERT(this->thread() == qApp->thread());
163 
164  if (!m_plugin_instances.isEmpty())
165  foreach (const KwavePluginPointer &plugin, m_plugin_instances)
166  if (plugin && plugin->isRunning())
167  plugin->stop() ;
168 
169  sync();
170 }
171 
172 //***************************************************************************
174 {
175  // check: this must be called from the GUI thread only!
176  Q_ASSERT(this->thread() == QThread::currentThread());
177  Q_ASSERT(this->thread() == qApp->thread());
178 
179  // show an error message and abort if the plugin is unknown
180  if (!(m_plugin_modules.contains(name))) {
182  i18n("The plugin '%1' is unknown or invalid.", name),
183  i18n("Error On Loading Plugin"));
184  return Q_NULLPTR;
185  }
186 
188 // qDebug("loadPlugin(%s) [module use count=%d]",
189 // DBG(name), info.m_use_count);
190 
191  KPluginFactory *factory = info.m_factory;
192  Q_ASSERT(factory);
193 
194  // call the loader function to create an instance
195  QVariantList args;
196  args << info.m_name;
197  args << info.m_description;
198  Kwave::Plugin *plugin = factory->create<Kwave::Plugin>(this, args);
199  Q_ASSERT(plugin);
200  if (!plugin) {
201  qWarning("PluginManager::loadPlugin('%s'): out of memory", DBG(name));
202  return Q_NULLPTR;
203  }
204  // now we have a newly created plugin, the use count is 1
205 
206  // append to our list of loaded plugins
207  m_plugin_instances.append(plugin);
208 
209  // connect all necessary signals/slots
210  connectPlugin(plugin);
211 
212  return plugin;
213 }
214 
215 //***************************************************************************
217  QStringList *params)
218 {
219  QString command;
220  int result = 0;
221 
222  // check: this must be called from the GUI thread only!
223  Q_ASSERT(this->thread() == QThread::currentThread());
224  Q_ASSERT(this->thread() == qApp->thread());
225 
226  // synchronize: wait until any currently running plugins are done
227  this->sync();
228 
229  // load the new plugin
231  if (!plugin) return -ENOMEM;
232 
233  if (params) {
234  // parameters were specified -> call directly
235  // without setup dialog
236  result = plugin->start(*params);
237 
238  // maybe the start() function has called close() ?
239  if (!m_plugin_instances.contains(plugin)) {
240  qDebug("PluginManager: plugin closed itself in start()");
241  result = -1;
242  plugin = Q_NULLPTR;
243  }
244 
245  if (plugin && (result >= 0)) {
246  plugin->execute(*params);
247  }
248  } else {
249  // load previous parameters from config
250  QStringList last_params = defaultParams(name);
251 
252  // call the plugin's setup function
253  params = plugin->setup(last_params);
254  if (params) {
255  // we have a non-zero parameter list, so
256  // the setup function has not been aborted.
257  // Now we can create a command string and
258  // emit a new command.
259 
260  // store parameters for the next time
261  savePluginDefaults(name, *params);
262 
263  // We DO NOT call the plugin's "execute"
264  // function directly, as it should be possible
265  // to record all function calls in the
266  // macro recorder
267  command = _("plugin:execute(");
268  command += name;
269  foreach (const QString &p, *params)
270  command += _(", ") + p;
271  delete params;
272  command += _(")");
273 // qDebug("PluginManager: command='%s'",command.data());
274  }
275  }
276 
277  // now the plugin is no longer needed here, release it
278  if (plugin) plugin->release();
279 
280  // emit a command, let the toplevel window (and macro recorder) get
281  // it and call us again later...
282  if (command.length()) emit sigCommand(command);
283 
284  return result;
285 }
286 
287 //***************************************************************************
289 {
290  // check: this must be called from the GUI thread only!
291  Q_ASSERT(this->thread() == QThread::currentThread());
292  Q_ASSERT(this->thread() == qApp->thread());
293 
294  if (!m_plugin_instances.isEmpty())
295  foreach (const KwavePluginPointer &plugin, m_plugin_instances)
296  if (plugin && !plugin->canClose()) return false;
297 
298  return true;
299 }
300 
301 //***************************************************************************
303 {
304  // check: this must be called from the GUI thread only!
305  Q_ASSERT(this->thread() == QThread::currentThread());
306  Q_ASSERT(this->thread() == qApp->thread());
307 
308  if (!m_plugin_instances.isEmpty())
309  foreach (const KwavePluginPointer &plugin, m_plugin_instances)
310  if (plugin && plugin->isRunning()) return true;
311 
312  return false;
313 }
314 
315 //***************************************************************************
317 {
318  // check: this must be called from the GUI thread only!
319  Q_ASSERT(this->thread() == QThread::currentThread());
320  Q_ASSERT(this->thread() == qApp->thread());
321 
322  // this triggers all kinds of garbage collector (objects queued for
323  // deletion through obj->deleteLater()
324  qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
325 
326  // wait until all plugins have finished their work...
327  while (onePluginRunning()) {
328  Kwave::yield();
329  qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
330  usleep(100000);
331  }
332 }
333 
334 //***************************************************************************
336  const QStringList &params)
337 {
338  // load the plugin
339  Kwave::Plugin *plugin = createPluginInstance(name);
340  if (!plugin) return -ENOMEM;
341 
342  // now the plugin is present and loaded
343  QStringList prev_params = (params.isEmpty()) ?
344  defaultParams(name) : params;
345 
346  // call the plugins' setup function
347  QStringList *new_params = plugin->setup(prev_params);
348  if (new_params) {
349  // we have a non-zero parameter list, so
350  // the setup function has not been aborted.
351  savePluginDefaults(name, *new_params);
352  delete new_params;
353  } else {
354  plugin->release();
355  return 1;
356  }
357 
358  plugin->release();
359  return 0;
360 }
361 
362 //***************************************************************************
363 QStringList Kwave::PluginManager::defaultParams(const QString &name)
364 {
365  QString def_version;
366  QString section = _("plugin ");
367  QStringList list;
368  section += name;
369 
370  // get the plugin version
371  if (!m_plugin_modules.contains(name)) return list;
372  const PluginModule &info = m_plugin_modules[name];
373  QString version = info.m_version;
374 
375  Q_ASSERT(KSharedConfig::openConfig());
376  if (!KSharedConfig::openConfig()) return list;
377  KConfigGroup cfg = KSharedConfig::openConfig()->group(section);
378 
379  cfg.sync();
380 
381  def_version = cfg.readEntry("version");
382  if (!def_version.length()) {
383  return list;
384  }
385  if (!(def_version == version)) {
386  qDebug("PluginManager::defaultParams: "
387  "plugin '%s': defaults for version '%s' not loaded, found "
388  "old ones of version '%s'.",
389  DBG(name), DBG(version), DBG(def_version));
390 
391  // delete the old settings
392  cfg.deleteEntry("version");
393  cfg.deleteEntry("defaults");
394 
395  return list;
396  }
397 
398  list = cfg.readEntry("defaults").split(QLatin1Char(','));
399  return list;
400 }
401 
402 //***************************************************************************
404  QStringList &params)
405 {
406 
407  // get the plugin version
408  if (!m_plugin_modules.contains(name)) return;
409  const PluginModule &info = m_plugin_modules[name];
410  QString version = info.m_version;
411 
412  QString section = _("plugin ");
413  section += name;
414 
415  Q_ASSERT(KSharedConfig::openConfig());
416  if (!KSharedConfig::openConfig()) return;
417  KConfigGroup cfg = KSharedConfig::openConfig()->group(section);
418 
419  cfg.sync();
420  cfg.writeEntry("version", version);
421  cfg.writeEntry("defaults", params.join(QLatin1Char(',')));
422  cfg.sync();
423 }
424 
425 //***************************************************************************
427 {
428  return m_signal_manager.length();
429 }
430 
431 //***************************************************************************
433 {
434  return m_signal_manager.rate();
435 }
436 
437 //***************************************************************************
439 {
440  return m_signal_manager.selection().first();
441 }
442 
443 //***************************************************************************
445 {
446  return m_signal_manager.selection().last();
447 }
448 
449 //***************************************************************************
451  sample_index_t length)
452 {
453  m_signal_manager.selectRange(offset, length);
454 }
455 
456 //***************************************************************************
458  unsigned int tracks,
459  const Kwave::PlayBackParam *playback_params
460 )
461 {
462  Kwave::PlayBackDevice *device =
464  tracks, playback_params);
465  if (!device) return Q_NULLPTR;
466 
467  // create the multi track playback sink
468  Kwave::SampleSink *sink = new Kwave::MultiPlaybackSink(tracks, device);
469  Q_ASSERT(sink);
470  return sink;
471 }
472 
473 //***************************************************************************
475 {
477 }
478 
479 //***************************************************************************
481 {
482  if (m_view_manager)
483  m_view_manager->insertView(view, controls);
484 }
485 
486 //***************************************************************************
488 {
489  Q_ASSERT(!view_manager || !m_view_manager);
490  m_view_manager = view_manager;
491 }
492 
493 //***************************************************************************
494 void Kwave::PluginManager::enqueueCommand(const QString &command)
495 {
496  emit sigCommand(command);
497 }
498 
499 //***************************************************************************
501 {
502  emit sigClosed();
503 }
504 
505 //***************************************************************************
507 {
508  // check: this must be called from the GUI thread only!
509  Q_ASSERT(this->thread() == QThread::currentThread());
510  Q_ASSERT(this->thread() == qApp->thread());
511 
512  Q_ASSERT(p);
513  if (!p) return;
514 
515  // disconnect the signals to avoid recursion
516  disconnectPlugin(p);
517 
518  if (m_plugin_instances.contains(p))
519  m_plugin_instances.removeAll(p);
520 
521  // schedule the deferred delete/unload of the plugin
522  p->deleteLater();
523 }
524 
525 //***************************************************************************
527 {
528  Q_ASSERT(p);
529  if (!p) return;
530 
531  // the plugin is running -> increase the usage count in order to
532  // prevent our lists from containing invalid entries
533  p->use();
534 
535  // add the plugin to the list of running plugins
536  m_running_plugins.append(p);
537 }
538 
539 //***************************************************************************
541 {
542  // check: this must be called from the GUI thread only!
543  Q_ASSERT(this->thread() == QThread::currentThread());
544  Q_ASSERT(this->thread() == qApp->thread());
545 
546  Q_ASSERT(p);
547  if (!p) return;
548 
549  // remove the plugin from the list of running plugins
550  m_running_plugins.removeAll(p);
551 
552  // release the plugin, at least we do no longer need it
553  p->release();
554 }
555 
556 //***************************************************************************
558 {
559  Q_ASSERT(plugin);
560  if (!plugin) return;
561 
562  connect(this, SIGNAL(sigClosed()),
563  plugin, SLOT(close()));
564 
565  connect(plugin, SIGNAL(sigClosed(Kwave::Plugin*)),
566  this, SLOT(pluginClosed(Kwave::Plugin*)),
567  Qt::QueuedConnection);
568 
569  connect(plugin, SIGNAL(sigRunning(Kwave::Plugin*)),
570  this, SLOT(pluginStarted(Kwave::Plugin*)),
571  Qt::DirectConnection);
572 
573  connect(plugin, SIGNAL(sigDone(Kwave::Plugin*)),
574  this, SLOT(pluginDone(Kwave::Plugin*)),
575  Qt::QueuedConnection);
576 }
577 
578 //***************************************************************************
580 {
581  Q_ASSERT(plugin);
582  if (!plugin) return;
583 
584  disconnect(plugin, SIGNAL(sigDone(Kwave::Plugin*)),
585  this, SLOT(pluginDone(Kwave::Plugin*)));
586 
587  disconnect(plugin, SIGNAL(sigRunning(Kwave::Plugin*)),
588  this, SLOT(pluginStarted(Kwave::Plugin*)));
589 
590  disconnect(this, SIGNAL(sigClosed()),
591  plugin, SLOT(close()));
592 
593  disconnect(plugin, SIGNAL(sigClosed(Kwave::Plugin*)),
594  this, SLOT(pluginClosed(Kwave::Plugin*)));
595 
596 }
597 
598 //***************************************************************************
600 {
601  emit sigSignalNameChanged(name);
602 }
603 
604 //***************************************************************************
606 {
607  if (!m_plugin_modules.isEmpty()) {
608  // this is not the first call -> increment module use count only
609  foreach (const QString &name, m_plugin_modules.keys()) {
611  p.m_use_count++;
612  }
613  return;
614  }
615 
616  KPluginInfo::List plugins = KPluginTrader::self()->query(
617  _("kwave"), _("Kwave/Plugin")
618  );
619  foreach (const KPluginInfo &i, plugins) {
620  QString library = i.libraryPath();
621  QString description = i.name();
622  QString name = i.pluginName();
623  QString version_raw = i.version();
624  QString version;
625  QString settings;
626  QString author = i.author();
627 
628  if (version_raw.contains(_(":"))) {
629  version = version_raw.split(_(":")).at(0);
630  settings = version_raw.split(_(":")).at(1);
631  }
632 
633 // qDebug("file='%s', name='%s', description='%s', binary_version='%s', "
634 // "settings_version='%s', author='%s'",
635 // DBG(library), DBG(name), DBG(description), DBG(version),
636 // DBG(settings), DBG(author)
637 // );
638 
639  if ( library.isEmpty() || description.isEmpty() ||
640  name.isEmpty() || version.isEmpty() ) {
641  qWarning("plugin '%s' has no library, name or version", DBG(name));
642  continue;
643  }
644 
645  if (version != _(KWAVE_VERSION)) {
646  qWarning("plugin '%s' has wrong ABI version: '%s' (should be %s)",
647  DBG(name), DBG(version), KWAVE_VERSION);
648  continue;
649  }
650 
651  KPluginLoader loader(library);
652  KPluginFactory* factory = loader.factory();
653  if (!factory) {
654  qWarning("plugin '%s': loading failed", DBG(name));
655  continue;
656  }
657 
658  emit sigProgress(i18n("Loading plugin %1...", name));
659  QApplication::processEvents();
660 
661  PluginModule info;
662  info.m_name = name;
663  info.m_author = author;
664  info.m_description = i18n(description.toUtf8());
665  info.m_version = settings;
666  info.m_factory = factory;
667  info.m_use_count = 1;
668 
669  m_plugin_modules.insert(info.m_name, info);
670 
671  qDebug("%16s %5s written by %s", DBG(name), DBG(settings), DBG(author));
672  }
673 
674  qDebug("--- \n found %d plugins\n", m_plugin_modules.count());
675 }
676 
677 //***************************************************************************
678 const QList<Kwave::PluginManager::PluginModule>
680 {
681  return m_plugin_modules.values();
682 }
683 
684 //***************************************************************************
686 {
687  // check: this must be called from the GUI thread only!
688  Q_ASSERT(this->thread() == QThread::currentThread());
689  Q_ASSERT(this->thread() == qApp->thread());
690 
691  Q_ASSERT(plugin);
692  if (!plugin) return;
693  if (m_active_instance == this) return; // nothing to do
694  Q_ASSERT(m_active_instance);
695  if (!m_active_instance) return; // should never happen
696 
697  Kwave::PluginManager *old_mgr = this;
698  old_mgr->m_plugin_instances.removeAll(plugin);
699  old_mgr->m_running_plugins.removeAll(plugin);
700  old_mgr->disconnectPlugin(plugin);
701 
703  new_mgr->m_plugin_instances.append(plugin);
704  new_mgr->m_running_plugins.append(plugin);
705  new_mgr->connectPlugin(plugin);
706 
707  plugin->setPluginManager(new_mgr);
708 }
709 
710 //***************************************************************************
711 //***************************************************************************
void sigCommand(const QString &command)
void pluginClosed(Kwave::Plugin *p)
void selectRange(sample_index_t offset, sample_index_t length)
sample_index_t first() const
Definition: Selection.h:71
void sigProgress(const QString &message)
QPointer< Kwave::Plugin > KwavePluginPointer
const QList< PluginModule > pluginInfoList() const
void enqueueCommand(const QString &command)
void registerViewManager(Kwave::ViewManager *view_manager)
void savePluginDefaults(const QString &name, QStringList &params)
sample_index_t last() const
Definition: Selection.h:76
Kwave::SampleSink * openMultiTrackPlayback(unsigned int tracks, const Kwave::PlayBackParam *playback_params=Q_NULLPTR)
ViewManager * m_view_manager
Kwave::PlaybackController & playbackController()
void setPluginManager(Kwave::PluginManager *new_plugin_manager)
Definition: Plugin.cpp:522
Kwave::Selection & selection()
void release()
Definition: Plugin.cpp:420
virtual void insertView(Kwave::SignalView *view, QWidget *controls)=0
int setupPlugin(const QString &name, const QStringList &params)
quint64 sample_index_t
Definition: Sample.h:28
Kwave::PlaybackController & playbackController()
void pluginStarted(Kwave::Plugin *p)
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
Kwave::Plugin * createPluginInstance(const QString &name)
PluginList m_plugin_instances
const char name[16]
Definition: memcpy.c:510
void migratePluginToActiveContext(Kwave::Plugin *plugin)
void connectPlugin(Kwave::Plugin *plugin)
static Kwave::PluginManager * m_active_instance
sample_index_t signalLength()
static int error(QWidget *widget, QString message, QString caption=QString())
Definition: MessageBox.cpp:126
void disconnectPlugin(Kwave::Plugin *plugin)
int executePlugin(const QString &name, QStringList *params)
sample_index_t length()
double rate() const
Kwave::SignalManager & m_signal_manager
static QMap< QString, PluginModule > m_plugin_modules
sample_index_t selectionStart()
#define _(m)
Definition: memcpy.c:66
#define DBG(qs)
Definition: String.h:55
void setSignalName(const QString &name)
void sigSignalNameChanged(const QString &name)
void Q_DECL_EXPORT yield()
Definition: Utils.cpp:39
Kwave::PlayBackDevice * openDevice(int tracks, const Kwave::PlayBackParam *playback_params)
void use()
Definition: Plugin.cpp:412
QPointer< QWidget > m_parent_widget
sample_index_t selectionEnd()
void insertView(Kwave::SignalView *view, QWidget *controls)
void pluginDone(Kwave::Plugin *p)
PluginList m_running_plugins
PluginManager(QWidget *parent, Kwave::SignalManager &signal_manager)
virtual QStringList * setup(QStringList &previous_params)
Definition: Plugin.cpp:134
QStringList defaultParams(const QString &name)
void selectRange(sample_index_t offset, sample_index_t length)