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)
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 "kopeteprotocol.h"
47
#include "kopetecontactlist.h"
48
#include "kopeteaccountmanager.h"
53
class PluginManagerPrivate
56
PluginManagerPrivate() : shutdownMode( StartingUp ), isAllPluginsLoaded(false)
58
plugins = KPluginInfo::fromServices( KServiceTypeTrader::self()->query( QLatin1String( "Kopete/Plugin" ), QLatin1String( "[X-Kopete-Version] == 1000900" ) ) );
61
~PluginManagerPrivate()
63
if ( shutdownMode != DoneShutdown && !loadedPlugins.empty() )
65
kWarning( 14010 ) << "Destructing plugin manager without going through the shutdown process! Backtrace is: " << endl << kBacktrace();
68
// Clean up loadedPlugins manually, because PluginManager can't access our global
69
// static once this destructor has started.
70
while ( !loadedPlugins.empty() )
72
InfoToPluginMap::ConstIterator it = loadedPlugins.constBegin();
73
kWarning( 14010 ) << "Deleting stale plugin '" << it.value()->objectName() << "'";
74
KPluginInfo info = it.key();
75
Plugin *plugin = it.value();
76
loadedPlugins.remove(info);
77
plugin->disconnect(&instance, SLOT(slotPluginDestroyed(QObject*)));
82
// All available plugins, regardless of category, and loaded or not
83
QList<KPluginInfo> plugins;
85
// Dict of all currently loaded plugins, mapping the KPluginInfo to
87
typedef QMap<KPluginInfo, Plugin *> InfoToPluginMap;
88
InfoToPluginMap loadedPlugins;
90
// The plugin manager's mode. The mode is StartingUp until loadAllPlugins()
91
// has finished loading the plugins, after which it is set to Running.
92
// ShuttingDown and DoneShutdown are used during Kopete shutdown by the
93
// async unloading of plugins.
94
enum ShutdownMode { StartingUp, Running, ShuttingDown, DoneShutdown };
95
ShutdownMode shutdownMode;
97
// Plugins pending for loading
98
QStack<QString> pluginsToLoad;
100
bool isAllPluginsLoaded;
101
PluginManager instance;
104
K_GLOBAL_STATIC(PluginManagerPrivate, _kpmp)
106
PluginManager* PluginManager::self()
108
return &_kpmp->instance;
111
PluginManager::PluginManager() : QObject( 0 )
113
// We want to add a reference to the application's event loop so we
114
// can remain in control when all windows are removed.
115
// This way we can unload plugins asynchronously, which is more
116
// robust if they are still doing processing.
120
PluginManager::~PluginManager()
124
QList<KPluginInfo> PluginManager::availablePlugins( const QString &category ) const
126
if ( category.isEmpty() )
127
return _kpmp->plugins;
129
QList<KPluginInfo> result;
130
QList<KPluginInfo>::ConstIterator it;
131
for ( it = _kpmp->plugins.constBegin(); it != _kpmp->plugins.constEnd(); ++it )
133
if ( it->category() == category && !(*it).service()->noDisplay() )
134
result.append( *it );
140
PluginList PluginManager::loadedPlugins( const QString &category ) const
144
for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin();
145
it != _kpmp->loadedPlugins.constEnd(); ++it )
147
if ( category.isEmpty() || it.key().category() == category )
148
result.append( it.value() );
155
KPluginInfo PluginManager::pluginInfo( const Plugin *plugin ) const
157
for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin();
158
it != _kpmp->loadedPlugins.constEnd(); ++it )
160
if ( it.value() == plugin )
163
return KPluginInfo();
166
void PluginManager::shutdown()
168
if(_kpmp->shutdownMode != PluginManagerPrivate::Running)
170
kDebug( 14010 ) << "called when not running. / state = " << _kpmp->shutdownMode;
174
_kpmp->shutdownMode = PluginManagerPrivate::ShuttingDown;
176
// Remove any pending plugins to load, we're shutting down now :)
177
_kpmp->pluginsToLoad.clear();
179
// Ask all plugins to unload
180
for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin();
181
it != _kpmp->loadedPlugins.constEnd(); /* EMPTY */ )
183
// Plugins could emit their ready for unload signal directly in response to this,
184
// which would invalidate the current iterator. Therefore, we copy the iterator
185
// and increment it beforehand.
186
PluginManagerPrivate::InfoToPluginMap::ConstIterator current( it );
188
// FIXME: a much cleaner approach would be to just delete the plugin now. if it needs
189
// to do some async processing, it can grab a reference to the app itself and create
190
// another object to do it.
191
current.value()->aboutToUnload();
194
// save the contact list now, just in case a change was made very recently
195
// and it hasn't autosaved yet
196
// from a OO point of view, theses lines should not be there, but i don't
197
// see better place -Olivier
198
Kopete::ContactList::self()->shutdown(); // Save and shutdown contact list
199
Kopete::AccountManager::self()->save();
201
// When running under valgrind, don't enable the timer because it will almost
202
// certainly fire due to valgrind's much slower processing
203
#if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__)
204
if ( RUNNING_ON_VALGRIND )
205
kDebug(14010) << "Running under valgrind, disabling plugin unload timeout guard";
208
QTimer::singleShot( 3000, this, SLOT(slotShutdownTimeout()) );
211
void PluginManager::slotPluginReadyForUnload()
213
// Using QObject::sender() is on purpose here, because otherwise all
214
// plugins would have to pass 'this' as parameter, which makes the API
215
// less clean for plugin authors
216
// FIXME: I don't buy the above argument. Add a Kopete::Plugin::emitReadyForUnload(void),
217
// and make readyForUnload be passed a plugin. - Richard
218
Plugin *plugin = dynamic_cast<Plugin *>( const_cast<QObject *>( sender() ) );
221
kWarning( 14010 ) << "Calling object is not a plugin!";
224
kDebug( 14010 ) << plugin->pluginId() << "ready for unload";
226
plugin->deleteLater();
230
void PluginManager::slotShutdownTimeout()
232
// When we were already done the timer might still fire.
233
// Do nothing in that case.
234
if ( _kpmp->shutdownMode == PluginManagerPrivate::DoneShutdown )
237
QStringList remaining;
238
for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin(); it != _kpmp->loadedPlugins.constEnd(); ++it )
239
remaining.append( it.value()->pluginId() );
241
kWarning( 14010 ) << "Some plugins didn't shutdown in time!" << endl
242
<< "Remaining plugins: " << remaining.join( QLatin1String( ", " ) ) << endl
243
<< "Forcing Kopete shutdown now." << endl;
248
void PluginManager::slotShutdownDone()
252
if (QTextCodec::codecForCStrings())
253
kWarning(14010) << "WARNING: Some plugin set QTextCodec::setCodecForCStrings this may break protocols!!!";
255
_kpmp->shutdownMode = PluginManagerPrivate::DoneShutdown;
260
void PluginManager::loadAllPlugins()
262
// FIXME: We need session management here - Martijn
264
KSharedConfig::Ptr config = KGlobal::config();
265
if ( config->hasGroup( QLatin1String( "Plugins" ) ) )
267
QMap<QString, bool> pluginsMap;
269
QMap<QString, QString> entries = config->entryMap( QLatin1String( "Plugins" ) );
270
QMap<QString, QString>::Iterator it;
271
for ( it = entries.begin(); it != entries.end(); ++it )
273
QString key = it.key();
274
if ( key.endsWith( QLatin1String( "Enabled" ) ) )
275
pluginsMap.insert( key.left(key.length() - 7), (it.value() == QLatin1String( "true" )) );
278
QList<KPluginInfo> plugins = availablePlugins( QString::null ); //krazy:exclude=nullstrassign for old broken gcc
279
QList<KPluginInfo>::ConstIterator it2 = plugins.constBegin();
280
QList<KPluginInfo>::ConstIterator end = plugins.constEnd();
281
for ( ; it2 != end; ++it2 )
283
// Protocols are loaded automatically so they aren't always in Plugins group. (fixes bug 167113)
284
if ( it2->category() == QLatin1String( "Protocols" ) )
287
QString pluginName = it2->pluginName();
288
if ( pluginsMap.value( pluginName, it2->isPluginEnabledByDefault() ) )
290
if ( !plugin( pluginName ) )
291
_kpmp->pluginsToLoad.push( pluginName );
295
//This happens if the user unloaded plugins with the config plugin page.
296
// No real need to be assync because the user usually unload few plugins
297
// compared tto the number of plugin to load in a cold start. - Olivier
298
if ( plugin( pluginName ) )
299
unloadPlugin( pluginName );
305
// we had no config, so we load any plugins that should be loaded by default.
306
QList<KPluginInfo> plugins = availablePlugins( QString::null ); //krazy:exclude=nullstrassign for old broken gcc
307
QList<KPluginInfo>::ConstIterator it = plugins.constBegin();
308
QList<KPluginInfo>::ConstIterator end = plugins.constEnd();
309
for ( ; it != end; ++it )
311
if ( it->isPluginEnabledByDefault() )
312
_kpmp->pluginsToLoad.push( it->pluginName() );
315
// Schedule the plugins to load
316
QTimer::singleShot( 0, this, SLOT(slotLoadNextPlugin()) );
319
void PluginManager::slotLoadNextPlugin()
321
if ( _kpmp->pluginsToLoad.isEmpty() )
323
if ( _kpmp->shutdownMode == PluginManagerPrivate::StartingUp )
325
_kpmp->shutdownMode = PluginManagerPrivate::Running;
326
_kpmp->isAllPluginsLoaded = true;
327
emit allPluginsLoaded();
332
QString key = _kpmp->pluginsToLoad.pop();
333
loadPluginInternal( key );
335
// Schedule the next run unconditionally to avoid code duplication on the
336
// allPluginsLoaded() signal's handling. This has the added benefit that
337
// the signal is delayed one event loop, so the accounts are more likely
338
// to be instantiated.
339
QTimer::singleShot( 0, this, SLOT(slotLoadNextPlugin()) );
342
Plugin * PluginManager::loadPlugin( const QString &_pluginId, PluginLoadMode mode /* = LoadSync */ )
344
QString pluginId = _pluginId;
346
// Try to find legacy code
347
// FIXME: Find any cases causing this, remove them, and remove this too - Richard
348
if ( pluginId.endsWith( QLatin1String( ".desktop" ) ) )
350
kWarning( 14010 ) << "Trying to use old-style API!" << endl << kBacktrace();
351
pluginId = pluginId.remove( QRegExp( QLatin1String( ".desktop$" ) ) );
354
if ( mode == LoadSync )
356
return loadPluginInternal( pluginId );
360
_kpmp->pluginsToLoad.push( pluginId );
361
QTimer::singleShot( 0, this, SLOT(slotLoadNextPlugin()) );
366
Plugin *PluginManager::loadPluginInternal( const QString &pluginId )
368
//kDebug( 14010 ) << pluginId;
370
KPluginInfo info = infoForPluginId( pluginId );
371
if ( !info.isValid() )
373
kWarning( 14010 ) << "Unable to find a plugin named '" << pluginId << "'!";
377
if ( _kpmp->loadedPlugins.contains( info ) )
378
return _kpmp->loadedPlugins[ info ];
381
Plugin *plugin = KServiceTypeTrader::createInstanceFromQuery<Plugin>( QString::fromLatin1( "Kopete/Plugin" ), QString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId ), this, QVariantList(), &error );
385
_kpmp->loadedPlugins.insert( info, plugin );
386
info.setPluginEnabled( true );
388
connect( plugin, SIGNAL(destroyed(QObject*)), this, SLOT(slotPluginDestroyed(QObject*)) );
389
connect( plugin, SIGNAL(readyForUnload()), this, SLOT(slotPluginReadyForUnload()) );
391
kDebug( 14010 ) << "Successfully loaded plugin '" << pluginId << "'";
393
emit pluginLoaded( plugin );
395
Protocol* protocol = dynamic_cast<Protocol*>( plugin );
397
emit protocolLoaded( protocol );
401
kDebug( 14010 ) << "Loading plugin " << pluginId << " failed, KServiceTypeTrader reported error: " << error ;
407
bool PluginManager::unloadPlugin( const QString &spec )
409
//kDebug(14010) << spec;
410
if( Plugin *thePlugin = plugin( spec ) )
412
thePlugin->aboutToUnload();
421
void PluginManager::slotPluginDestroyed( QObject *plugin )
423
for ( PluginManagerPrivate::InfoToPluginMap::Iterator it = _kpmp->loadedPlugins.begin();
424
it != _kpmp->loadedPlugins.end(); ++it )
426
if ( it.value() == plugin )
428
QString pluginName = it.key().pluginName();
429
_kpmp->loadedPlugins.erase( it );
430
emit pluginUnloaded( pluginName );
435
if ( _kpmp->shutdownMode == PluginManagerPrivate::ShuttingDown && _kpmp->loadedPlugins.isEmpty() )
437
// Use a timer to make sure any pending deleteLater() calls have
438
// been handled first
439
QTimer::singleShot( 0, this, SLOT(slotShutdownDone()) );
446
Plugin* PluginManager::plugin( const QString &_pluginId ) const
448
// Hack for compatibility with Plugin::pluginId(), which returns
449
// classname() instead of the internal name. Changing that is not easy
450
// as it invalidates the config file, the contact list, and most likely
451
// other code as well.
452
// For now, just transform FooProtocol to kopete_foo.
453
// FIXME: In the future we'll need to change this nevertheless to unify
454
// the handling - Martijn
455
QString pluginId = _pluginId;
456
if ( pluginId.endsWith( QLatin1String( "Protocol" ) ) )
457
pluginId = QLatin1String( "kopete_" ) + _pluginId.toLower().remove( QString::fromLatin1( "protocol" ) );
460
KPluginInfo info = infoForPluginId( pluginId );
461
if ( !info.isValid() )
464
if ( _kpmp->loadedPlugins.contains( info ) )
465
return _kpmp->loadedPlugins[ info ];
470
KPluginInfo PluginManager::infoForPluginId( const QString &pluginId ) const
472
QList<KPluginInfo>::ConstIterator it;
473
for ( it = _kpmp->plugins.constBegin(); it != _kpmp->plugins.constEnd(); ++it )
475
if ( it->pluginName() == pluginId )
479
return KPluginInfo();
483
bool PluginManager::setPluginEnabled( const QString &_pluginId, bool enabled /* = true */ )
485
QString pluginId = _pluginId;
487
KConfigGroup config(KGlobal::config(), "Plugins");
489
// FIXME: What is this for? This sort of thing is kconf_update's job - Richard
490
if ( !pluginId.startsWith( QLatin1String( "kopete_" ) ) )
491
pluginId.prepend( QLatin1String( "kopete_" ) );
493
if ( !infoForPluginId( pluginId ).isValid() )
496
config.writeEntry( pluginId + QLatin1String( "Enabled" ), enabled );
502
bool PluginManager::isAllPluginsLoaded() const
504
return _kpmp->isAllPluginsLoaded;
507
} //END namespace Kopete
510
#include "kopetepluginmanager.moc"