pluginmanager.cpp
1
22#include "pluginmanager.h"
23
24#include "plugin.h"
25
26#include <tqapplication.h>
27#include <tqfile.h>
28#include <tqregexp.h>
29#include <tqtimer.h>
30#include <tqvaluestack.h>
31
32#include <tdeapplication.h>
33#include <kdebug.h>
34#include <tdeparts/componentfactory.h>
35#include <kplugininfo.h>
36#include <ksettings/dispatcher.h>
37#include <ksimpleconfig.h>
38#include <tdestandarddirs.h>
39#include <kstaticdeleter.h>
40#include <kurl.h>
41
42
43namespace Komposer
44{
45
46class PluginManager::Private
47{
48public:
49 // All available plugins, regardless of category, and loaded or not
50 TQValueList<KPluginInfo*> plugins;
51
52 // Dict of all currently loaded plugins, mapping the KPluginInfo to
53 // a plugin
54 TQMap<KPluginInfo*, Plugin*> loadedPlugins;
55
56 // The plugin manager's mode. The mode is StartingUp until loadAllPlugins()
57 // has finished loading the plugins, after which it is set to Running.
58 // ShuttingDown and DoneShutdown are used during Komposer shutdown by the
59 // async unloading of plugins.
60 enum ShutdownMode { StartingUp, Running, ShuttingDown, DoneShutdown };
61 ShutdownMode shutdownMode;
62
63 TDESharedConfig::Ptr config;
64 // Plugins pending for loading
65 TQValueStack<TQString> pluginsToLoad;
66};
67
68PluginManager::PluginManager( TQObject *parent )
69 : TQObject( parent )
70{
71 d = new Private;
72
73 // We want to add a reference to the application's event loop so we
74 // can remain in control when all windows are removed.
75 // This way we can unload plugins asynchronously, which is more
76 // robust if they are still doing processing.
77 tdeApp->ref();
78 d->shutdownMode = Private::StartingUp;
79
80 KSettings::Dispatcher::self()->registerInstance( TDEGlobal::instance(),
81 this, TQ_SLOT( loadAllPlugins() ) );
82
83 d->plugins = KPluginInfo::fromServices(
84 TDETrader::self()->query( TQString::fromLatin1( "Komposer/Plugin" ),
85 TQString::fromLatin1( "[X-Komposer-Version] == 1" ) ) );
86}
87
88PluginManager::~PluginManager()
89{
90 if ( d->shutdownMode != Private::DoneShutdown ) {
91 slotShutdownTimeout();
92#if 0
93 kdWarning() << k_funcinfo
94 << "Destructing plugin manager without going through "
95 << "the shutdown process!"
96 << endl
97 << kdBacktrace(10) << endl;
98#endif
99 }
100
101 // Quick cleanup of the remaining plugins, hope it helps
102 TQMap<KPluginInfo*, Plugin*>::ConstIterator it;
103 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); /* EMPTY */ )
104 {
105 // Remove causes the iterator to become invalid, so pre-increment first
106 TQMap<KPluginInfo*, Plugin*>::ConstIterator nextIt( it );
107 ++nextIt;
108 kdWarning() << k_funcinfo << "Deleting stale plugin '"
109 << it.data()->name() << "'" << endl;
110 delete it.data();
111 it = nextIt;
112 }
113
114 delete d;
115}
116
117TQValueList<KPluginInfo*>
118PluginManager::availablePlugins( const TQString &category ) const
119{
120 if ( category.isEmpty() )
121 return d->plugins;
122
123 TQValueList<KPluginInfo*> result;
124 TQValueList<KPluginInfo*>::ConstIterator it;
125 for ( it = d->plugins.begin(); it != d->plugins.end(); ++it )
126 {
127 if ( ( *it )->category() == category )
128 result.append( *it );
129 }
130
131 return result;
132}
133
134TQMap<KPluginInfo*, Plugin*>
135PluginManager::loadedPlugins( const TQString &category ) const
136{
137 TQMap<KPluginInfo*, Plugin*> result;
138 TQMap<KPluginInfo*, Plugin*>::ConstIterator it;
139 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
140 {
141 if ( category.isEmpty() || it.key()->category() == category )
142 result.insert( it.key(), it.data() );
143 }
144
145 return result;
146}
147
148void
149PluginManager::shutdown()
150{
151 d->shutdownMode = Private::ShuttingDown;
152
153 // Remove any pending plugins to load, we're shutting down now :)
154 d->pluginsToLoad.clear();
155
156 // Ask all plugins to unload
157 if ( d->loadedPlugins.empty() ) {
158 d->shutdownMode = Private::DoneShutdown;
159 } else {
160 TQMap<KPluginInfo*, Plugin*>::ConstIterator it;
161 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); /* EMPTY */ )
162 {
163 // Remove causes the iterator to become invalid, so pre-increment first
164 TQMap<KPluginInfo*, Plugin*>::ConstIterator nextIt( it );
165 ++nextIt;
166 it.data()->aboutToUnload();
167 it = nextIt;
168 }
169 }
170
171 TQTimer::singleShot( 3000, this, TQ_SLOT(slotShutdownTimeout()) );
172}
173
174void
175PluginManager::slotPluginReadyForUnload()
176{
177 // Using TQObject::sender() is on purpose here, because otherwise all
178 // plugins would have to pass 'this' as parameter, which makes the API
179 // less clean for plugin authors
180 Plugin* plugin = dynamic_cast<Plugin*>( const_cast<TQObject*>( sender() ) );
181 if ( !plugin )
182 {
183 kdWarning() << k_funcinfo << "Calling object is not a plugin!" << endl;
184 return;
185
186 }
187 kdDebug()<<"manager unloading"<<endl;
188 plugin->deleteLater();
189}
190
191void
192PluginManager::slotShutdownTimeout()
193{
194 // When we were already done the timer might still fire.
195 // Do nothing in that case.
196 if ( d->shutdownMode == Private::DoneShutdown )
197 return;
198
199#ifndef NDEBUG
200 TQStringList remaining;
201 for ( TQMap<KPluginInfo*, Plugin*>::ConstIterator it = d->loadedPlugins.begin();
202 it != d->loadedPlugins.end(); ++it )
203 remaining.append( it.key()->pluginName() );
204
205 kdWarning() << k_funcinfo << "Some plugins didn't shutdown in time!" << endl
206 << "Remaining plugins: "
207 << remaining.join( TQString::fromLatin1( ", " ) ) << endl
208 << "Forcing Komposer shutdown now." << endl;
209#endif
210
211 slotShutdownDone();
212}
213
214void
215PluginManager::slotShutdownDone()
216{
217 d->shutdownMode = Private::DoneShutdown;
218
219 tdeApp->deref();
220}
221
222void
223PluginManager::loadAllPlugins()
224{
225 // FIXME: We need session management here - Martijn
226
227 if ( !d->config )
228 d->config = TDESharedConfig::openConfig( "komposerrc" );
229
230 TQMap<TQString, TQString> entries = d->config->entryMap(
231 TQString::fromLatin1( "Plugins" ) );
232
233 TQMap<TQString, TQString>::Iterator it;
234 for ( it = entries.begin(); it != entries.end(); ++it )
235 {
236 TQString key = it.key();
237 if ( key.endsWith( TQString::fromLatin1( "Enabled" ) ) )
238 {
239 key.setLength( key.length() - 7 );
240 //kdDebug() << k_funcinfo << "Set " << key << " to " << it.data() << endl;
241
242 if ( it.data() == TQString::fromLatin1( "true" ) )
243 {
244 if ( !plugin( key ) )
245 d->pluginsToLoad.push( key );
246 }
247 else
248 {
249 // FIXME: Does this ever happen? As loadAllPlugins is only called on startup
250 // I'd say 'no'. If it does, it should be made async
251 // though. - Martijn
252 if ( plugin( key ) )
253 unloadPlugin( key );
254 }
255 }
256 }
257
258 // Schedule the plugins to load
259 TQTimer::singleShot( 0, this, TQ_SLOT( slotLoadNextPlugin() ) );
260}
261
262void PluginManager::slotLoadNextPlugin()
263{
264 if ( d->pluginsToLoad.isEmpty() )
265 {
266 if ( d->shutdownMode == Private::StartingUp )
267 {
268 d->shutdownMode = Private::Running;
269 emit allPluginsLoaded();
270 }
271 return;
272 }
273
274 TQString key = d->pluginsToLoad.pop();
275 loadPluginInternal( key );
276
277 // Schedule the next run unconditionally to avoid code duplication on the
278 // allPluginsLoaded() signal's handling. This has the added benefit that
279 // the signal is delayed one event loop, so the accounts are more likely
280 // to be instantiated.
281 TQTimer::singleShot( 0, this, TQ_SLOT( slotLoadNextPlugin() ) );
282}
283
284Plugin*
285PluginManager::loadPlugin( const TQString &pluginId,
286 PluginLoadMode mode /* = LoadSync */ )
287{
288 if ( mode == LoadSync ) {
289 return loadPluginInternal( pluginId );
290 } else {
291 d->pluginsToLoad.push( pluginId );
292 TQTimer::singleShot( 0, this, TQ_SLOT( slotLoadNextPlugin() ) );
293 return 0;
294 }
295}
296
297Plugin*
298PluginManager::loadPluginInternal( const TQString &pluginId )
299{
300 KPluginInfo* info = infoForPluginId( pluginId );
301 if ( !info ) {
302 kdWarning() << k_funcinfo << "Unable to find a plugin named '"
303 << pluginId << "'!" << endl;
304 return 0;
305 }
306
307 if ( d->loadedPlugins.contains( info ) )
308 return d->loadedPlugins[ info ];
309
310 int error = 0;
311 Plugin *plugin = KParts::ComponentFactory::createInstanceFromQuery<Komposer::Plugin>(
312 TQString::fromLatin1( "Komposer/Plugin" ),
313 TQString::fromLatin1( "[X-TDE-PluginInfo-Name]=='%1'" ).arg( pluginId ),
314 this, 0, TQStringList(), &error );
315
316 if ( plugin ) {
317 d->loadedPlugins.insert( info, plugin );
318 info->setPluginEnabled( true );
319
320 connect( plugin, TQ_SIGNAL(destroyed(TQObject*)),
321 this, TQ_SLOT(slotPluginDestroyed(TQObject*)) );
322 connect( plugin, TQ_SIGNAL(readyForUnload()),
323 this, TQ_SLOT(slotPluginReadyForUnload()) );
324
325 kdDebug() << k_funcinfo << "Successfully loaded plugin '"
326 << pluginId << "'" << endl;
327
328 emit pluginLoaded( plugin );
329 } else {
330 switch ( error ) {
331 case KParts::ComponentFactory::ErrNoServiceFound:
332 kdDebug() << k_funcinfo << "No service implementing the given mimetype "
333 << "and fullfilling the given constraint expression can be found."
334 << endl;
335 break;
336
337 case KParts::ComponentFactory::ErrServiceProvidesNoLibrary:
338 kdDebug() << "the specified service provides no shared library." << endl;
339 break;
340
341 case KParts::ComponentFactory::ErrNoLibrary:
342 kdDebug() << "the specified library could not be loaded." << endl;
343 break;
344
345 case KParts::ComponentFactory::ErrNoFactory:
346 kdDebug() << "the library does not export a factory for creating components."
347 << endl;
348 break;
349
350 case KParts::ComponentFactory::ErrNoComponent:
351 kdDebug() << "the factory does not support creating components "
352 << "of the specified type."
353 << endl;
354 break;
355 }
356
357 kdDebug() << k_funcinfo << "Loading plugin '" << pluginId
358 << "' failed, KLibLoader reported error: '"
359 << KLibLoader::self()->lastErrorMessage()
360 << "'" << endl;
361 }
362
363 return plugin;
364}
365
366bool
367PluginManager::unloadPlugin( const TQString &spec )
368{
369 TQMap<KPluginInfo*, Plugin*>::ConstIterator it;
370 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
371 {
372 if ( it.key()->pluginName() == spec )
373 {
374 it.data()->aboutToUnload();
375 return true;
376 }
377 }
378
379 return false;
380}
381
382void
383PluginManager::slotPluginDestroyed( TQObject *plugin )
384{
385 TQMap<KPluginInfo*, Plugin*>::Iterator it;
386 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
387 {
388 if ( it.data() == plugin )
389 {
390 d->loadedPlugins.erase( it );
391 break;
392 }
393 }
394
395 if ( d->shutdownMode == Private::ShuttingDown && d->loadedPlugins.isEmpty() )
396 {
397 // Use a timer to make sure any pending deleteLater() calls have
398 // been handled first
399 TQTimer::singleShot( 0, this, TQ_SLOT(slotShutdownDone()) );
400 }
401}
402
403Plugin*
404PluginManager::plugin( const TQString &pluginId ) const
405{
406 KPluginInfo *info = infoForPluginId( pluginId );
407 if ( !info )
408 return 0;
409
410 if ( d->loadedPlugins.contains( info ) )
411 return d->loadedPlugins[ info ];
412 else
413 return 0;
414}
415
416TQString
417PluginManager::pluginName( const Plugin *plugin ) const
418{
419 TQMap<KPluginInfo*, Plugin*>::ConstIterator it;
420 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
421 {
422 if ( it.data() == plugin )
423 return it.key()->name();
424 }
425
426 return TQString::fromLatin1( "Unknown" );
427}
428
429TQString
430PluginManager::pluginId( const Plugin *plugin ) const
431{
432 TQMap<KPluginInfo*, Plugin*>::ConstIterator it;
433 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
434 {
435 if ( it.data() == plugin )
436 return it.key()->pluginName();
437 }
438
439 return TQString::fromLatin1( "unknown" );
440}
441
442TQString
443PluginManager::pluginIcon( const Plugin *plugin ) const
444{
445 TQMap<KPluginInfo*, Plugin*>::ConstIterator it;
446 for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
447 {
448 if ( it.data() == plugin )
449 return it.key()->icon();
450 }
451
452 return TQString::fromLatin1( "Unknown" );
453}
454
455KPluginInfo*
456PluginManager::infoForPluginId( const TQString &pluginId ) const
457{
458 TQValueList<KPluginInfo*>::ConstIterator it;
459 for ( it = d->plugins.begin(); it != d->plugins.end(); ++it )
460 {
461 if ( ( *it )->pluginName() == pluginId )
462 return *it;
463 }
464
465 return 0;
466}
467
468bool
469PluginManager::setPluginEnabled( const TQString &pluginId, bool enabled /* = true */ )
470{
471 if ( !d->config )
472 d->config = TDESharedConfig::openConfig( "komposerrc" );
473
474 d->config->setGroup( "Plugins" );
475
476
477 if ( !infoForPluginId( pluginId ) )
478 return false;
479
480 d->config->writeEntry( pluginId + TQString::fromLatin1( "Enabled" ), enabled );
481 d->config->sync();
482
483 return true;
484}
485
486}
487
488#include "pluginmanager.moc"
attachment.h
Definition: attachment.h:29