~ubuntu-branches/ubuntu/saucy/kopete/saucy-proposed

« back to all changes in this revision

Viewing changes to libkopete/kopetepluginmanager.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-06-21 02:22:39 UTC
  • Revision ID: package-import@ubuntu.com-20130621022239-63l3zc8p0nf26pt6
Tags: upstream-4.10.80
ImportĀ upstreamĀ versionĀ 4.10.80

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    kopetepluginmanager.cpp - Kopete Plugin Loader
 
3
 
 
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>
 
7
 
 
8
    Kopete    (c) 2002-2003 by the Kopete developers  <kopete-devel@kde.org>
 
9
 
 
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
    *************************************************************************
 
18
*/
 
19
 
 
20
#include "config-kopete.h"
 
21
 
 
22
#include "kopetepluginmanager.h"
 
23
 
 
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>
 
28
#endif
 
29
 
 
30
#include <QApplication>
 
31
#include <QFile>
 
32
#include <QRegExp>
 
33
#include <QTimer>
 
34
#include <QStack>
 
35
 
 
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>
 
44
 
 
45
#include "kopeteplugin.h"
 
46
#include "kopeteprotocol.h"
 
47
#include "kopetecontactlist.h"
 
48
#include "kopeteaccountmanager.h"
 
49
 
 
50
namespace Kopete
 
51
{
 
52
 
 
53
class PluginManagerPrivate
 
54
{
 
55
public:
 
56
        PluginManagerPrivate() : shutdownMode( StartingUp ), isAllPluginsLoaded(false)
 
57
        {
 
58
                plugins = KPluginInfo::fromServices( KServiceTypeTrader::self()->query( QLatin1String( "Kopete/Plugin" ), QLatin1String( "[X-Kopete-Version] == 1000900" ) ) );
 
59
        }
 
60
 
 
61
        ~PluginManagerPrivate()
 
62
        {
 
63
                if ( shutdownMode != DoneShutdown && !loadedPlugins.empty() )
 
64
                {
 
65
                        kWarning( 14010 ) << "Destructing plugin manager without going through the shutdown process! Backtrace is: " << endl << kBacktrace();
 
66
                }
 
67
 
 
68
                // Clean up loadedPlugins manually, because PluginManager can't access our global
 
69
                // static once this destructor has started.
 
70
                while ( !loadedPlugins.empty() )
 
71
                {
 
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*)));
 
78
                        delete plugin;
 
79
                }
 
80
        }
 
81
 
 
82
        // All available plugins, regardless of category, and loaded or not
 
83
        QList<KPluginInfo> plugins;
 
84
 
 
85
        // Dict of all currently loaded plugins, mapping the KPluginInfo to
 
86
        // a plugin
 
87
        typedef QMap<KPluginInfo, Plugin *> InfoToPluginMap;
 
88
        InfoToPluginMap loadedPlugins;
 
89
 
 
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;
 
96
 
 
97
        // Plugins pending for loading
 
98
        QStack<QString> pluginsToLoad;
 
99
 
 
100
        bool isAllPluginsLoaded;
 
101
        PluginManager instance;
 
102
};
 
103
 
 
104
K_GLOBAL_STATIC(PluginManagerPrivate, _kpmp)
 
105
 
 
106
PluginManager* PluginManager::self()
 
107
{
 
108
        return &_kpmp->instance;
 
109
}
 
110
 
 
111
PluginManager::PluginManager() : QObject( 0 )
 
112
{
 
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.
 
117
        KGlobal::ref();
 
118
}
 
119
 
 
120
PluginManager::~PluginManager()
 
121
{
 
122
}
 
123
 
 
124
QList<KPluginInfo> PluginManager::availablePlugins( const QString &category ) const
 
125
{
 
126
        if ( category.isEmpty() )
 
127
                return _kpmp->plugins;
 
128
 
 
129
        QList<KPluginInfo> result;
 
130
        QList<KPluginInfo>::ConstIterator it;
 
131
        for ( it = _kpmp->plugins.constBegin(); it != _kpmp->plugins.constEnd(); ++it )
 
132
        {
 
133
                if ( it->category() == category && !(*it).service()->noDisplay() )
 
134
                        result.append( *it );
 
135
        }
 
136
 
 
137
        return result;
 
138
}
 
