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