Make things compile again.
[kdenetwork.git] / kopete / libkopete / kopetepluginmanager.cpp
blobbef86b2155b695d705926e9477019254cfb4d800
1 /*
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 *************************************************************************
11 * *
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. *
16 * *
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>
28 #endif
30 #include <QApplication>
31 #include <QFile>
32 #include <QRegExp>
33 #include <QTimer>
34 #include <QStack>
36 #include <ksharedconfig.h>
37 #include <kdebug.h>
38 #include <kparts/componentfactory.h>
39 #include <kplugininfo.h>
40 #include <kconfig.h>
41 #include <kstandarddirs.h>
42 #include <kurl.h>
43 #include <kservicetypetrader.h>
45 #include "kopeteplugin.h"
46 #include "kopetecontactlist.h"
47 #include "kopeteaccountmanager.h"
49 namespace Kopete
52 class PluginManagerPrivate
54 public:
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.begin();
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*)));
75 delete plugin;
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
83 // a plugin
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.
114 KGlobal::ref();
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.begin(); it != _kpmp->plugins.end(); ++it )
130 if ( it->category() == category && !(*it).service()->noDisplay() )
131 result.append( *it );
134 return result;
137 PluginList PluginManager::loadedPlugins( const QString &category ) const
139 PluginList result;
141 for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.begin();
142 it != _kpmp->loadedPlugins.end(); ++it )
144 if ( category.isEmpty() || it.key().category() == category )
145 result.append( it.value() );
148 return result;
152 KPluginInfo PluginManager::pluginInfo( const Plugin *plugin ) const
154 for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.begin();
155 it != _kpmp->loadedPlugins.end(); ++it )
157 if ( it.value() == plugin )
158 return it.key();
160 return KPluginInfo();
163 void PluginManager::shutdown()
165 if(_kpmp->shutdownMode != PluginManagerPrivate::Running)
167 kDebug( 14010 ) << "called when not running. / state = " << _kpmp->shutdownMode;
168 return;
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.begin();
187 it != _kpmp->loadedPlugins.end(); /* 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 );
193 ++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";
205 else
206 #endif
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() ) );
218 if ( !plugin )
220 kWarning( 14010 ) << "Calling object is not a plugin!";
221 return;
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 )
234 return;
236 QStringList remaining;
237 for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.begin(); it != _kpmp->loadedPlugins.end(); ++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;
244 slotShutdownDone();
247 void PluginManager::slotShutdownDone()
249 kDebug( 14010 ) ;
251 _kpmp->shutdownMode = PluginManagerPrivate::DoneShutdown;
253 KGlobal::deref();
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.begin();
276 QList<KPluginInfo>::ConstIterator end = plugins.end();
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" ) )
281 continue;
283 QString pluginName = it2->pluginName();
284 if ( pluginsMap.value( pluginName, it2->isPluginEnabledByDefault() ) )
286 if ( !plugin( pluginName ) )
287 _kpmp->pluginsToLoad.push( pluginName );
289 else
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 );
299 else
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.begin();
304 QList<KPluginInfo>::ConstIterator end = plugins.end();
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();
325 return;
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 );
354 else
356 _kpmp->pluginsToLoad.push( pluginId );
357 QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
358 return 0L;
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 << "'!";
370 return 0L;
373 if ( _kpmp->loadedPlugins.contains( info ) )
374 return _kpmp->loadedPlugins[ info ];
376 QString error;
377 Plugin *plugin = KServiceTypeTrader::createInstanceFromQuery<Plugin>( QString::fromLatin1( "Kopete/Plugin" ), QString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId ), this, QVariantList(), &error );
379 if ( plugin )
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 );
391 else
393 kDebug( 14010 ) << "Loading plugin " << pluginId << " failed, KServiceTypeTrader reported error: " << error ;
396 return plugin;
399 bool PluginManager::unloadPlugin( const QString &spec )
401 //kDebug(14010) << spec;
402 if( Plugin *thePlugin = plugin( spec ) )
404 thePlugin->aboutToUnload();
405 return true;
407 else
408 return false;
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 _kpmp->loadedPlugins.erase( it );
421 break;
425 if ( _kpmp->shutdownMode == PluginManagerPrivate::ShuttingDown && _kpmp->loadedPlugins.isEmpty() )
427 // Use a timer to make sure any pending deleteLater() calls have
428 // been handled first
429 QTimer::singleShot( 0, this, SLOT( slotShutdownDone() ) );
436 Plugin* PluginManager::plugin( const QString &_pluginId ) const
438 // Hack for compatibility with Plugin::pluginId(), which returns
439 // classname() instead of the internal name. Changing that is not easy
440 // as it invalidates the config file, the contact list, and most likely
441 // other code as well.
442 // For now, just transform FooProtocol to kopete_foo.
443 // FIXME: In the future we'll need to change this nevertheless to unify
444 // the handling - Martijn
445 QString pluginId = _pluginId;
446 if ( pluginId.endsWith( QLatin1String( "Protocol" ) ) )
447 pluginId = QLatin1String( "kopete_" ) + _pluginId.toLower().remove( QString::fromLatin1( "protocol" ) );
448 // End hack
450 KPluginInfo info = infoForPluginId( pluginId );
451 if ( !info.isValid() )
452 return 0L;
454 if ( _kpmp->loadedPlugins.contains( info ) )
455 return _kpmp->loadedPlugins[ info ];
456 else
457 return 0L;
460 KPluginInfo PluginManager::infoForPluginId( const QString &pluginId ) const
462 QList<KPluginInfo>::ConstIterator it;
463 for ( it = _kpmp->plugins.begin(); it != _kpmp->plugins.end(); ++it )
465 if ( it->pluginName() == pluginId )
466 return *it;
469 return KPluginInfo();
473 bool PluginManager::setPluginEnabled( const QString &_pluginId, bool enabled /* = true */ )
475 QString pluginId = _pluginId;
477 KConfigGroup config(KGlobal::config(), "Plugins");
479 // FIXME: What is this for? This sort of thing is kconf_update's job - Richard
480 if ( !pluginId.startsWith( QLatin1String( "kopete_" ) ) )
481 pluginId.prepend( QLatin1String( "kopete_" ) );
483 if ( !infoForPluginId( pluginId ).isValid() )
484 return false;
486 config.writeEntry( pluginId + QLatin1String( "Enabled" ), enabled );
487 config.sync();
489 return true;
492 bool PluginManager::isAllPluginsLoaded() const
494 return _kpmp->isAllPluginsLoaded;
497 } //END namespace Kopete
500 #include "kopetepluginmanager.moc"