139
 
 
140
PluginList PluginManager::loadedPlugins( const QString &category ) const
 
141
{
 
142
        PluginList result;
 
143
 
 
144
        for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin();
 
145
              it != _kpmp->loadedPlugins.constEnd(); ++it )
 
146
        {
 
147
                if ( category.isEmpty() || it.key().category() == category )
 
148
                        result.append( it.value() );
 
149
        }
 
150
 
 
151
        return result;
 
152
}
 
153
 
 
154
 
 
155
KPluginInfo PluginManager::pluginInfo( const Plugin *plugin ) const
 
156
{
 
157
        for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin();
 
158
              it != _kpmp->loadedPlugins.constEnd(); ++it )
 
159
        {
 
160
                if ( it.value() == plugin )
 
161
                        return it.key();
 
162
        }
 
163
        return KPluginInfo();
 
164
}
 
165
 
 
166
void PluginManager::shutdown()
 
167
{
 
168
        if(_kpmp->shutdownMode != PluginManagerPrivate::Running)
 
169
        {
 
170
                kDebug( 14010 ) << "called when not running.  / state = " << _kpmp->shutdownMode;
 
171
                return;
 
172
        }
 
173
 
 
174
        _kpmp->shutdownMode = PluginManagerPrivate::ShuttingDown;
 
175
 
 
176
        // Remove any pending plugins to load, we're shutting down now :)
 
177
        _kpmp->pluginsToLoad.clear();
 
178
 
 
179
        // Ask all plugins to unload
 
180
        for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin();
 
181
              it != _kpmp->loadedPlugins.constEnd(); /* EMPTY */ )
 
182
        {
 
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 );
 
187
                ++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();
 
192
        }
 
193
 
 
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();
 
200
 
 
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";
 
206
        else
 
207
#endif
 
208
                QTimer::singleShot( 3000, this, SLOT(slotShutdownTimeout()) );
 
209
}
 
210
 
 
211
void PluginManager::slotPluginReadyForUnload()
 
212
{
 
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() ) );
 
219
        if ( !plugin )
 
220
        {
 
221
                kWarning( 14010 ) << "Calling object is not a plugin!";
 
222
                return;
 
223
        }
 
224
        kDebug( 14010 ) << plugin->pluginId() << "ready for unload";
 
225
 
 
226
        plugin->deleteLater();
 
227
}
 
228
 
 
229
 
 
230
void PluginManager::slotShutdownTimeout()
 
231
{
 
232
        // When we were already done the timer might still fire.
 
233
        // Do nothing in that case.
 
234
        if ( _kpmp->shutdownMode == PluginManagerPrivate::DoneShutdown )
 
235
                return;
 
236
 
 
237
        QStringList remaining;
 
238
        for ( PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin(); it != _kpmp->loadedPlugins.constEnd(); ++it )
 
239
                remaining.append( it.value()->pluginId() );
 
240
 
 
241
        kWarning( 14010 ) << "Some plugins didn't shutdown in time!" << endl
 
242
                << "Remaining plugins: " << remaining.join( QLatin1String( ", " ) ) << endl
 
243
                << "Forcing Kopete shutdown now." << endl;
 
244
 
 
245
        slotShutdownDone();
 
246
}
 
247
 
 
248
void PluginManager::slotShutdownDone()
 
249
{
 
250
        kDebug( 14010 ) ;
 
251
 
 
252
        if (QTextCodec::codecForCStrings())
 
253
                kWarning(14010) << "WARNING: Some plugin set QTextCodec::setCodecForCStrings this may break protocols!!!";
 
254
 
 
255
        _kpmp->shutdownMode = PluginManagerPrivate::DoneShutdown;
 
256
 
 
257
        KGlobal::deref();
 
258
}
 
259
 
 
260
void PluginManager::loadAllPlugins()
 
