2 kopetepluginmanager.cpp - Kopete Plugin Loader
4 Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett <duncan@kde.org>
5 Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
6 Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @tiscalinet.be>
8 Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org>
10 *************************************************************************
12 * This library is free software; you can redistribute it and/or *
13 * modify it under the terms of the GNU Lesser General Public *
14 * License as published by the Free Software Foundation; either *
15 * version 2 of the License, or (at your option) any later version. *
17 *************************************************************************
20 #include "config-kopete.h"
22 #include "kopetepluginmanager.h"
24 #if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__)
25 // We don't want the per-skin includes, so pretend we have a skin header already
26 #define __VALGRIND_SOMESKIN_H
27 #include <valgrind/valgrind.h>
30 #include <QApplication>
36 #include <ksharedconfig.h>
38 #include <kparts/componentfactory.h>
39 #include <kplugininfo.h>
41 #include <kstandarddirs.h>
43 #include <kservicetypetrader.h>
45 #include "kopeteplugin.h"
46 #include "kopetecontactlist.h"
47 #include "kopeteaccountmanager.h"
52 class PluginManagerPrivate
55 PluginManagerPrivate() : shutdownMode( StartingUp
), isAllPluginsLoaded(false)
57 plugins
= KPluginInfo::fromServices( KServiceTypeTrader::self()->query( QLatin1String( "Kopete/Plugin" ), QLatin1String( "[X-Kopete-Version] == 1000900" ) ) );
60 ~PluginManagerPrivate()
62 if ( shutdownMode
!= DoneShutdown
)
63 kWarning( 14010 ) << "Destructing plugin manager without going through the shutdown process! Backtrace is: " << endl
<< kBacktrace();
65 // Clean up loadedPlugins manually, because PluginManager can't access our global
66 // static once this destructor has started.
67 while ( !loadedPlugins
.empty() )
69 InfoToPluginMap::ConstIterator it
= loadedPlugins
.constBegin();
70 kWarning( 14010 ) << "Deleting stale plugin '" << it
.value()->objectName() << "'";
71 KPluginInfo info
= it
.key();
72 Plugin
*plugin
= it
.value();
73 loadedPlugins
.remove(info
);
74 plugin
->disconnect(&instance
, SLOT(slotPluginDestroyed(QObject
*)));
79 // All available plugins, regardless of category, and loaded or not
80 QList
<KPluginInfo
> plugins
;
82 // Dict of all currently loaded plugins, mapping the KPluginInfo to
84 typedef QMap
<KPluginInfo
, Plugin
*> InfoToPluginMap
;
85 InfoToPluginMap loadedPlugins
;
87 // The plugin manager's mode. The mode is StartingUp until loadAllPlugins()
88 // has finished loading the plugins, after which it is set to Running.
89 // ShuttingDown and DoneShutdown are used during Kopete shutdown by the
90 // async unloading of plugins.
91 enum ShutdownMode
{ StartingUp
, Running
, ShuttingDown
, DoneShutdown
};
92 ShutdownMode shutdownMode
;
94 // Plugins pending for loading
95 QStack
<QString
> pluginsToLoad
;
97 bool isAllPluginsLoaded
;
98 PluginManager instance
;
101 K_GLOBAL_STATIC(PluginManagerPrivate
, _kpmp
)
103 PluginManager
* PluginManager::self()
105 return &_kpmp
->instance
;
108 PluginManager::PluginManager() : QObject( 0 )
110 // We want to add a reference to the application's event loop so we
111 // can remain in control when all windows are removed.
112 // This way we can unload plugins asynchronously, which is more
113 // robust if they are still doing processing.
117 PluginManager::~PluginManager()
121 QList
<KPluginInfo
> PluginManager::availablePlugins( const QString
&category
) const
123 if ( category
.isEmpty() )
124 return _kpmp
->plugins
;
126 QList
<KPluginInfo
> result
;
127 QList
<KPluginInfo
>::ConstIterator it
;
128 for ( it
= _kpmp
->plugins
.constBegin(); it
!= _kpmp
->plugins
.constEnd(); ++it
)
130 if ( it
->category() == category
&& !(*it
).service()->noDisplay() )
131 result
.append( *it
);
137 PluginList
PluginManager::loadedPlugins( const QString
&category
) const
141 for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it
= _kpmp
->loadedPlugins
.constBegin();
142 it
!= _kpmp
->loadedPlugins
.constEnd(); ++it
)
144 if ( category
.isEmpty() || it
.key().category() == category
)
145 result
.append( it
.value() );
152 KPluginInfo
PluginManager::pluginInfo( const Plugin
*plugin
) const
154 for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it
= _kpmp
->loadedPlugins
.constBegin();
155 it
!= _kpmp
->loadedPlugins
.constEnd(); ++it
)
157 if ( it
.value() == plugin
)
160 return KPluginInfo();
163 void PluginManager::shutdown()
165 if(_kpmp
->shutdownMode
!= PluginManagerPrivate::Running
)
167 kDebug( 14010 ) << "called when not running. / state = " << _kpmp
->shutdownMode
;
171 _kpmp
->shutdownMode
= PluginManagerPrivate::ShuttingDown
;
174 /* save the contact list now, just in case a change was made very recently
175 and it hasn't autosaved yet
176 from a OO point of view, theses lines should not be there, but i don't
177 see better place -Olivier
179 Kopete::ContactList::self()->save();
180 Kopete::AccountManager::self()->save();
182 // Remove any pending plugins to load, we're shutting down now :)
183 _kpmp
->pluginsToLoad
.clear();
185 // Ask all plugins to unload
186 for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it
= _kpmp
->loadedPlugins
.constBegin();
187 it
!= _kpmp
->loadedPlugins
.constEnd(); /* EMPTY */ )
189 // Plugins could emit their ready for unload signal directly in response to this,
190 // which would invalidate the current iterator. Therefore, we copy the iterator
191 // and increment it beforehand.
192 PluginManagerPrivate::InfoToPluginMap::ConstIterator
current( it
);
194 // FIXME: a much cleaner approach would be to just delete the plugin now. if it needs
195 // to do some async processing, it can grab a reference to the app itself and create
196 // another object to do it.
197 current
.value()->aboutToUnload();
200 // When running under valgrind, don't enable the timer because it will almost
201 // certainly fire due to valgrind's much slower processing
202 #if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__)
203 if ( RUNNING_ON_VALGRIND
)
204 kDebug(14010) << "Running under valgrind, disabling plugin unload timeout guard";
207 QTimer::singleShot( 3000, this, SLOT( slotShutdownTimeout() ) );
210 void PluginManager::slotPluginReadyForUnload()
212 // Using QObject::sender() is on purpose here, because otherwise all
213 // plugins would have to pass 'this' as parameter, which makes the API
214 // less clean for plugin authors
215 // FIXME: I don't buy the above argument. Add a Kopete::Plugin::emitReadyForUnload(void),
216 // and make readyForUnload be passed a plugin. - Richard
217 Plugin
*plugin
= dynamic_cast<Plugin
*>( const_cast<QObject
*>( sender() ) );
220 kWarning( 14010 ) << "Calling object is not a plugin!";
223 kDebug( 14010 ) << plugin
->pluginId() << "ready for unload";
225 plugin
->deleteLater();
229 void PluginManager::slotShutdownTimeout()
231 // When we were already done the timer might still fire.
232 // Do nothing in that case.
233 if ( _kpmp
->shutdownMode
== PluginManagerPrivate::DoneShutdown
)
236 QStringList remaining
;
237 for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it
= _kpmp
->loadedPlugins
.constBegin(); it
!= _kpmp
->loadedPlugins
.constEnd(); ++it
)
238 remaining
.append( it
.value()->pluginId() );
240 kWarning( 14010 ) << "Some plugins didn't shutdown in time!" << endl
241 << "Remaining plugins: " << remaining
.join( QLatin1String( ", " ) ) << endl
242 << "Forcing Kopete shutdown now." << endl
;
247 void PluginManager::slotShutdownDone()
251 _kpmp
->shutdownMode
= PluginManagerPrivate::DoneShutdown
;
256 void PluginManager::loadAllPlugins()
258 // FIXME: We need session management here - Martijn
260 KSharedConfig::Ptr config
= KGlobal::config();
261 if ( config
->hasGroup( QLatin1String( "Plugins" ) ) )
263 QMap
<QString
, bool> pluginsMap
;
265 QMap
<QString
, QString
> entries
= config
->entryMap( QLatin1String( "Plugins" ) );
266 QMap
<QString
, QString
>::Iterator it
;
267 for ( it
= entries
.begin(); it
!= entries
.end(); ++it
)
269 QString key
= it
.key();
270 if ( key
.endsWith( QLatin1String( "Enabled" ) ) )
271 pluginsMap
.insert( key
.left(key
.length() - 7), (it
.value() == QLatin1String( "true" )) );
274 QList
<KPluginInfo
> plugins
= availablePlugins( QString::null
); //krazy:exclude=nullstrassign for old broken gcc
275 QList
<KPluginInfo
>::ConstIterator it2
= plugins
.constBegin();
276 QList
<KPluginInfo
>::ConstIterator end
= plugins
.constEnd();
277 for ( ; it2
!= end
; ++it2
)
279 // Protocols are loaded automatically so they aren't always in Plugins group. (fixes bug 167113)
280 if ( it2
->category() == QLatin1String( "Protocols" ) )
283 QString pluginName
= it2
->pluginName();
284 if ( pluginsMap
.value( pluginName
, it2
->isPluginEnabledByDefault() ) )
286 if ( !plugin( pluginName
) )
287 _kpmp
->pluginsToLoad
.push( pluginName
);
291 //This happens if the user unloaded plugins with the config plugin page.
292 // No real need to be assync because the user usually unload few plugins
293 // compared tto the number of plugin to load in a cold start. - Olivier
294 if ( plugin( pluginName
) )
295 unloadPlugin( pluginName
);
301 // we had no config, so we load any plugins that should be loaded by default.
302 QList
<KPluginInfo
> plugins
= availablePlugins( QString::null
); //krazy:exclude=nullstrassign for old broken gcc
303 QList
<KPluginInfo
>::ConstIterator it
= plugins
.constBegin();
304 QList
<KPluginInfo
>::ConstIterator end
= plugins
.constEnd();
305 for ( ; it
!= end
; ++it
)
307 if ( it
->isPluginEnabledByDefault() )
308 _kpmp
->pluginsToLoad
.push( it
->pluginName() );
311 // Schedule the plugins to load
312 QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
315 void PluginManager::slotLoadNextPlugin()
317 if ( _kpmp
->pluginsToLoad
.isEmpty() )
319 if ( _kpmp
->shutdownMode
== PluginManagerPrivate::StartingUp
)
321 _kpmp
->shutdownMode
= PluginManagerPrivate::Running
;
322 _kpmp
->isAllPluginsLoaded
= true;
323 emit
allPluginsLoaded();
328 QString key
= _kpmp
->pluginsToLoad
.pop();
329 loadPluginInternal( key
);
331 // Schedule the next run unconditionally to avoid code duplication on the
332 // allPluginsLoaded() signal's handling. This has the added benefit that
333 // the signal is delayed one event loop, so the accounts are more likely
334 // to be instantiated.
335 QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
338 Plugin
* PluginManager::loadPlugin( const QString
&_pluginId
, PluginLoadMode mode
/* = LoadSync */ )
340 QString pluginId
= _pluginId
;
342 // Try to find legacy code
343 // FIXME: Find any cases causing this, remove them, and remove this too - Richard
344 if ( pluginId
.endsWith( QLatin1String( ".desktop" ) ) )
346 kWarning( 14010 ) << "Trying to use old-style API!" << endl
<< kBacktrace();
347 pluginId
= pluginId
.remove( QRegExp( QLatin1String( ".desktop$" ) ) );
350 if ( mode
== LoadSync
)
352 return loadPluginInternal( pluginId
);
356 _kpmp
->pluginsToLoad
.push( pluginId
);
357 QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
362 Plugin
*PluginManager::loadPluginInternal( const QString
&pluginId
)
364 //kDebug( 14010 ) << pluginId;
366 KPluginInfo info
= infoForPluginId( pluginId
);
367 if ( !info
.isValid() )
369 kWarning( 14010 ) << "Unable to find a plugin named '" << pluginId
<< "'!";
373 if ( _kpmp
->loadedPlugins
.contains( info
) )
374 return _kpmp
->loadedPlugins
[ info
];
377 Plugin
*plugin
= KServiceTypeTrader::createInstanceFromQuery
<Plugin
>( QString::fromLatin1( "Kopete/Plugin" ), QString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId
), this, QVariantList(), &error
);
381 _kpmp
->loadedPlugins
.insert( info
, plugin
);
382 info
.setPluginEnabled( true );
384 connect( plugin
, SIGNAL( destroyed( QObject
* ) ), this, SLOT( slotPluginDestroyed( QObject
* ) ) );
385 connect( plugin
, SIGNAL( readyForUnload() ), this, SLOT( slotPluginReadyForUnload() ) );
387 kDebug( 14010 ) << "Successfully loaded plugin '" << pluginId
<< "'";
389 emit
pluginLoaded( plugin
);
393 kDebug( 14010 ) << "Loading plugin " << pluginId
<< " failed, KServiceTypeTrader reported error: " << error
;
399 bool PluginManager::unloadPlugin( const QString
&spec
)
401 //kDebug(14010) << spec;
402 if( Plugin
*thePlugin
= plugin( spec
) )
404 thePlugin
->aboutToUnload();
413 void PluginManager::slotPluginDestroyed( QObject
*plugin
)
415 for ( PluginManagerPrivate::InfoToPluginMap::Iterator it
= _kpmp
->loadedPlugins
.begin();
416 it
!= _kpmp
->loadedPlugins
.end(); ++it
)
418 if ( it
.value() == plugin
)
420 QString pluginName
= it
.key().pluginName();
421 _kpmp
->loadedPlugins
.erase( it
);
422 emit
pluginUnloaded( pluginName
);
427 if ( _kpmp
->shutdownMode
== PluginManagerPrivate::ShuttingDown
&& _kpmp
->loadedPlugins
.isEmpty() )
429 // Use a timer to make sure any pending deleteLater() calls have
430 // been handled first
431 QTimer::singleShot( 0, this, SLOT( slotShutdownDone() ) );
438 Plugin
* PluginManager::plugin( const QString
&_pluginId
) const
440 // Hack for compatibility with Plugin::pluginId(), which returns
441 // classname() instead of the internal name. Changing that is not easy
442 // as it invalidates the config file, the contact list, and most likely
443 // other code as well.
444 // For now, just transform FooProtocol to kopete_foo.
445 // FIXME: In the future we'll need to change this nevertheless to unify
446 // the handling - Martijn
447 QString pluginId
= _pluginId
;
448 if ( pluginId
.endsWith( QLatin1String( "Protocol" ) ) )
449 pluginId
= QLatin1String( "kopete_" ) + _pluginId
.toLower().remove( QString::fromLatin1( "protocol" ) );
452 KPluginInfo info
= infoForPluginId( pluginId
);
453 if ( !info
.isValid() )
456 if ( _kpmp
->loadedPlugins
.contains( info
) )
457 return _kpmp
->loadedPlugins
[ info
];
462 KPluginInfo
PluginManager::infoForPluginId( const QString
&pluginId
) const
464 QList
<KPluginInfo
>::ConstIterator it
;
465 for ( it
= _kpmp
->plugins
.constBegin(); it
!= _kpmp
->plugins
.constEnd(); ++it
)
467 if ( it
->pluginName() == pluginId
)
471 return KPluginInfo();
475 bool PluginManager::setPluginEnabled( const QString
&_pluginId
, bool enabled
/* = true */ )
477 QString pluginId
= _pluginId
;
479 KConfigGroup
config(KGlobal::config(), "Plugins");
481 // FIXME: What is this for? This sort of thing is kconf_update's job - Richard
482 if ( !pluginId
.startsWith( QLatin1String( "kopete_" ) ) )
483 pluginId
.prepend( QLatin1String( "kopete_" ) );
485 if ( !infoForPluginId( pluginId
).isValid() )
488 config
.writeEntry( pluginId
+ QLatin1String( "Enabled" ), enabled
);
494 bool PluginManager::isAllPluginsLoaded() const
496 return _kpmp
->isAllPluginsLoaded
;
499 } //END namespace Kopete
502 #include "kopetepluginmanager.moc"