261
{
 
262
        // FIXME: We need session management here - Martijn
 
263
 
 
264
        KSharedConfig::Ptr config = KGlobal::config();
 
265
        if ( config->hasGroup( QLatin1String( "Plugins" ) ) )
 
266
        {
 
267
                QMap<QString, bool> pluginsMap;
 
268
 
 
269
                QMap<QString, QString> entries = config->entryMap( QLatin1String( "Plugins" ) );
 
270
                QMap<QString, QString>::Iterator it;
 
271
                for ( it = entries.begin(); it != entries.end(); ++it )
 
272
                {
 
273
                        QString key = it.key();
 
274
                        if ( key.endsWith( QLatin1String( "Enabled" ) ) )
 
275
                                pluginsMap.insert( key.left(key.length() - 7), (it.value() == QLatin1String( "true" )) );
 
276
                }
 
277
 
 
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 )
 
282
                {
 
283
                        // Protocols are loaded automatically so they aren't always in Plugins group. (fixes bug 167113)
 
284
                        if ( it2->category() == QLatin1String( "Protocols" ) )
 
285
                                continue;
 
286
 
 
287
                        QString pluginName = it2->pluginName();
 
288
                        if ( pluginsMap.value( pluginName, it2->isPluginEnabledByDefault() ) )
 
289
                        {
 
290
                                if ( !plugin( pluginName ) )
 
291
                                        _kpmp->pluginsToLoad.push( pluginName );
 
292
                        }
 
293
                        else
 
294
                        {
 
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 );
 
300
                        }
 
301
                }
 
302
        }
 
303
        else
 
304
        {
 
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 )
 
310
                {
 
311
                        if ( it->isPluginEnabledByDefault() )
 
312
                                _kpmp->pluginsToLoad.push( it->pluginName() );
 
313
                }
 
314
        }
 
315
        // Schedule the plugins to load
 
316
        QTimer::singleShot( 0, this, SLOT(slotLoadNextPlugin()) );
 
317
}
 
318
 
 
319
void PluginManager::slotLoadNextPlugin()
 
320
{
 
321
        if ( _kpmp->pluginsToLoad.isEmpty() )
 
322
        {
 
323
                if ( _kpmp->shutdownMode == PluginManagerPrivate::StartingUp )
 
324
                {
 
325
                        _kpmp->shutdownMode = PluginManagerPrivate::Running;
 
326
                        _kpmp->isAllPluginsLoaded = true;
 
327
                        emit allPluginsLoaded();
 
328
                }
 
329
                return;
 
330
        }
 
331
 
 
332
        QString key = _kpmp->pluginsToLoad.pop();
 
333
        loadPluginInternal( key );
 
334
 
 
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()) );
 
340
}
 
341
 
 
342
Plugin * PluginManager::loadPlugin( const QString &_pluginId, PluginLoadMode mode /* = LoadSync */ )
 
343
{
 
344
        QString pluginId = _pluginId;
 
345
 
 
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" ) ) )
 
349
        {
 
350
                kWarning( 14010 ) << "Trying to use old-style API!" << endl << kBacktrace();
 
351
                pluginId = pluginId.remove( QRegExp( QLatin1String( ".desktop$" ) ) );
 
352
        }
 
353
 
 
354
        if ( mode == LoadSync )
 
355
        {
 
356
                return loadPluginInternal( pluginId );
 
357
        }
 
358
        else
 
359
        {
 
360
                _kpmp->pluginsToLoad.push( pluginId );
 
361
                QTimer::singleShot( 0, this, SLOT(slotLoadNextPlugin()) );
 
362
                return 0L;
 
363
        }
 
364
}
 
365
 
 
366
Plugin *PluginManager::loadPluginInternal( const QString &pluginId )
 
367
{
 
368
        //kDebug( 14010 ) << pluginId;
 
369
 
 
370
        KPluginInfo info = infoForPluginId( pluginId );
 
371
        if ( !info.isValid() )
 
372
        {
 
373
                kWarning( 14010 ) << "Unable to find a plugin named '" << pluginId << "'!";
 
374
                return 0L;
 
375
        }
 
376
 
 
377
        if ( _kpmp->loadedPlugins.contains( info ) )
 
378
                return _kpmp->loadedPlugins[ info ];
 
379
 
 
380
        QString error;
 
381
        Plugin *plugin = KServiceTypeTrader::createInstanceFromQuery<Plugin>( QString::fromLatin1( "Kopete/Plugin" ), QString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId ), this, QVariantList(), &error );
 
382
 
 
383
        if ( plugin )
 
384
        {
 
385
                _kpmp->loadedPlugins.insert( info, plugin );
 
386
                info.setPluginEnabled( true );
 
387
 
 
388
                connect( plugin, SIGNAL(destroyed(QObject*)), this, SLOT(slotPluginDestroyed(QObject*)) );
 
389
                connect( plugin, SIGNAL(readyForUnload()), this, SLOT(slotPluginReadyForUnload()) );
 
390
 
 
391
                kDebug( 14010 ) << "Successfully loaded plugin '" << pluginId << "'";
 
392
 
 
393
                emit pluginLoaded( plugin );
 
394
 
 
395
                Protocol* protocol = dynamic_cast<Protocol*>( plugin );
 
396
                if ( protocol )
 
397
                        emit protocolLoaded( protocol );
 
398
        }
 
399
        else
 
400
        {
 
401
                kDebug( 14010 ) << "Loading plugin " << pluginId << " failed, KServiceTypeTrader reported error: " << error ;
 
402
        }
 
403
 
 
404
        return plugin;
 
405
}
 
406
 
 
407
bool PluginManager::unloadPlugin( const QString &spec )
 
408
{
 
409
        //kDebug(14010) << spec;
 
410
        if( Plugin *thePlugin = plugin( spec ) )
 
411
        {
 
412
                thePlugin->aboutToUnload();
 
413
                return true;
 
414
        }
 
415
        else
 
416
                return false;
 
417
}
 
418
 
 
419
 
 
420
 
 
421
void PluginManager::slotPluginDestroyed( QObject *plugin )
 
422
{
 
423
        for ( PluginManagerPrivate::InfoToPluginMap::Iterator it = _kpmp->loadedPlugins.begin();
 
424
              it != _kpmp->loadedPlugins.end(); ++it )
 
425
        {
 
426
                if ( it.value() == plugin )
 
427
                {
 
428
                        QString pluginName = it.key().pluginName();
 
429
                        _kpmp->loadedPlugins.erase( it );
 
430
                        emit pluginUnloaded( pluginName );
 
431
                        break;
 
432
                }
 
433
        }
 
434
 
 
435
        if ( _kpmp->shutdownMode == PluginManagerPrivate::ShuttingDown && _kpmp->loadedPlugins.isEmpty() )
 
436
        {
 
437
                // Use a timer to make sure any pending deleteLater() calls have
 
438
                // been handled first
 
439
                QTimer::singleShot( 0, this, SLOT(slotShutdownDone()) );
 
440
        }
 
441
}
 
442
 
 
443
 
 
444
 
 
445
 
 
446
Plugin* PluginManager::plugin( const QString &_pluginId ) const
 
447
{
 
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" ) );
 
458
        // End hack
 
459
 
 
460
        KPluginInfo info = infoForPluginId( pluginId );
 
461
        if ( !info.isValid() )
 
462
                return 0L;
 
463
 
 
464
        if ( _kpmp->loadedPlugins.contains( info ) )
 
465
                return _kpmp->loadedPlugins[ info ];
 
466
        else
 
467
                return 0L;
 
468
}
 
469
 
 
470
KPluginInfo PluginManager::infoForPluginId( const QString &pluginId ) const
 
471
{
 
472
        QList<KPluginInfo>::ConstIterator it;
 
473
        for ( it = _kpmp->plugins.constBegin(); it != _kpmp->plugins.constEnd(); ++it )
 
474
        {
 
475
                if ( it->pluginName() == pluginId )
 
476
                        return *it;
 
477
        }
 
478
 
 
479
        return KPluginInfo();
 
480
}
 
481
 
 
482
 
 
483
bool PluginManager::setPluginEnabled( const QString &_pluginId, bool enabled /* = true */ )
 
484
{
 
485
        QString pluginId = _pluginId;
 
486
 
 
487
        KConfigGroup config(KGlobal::config(), "Plugins");
 
488
 
 
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_" ) );
 
492
 
 
493
        if ( !infoForPluginId( pluginId ).isValid() )
 
494
                return false;
 
495
 
 
496
        config.writeEntry( pluginId + QLatin1String( "Enabled" ), enabled );
 
497
        config.sync();
 
498
 
 
499
        return true;
 
500
}
 
501
 
 
502
bool PluginManager::isAllPluginsLoaded() const
 
503
{
 
504
        return _kpmp->isAllPluginsLoaded;
 
505
}
 
506
 
 
507
} //END namespace Kopete
 
508
 
 
509
 
 
510
#include "kopetepluginmanager.moc"
 
511
 
 
512
 
 
513
 
 
514
 
 
515