libkdepim Library API Documentation

pluginmanager.cpp

00001 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*-
00023 #include "pluginmanager.h"
00024 
00025 #include "plugin.h"
00026 
00027 #include <qapplication.h>
00028 #include <qfile.h>
00029 #include <qregexp.h>
00030 #include <qtimer.h>
00031 #include <qvaluestack.h>
00032 
00033 #include <kapplication.h>
00034 #include <kdebug.h>
00035 #include <kparts/componentfactory.h>
00036 #include <kplugininfo.h>
00037 #include <ksettings/dispatcher.h>
00038 #include <ksimpleconfig.h>
00039 #include <kstandarddirs.h>
00040 #include <kstaticdeleter.h>
00041 #include <kurl.h>
00042 
00043 
00044 namespace Komposer
00045 {
00046 
00047 class PluginManager::Private
00048 {
00049 public:
00050   // All available plugins, regardless of category, and loaded or not
00051   QValueList<KPluginInfo*> plugins;
00052 
00053   // Dict of all currently loaded plugins, mapping the KPluginInfo to
00054   // a plugin
00055   QMap<KPluginInfo*, Plugin*> loadedPlugins;
00056 
00057   // The plugin manager's mode. The mode is StartingUp until loadAllPlugins()
00058   // has finished loading the plugins, after which it is set to Running.
00059   // ShuttingDown and DoneShutdown are used during Komposer shutdown by the
00060   // async unloading of plugins.
00061   enum ShutdownMode { StartingUp, Running, ShuttingDown, DoneShutdown };
00062   ShutdownMode shutdownMode;
00063 
00064   KSharedConfig::Ptr config;
00065   // Plugins pending for loading
00066   QValueStack<QString> pluginsToLoad;
00067 };
00068 
00069 PluginManager::PluginManager( QObject* parent )
00070   : QObject( parent )
00071 {
00072   d = new Private;
00073 
00074   // We want to add a reference to the application's event loop so we
00075   // can remain in control when all windows are removed.
00076   // This way we can unload plugins asynchronously, which is more
00077   // robust if they are still doing processing.
00078   kapp->ref();
00079   d->shutdownMode = Private::StartingUp;
00080 
00081   KSettings::Dispatcher::self()->registerInstance( KGlobal::instance(),
00082                                                    this, SLOT( loadAllPlugins() ) );
00083 
00084   d->plugins = KPluginInfo::fromServices(
00085     KTrader::self()->query( QString::fromLatin1( "Komposer/Plugin" ),
00086                             QString::fromLatin1( "[X-Komposer-Version] == 1" ) ) );
00087 }
00088 
00089 PluginManager::~PluginManager()
00090 {
00091   if ( d->shutdownMode != Private::DoneShutdown )
00092     kdWarning() << k_funcinfo
00093                        << "Destructing plugin manager without going through the shutdown process!"
00094                        << endl
00095                        << kdBacktrace() << endl;
00096 
00097   // Quick cleanup of the remaining plugins, hope it helps
00098   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00099   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); /* EMPTY */ )
00100   {
00101     // Remove causes the iterator to become invalid, so pre-increment first
00102     QMap<KPluginInfo*, Plugin*>::ConstIterator nextIt( it );
00103     ++nextIt;
00104     kdWarning() << k_funcinfo << "Deleting stale plugin '"
00105                        << it.data()->name() << "'" << endl;
00106     delete it.data();
00107     it = nextIt;
00108   }
00109 
00110   delete d;
00111 }
00112 
00113 QValueList<KPluginInfo*>
00114 PluginManager::availablePlugins( const QString& category ) const
00115 {
00116   if ( category.isEmpty() )
00117     return d->plugins;
00118 
00119   QValueList<KPluginInfo*> result;
00120   QValueList<KPluginInfo*>::ConstIterator it;
00121   for ( it = d->plugins.begin(); it != d->plugins.end(); ++it )
00122   {
00123     if ( ( *it )->category() == category )
00124       result.append( *it );
00125   }
00126 
00127   return result;
00128 }
00129 
00130 QMap<KPluginInfo*, Plugin*>
00131 PluginManager::loadedPlugins( const QString& category ) const
00132 {
00133   QMap<KPluginInfo*, Plugin*> result;
00134   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00135   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00136   {
00137     if ( category.isEmpty() || it.key()->category() == category )
00138       result.insert( it.key(), it.data() );
00139   }
00140 
00141   return result;
00142 }
00143 
00144 void
00145 PluginManager::shutdown()
00146 {
00147   kdDebug() << k_funcinfo << endl;
00148 
00149   d->shutdownMode = Private::ShuttingDown;
00150 
00151   // Remove any pending plugins to load, we're shutting down now :)
00152   d->pluginsToLoad.clear();
00153 
00154   // Ask all plugins to unload
00155   if ( d->loadedPlugins.empty() ) {
00156     d->shutdownMode = Private::DoneShutdown;
00157   } else {
00158     QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00159     for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); /* EMPTY */ )
00160     {
00161       // Remove causes the iterator to become invalid, so pre-increment first
00162       QMap<KPluginInfo*, Plugin*>::ConstIterator nextIt( it );
00163       ++nextIt;
00164       it.data()->aboutToUnload();
00165       it = nextIt;
00166     }
00167   }
00168 
00169   QTimer::singleShot( 3000, this, SLOT(slotShutdownTimeout()) );
00170 }
00171 
00172 void
00173 PluginManager::slotPluginReadyForUnload()
00174 {
00175   // Using QObject::sender() is on purpose here, because otherwise all
00176   // plugins would have to pass 'this' as parameter, which makes the API
00177   // less clean for plugin authors
00178   Plugin* plugin = dynamic_cast<Plugin*>( const_cast<QObject*>( sender() ) );
00179   if ( !plugin )
00180   {
00181     kdWarning() << k_funcinfo << "Calling object is not a plugin!" << endl;
00182     return;
00183 
00184   }
00185 
00186   plugin->deleteLater();
00187 }
00188 
00189 void
00190 PluginManager::slotShutdownTimeout()
00191 {
00192   // When we were already done the timer might still fire.
00193   // Do nothing in that case.
00194   if ( d->shutdownMode == Private::DoneShutdown )
00195     return;
00196 
00197 #ifndef NDEBUG
00198   QStringList remaining;
00199   for ( QMap<KPluginInfo*, Plugin*>::ConstIterator it = d->loadedPlugins.begin();
00200         it != d->loadedPlugins.end(); ++it )
00201     remaining.append( it.key()->pluginName() );
00202 
00203   kdWarning() << k_funcinfo << "Some plugins didn't shutdown in time!" << endl
00204               << "Remaining plugins: "
00205               << remaining.join( QString::fromLatin1( ", " ) ) << endl
00206               << "Forcing Komposer shutdown now." << endl;
00207 #endif
00208 
00209   slotShutdownDone();
00210 }
00211 
00212 void
00213 PluginManager::slotShutdownDone()
00214 {
00215   kdDebug() << k_funcinfo << endl;
00216 
00217   d->shutdownMode = Private::DoneShutdown;
00218 
00219   kapp->deref();
00220 }
00221 
00222 void
00223 PluginManager::loadAllPlugins()
00224 {
00225   // FIXME: We need session management here - Martijn
00226 
00227   if ( !d->config )
00228     d->config = KSharedConfig::openConfig( "komposerrc" );
00229 
00230   QMap<QString, QString> entries = d->config->entryMap(
00231     QString::fromLatin1( "Plugins" ) );
00232 
00233   QMap<QString, QString>::Iterator it;
00234   for ( it = entries.begin(); it != entries.end(); ++it )
00235   {
00236     QString key = it.key();
00237     if ( key.endsWith( QString::fromLatin1( "Enabled" ) ) )
00238     {
00239       key.setLength( key.length() - 7 );
00240       //kdDebug() << k_funcinfo << "Set " << key << " to " << it.data() << endl;
00241 
00242       if ( it.data() == QString::fromLatin1( "true" ) )
00243       {
00244         if ( !plugin( key ) )
00245           d->pluginsToLoad.push( key );
00246       }
00247       else
00248       {
00249         // FIXME: Does this ever happen? As loadAllPlugins is only called on startup I'd say 'no'.
00250         //        If it does, it should be made async though. - Martijn
00251         if ( plugin( key ) )
00252           unloadPlugin( key );
00253       }
00254     }
00255   }
00256 
00257   // Schedule the plugins to load
00258   QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
00259 }
00260 
00261 void PluginManager::slotLoadNextPlugin()
00262 {
00263   if ( d->pluginsToLoad.isEmpty() )
00264   {
00265     if ( d->shutdownMode == Private::StartingUp )
00266     {
00267       d->shutdownMode = Private::Running;
00268       emit allPluginsLoaded();
00269     }
00270     return;
00271   }
00272 
00273   QString key = d->pluginsToLoad.pop();
00274   loadPluginInternal( key );
00275 
00276   // Schedule the next run unconditionally to avoid code duplication on the
00277   // allPluginsLoaded() signal's handling. This has the added benefit that
00278   // the signal is delayed one event loop, so the accounts are more likely
00279   // to be instantiated.
00280   QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
00281 }
00282 
00283 Plugin*
00284 PluginManager::loadPlugin( const QString& pluginId,
00285                            PluginLoadMode mode /* = LoadSync */ )
00286 {
00287   if ( mode == LoadSync ) {
00288     return loadPluginInternal( pluginId );
00289   } else {
00290     d->pluginsToLoad.push( pluginId );
00291     QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
00292     return 0;
00293   }
00294 }
00295 
00296 Plugin*
00297 PluginManager::loadPluginInternal( const QString& pluginId )
00298 {
00299   kdDebug() << k_funcinfo << pluginId << endl;
00300 
00301   KPluginInfo* info = infoForPluginId( pluginId );
00302   if ( !info ) {
00303     kdWarning() << k_funcinfo << "Unable to find a plugin named '"
00304                 << pluginId << "'!" << endl;
00305     return 0;
00306   }
00307 
00308   if ( d->loadedPlugins.contains( info ) )
00309     return d->loadedPlugins[ info ];
00310 
00311   int error = 0;
00312   Plugin* plugin = KParts::ComponentFactory::createInstanceFromQuery<Plugin>(
00313     QString::fromLatin1( "Komposer/Plugin" ),
00314     QString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId ),
00315     this, 0, QStringList(), &error );
00316 
00317   if ( plugin ) {
00318     d->loadedPlugins.insert( info, plugin );
00319     info->setPluginEnabled( true );
00320 
00321     connect( plugin, SIGNAL(destroyed(QObject*)),
00322              this, SLOT(slotPluginDestroyed(QObject*)) );
00323     connect( plugin, SIGNAL( readyForUnload() ),
00324              this, SLOT(slotPluginReadyForUnload()) );
00325 
00326     kdDebug() << k_funcinfo << "Successfully loaded plugin '"
00327               << pluginId << "'" << endl;
00328 
00329     emit pluginLoaded( plugin );
00330   } else {
00331     switch ( error ) {
00332     case KParts::ComponentFactory::ErrNoServiceFound:
00333       kdDebug() << k_funcinfo << "No service implementing the given mimetype "
00334                 << "and fullfilling the given constraint expression can be found."
00335                 << endl;
00336       break;
00337 
00338     case KParts::ComponentFactory::ErrServiceProvidesNoLibrary:
00339       kdDebug() << "the specified service provides no shared library." << endl;
00340       break;
00341 
00342     case KParts::ComponentFactory::ErrNoLibrary:
00343       kdDebug() << "the specified library could not be loaded." << endl;
00344       break;
00345 
00346     case KParts::ComponentFactory::ErrNoFactory:
00347       kdDebug() << "the library does not export a factory for creating components."
00348                 << endl;
00349       break;
00350 
00351     case KParts::ComponentFactory::ErrNoComponent:
00352       kdDebug() << "the factory does not support creating components of the specified type."
00353                 << endl;
00354       break;
00355     }
00356 
00357     kdDebug() << k_funcinfo << "Loading plugin '" << pluginId
00358               << "' failed, KLibLoader reported error: '" << endl
00359               << KLibLoader::self()->lastErrorMessage()
00360               << "'" << endl;
00361   }
00362 
00363   return plugin;
00364 }
00365 
00366 bool
00367 PluginManager::unloadPlugin( const QString& spec )
00368 {
00369   kdDebug() << k_funcinfo << spec << endl;
00370 
00371   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00372   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00373   {
00374     if ( it.key()->pluginName() == spec )
00375     {
00376       it.data()->aboutToUnload();
00377       return true;
00378     }
00379   }
00380 
00381   return false;
00382 }
00383 
00384 void
00385 PluginManager::slotPluginDestroyed( QObject* plugin )
00386 {
00387   QMap<KPluginInfo*, Plugin*>::Iterator it;
00388   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00389   {
00390     if ( it.data() == plugin )
00391     {
00392       d->loadedPlugins.erase( it );
00393       break;
00394     }
00395   }
00396 
00397   if ( d->shutdownMode == Private::ShuttingDown && d->loadedPlugins.isEmpty() )
00398   {
00399     // Use a timer to make sure any pending deleteLater() calls have
00400     // been handled first
00401     QTimer::singleShot( 0, this, SLOT(slotShutdownDone()) );
00402   }
00403 }
00404 
00405 Plugin*
00406 PluginManager::plugin( const QString& pluginId ) const
00407 {
00408   KPluginInfo* info = infoForPluginId( pluginId );
00409   if ( !info )
00410     return 0;
00411 
00412   if ( d->loadedPlugins.contains( info ) )
00413     return d->loadedPlugins[ info ];
00414   else
00415     return 0;
00416 }
00417 
00418 QString
00419 PluginManager::pluginName( const Plugin* plugin ) const
00420 {
00421   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00422   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00423   {
00424     if ( it.data() == plugin )
00425       return it.key()->name();
00426   }
00427 
00428   return QString::fromLatin1( "Unknown" );
00429 }
00430 
00431 QString
00432 PluginManager::pluginId( const Plugin* plugin ) const
00433 {
00434   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00435   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00436   {
00437     if ( it.data() == plugin )
00438       return it.key()->pluginName();
00439   }
00440 
00441   return QString::fromLatin1( "unknown" );
00442 }
00443 
00444 QString
00445 PluginManager::pluginIcon( const Plugin* plugin ) const
00446 {
00447   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00448   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00449   {
00450     if ( it.data() == plugin )
00451       return it.key()->icon();
00452   }
00453 
00454   return QString::fromLatin1( "Unknown" );
00455 }
00456 
00457 KPluginInfo*
00458 PluginManager::infoForPluginId( const QString& pluginId ) const
00459 {
00460   QValueList<KPluginInfo*>::ConstIterator it;
00461   for ( it = d->plugins.begin(); it != d->plugins.end(); ++it )
00462   {
00463     if ( ( *it )->pluginName() == pluginId )
00464       return *it;
00465   }
00466 
00467   return 0;
00468 }
00469 
00470 bool
00471 PluginManager::setPluginEnabled( const QString& pluginId, bool enabled /* = true */ )
00472 {
00473   if ( !d->config )
00474     d->config = KSharedConfig::openConfig( "komposerrc" );
00475 
00476   d->config->setGroup( "Plugins" );
00477 
00478 
00479   if ( !infoForPluginId( pluginId ) )
00480     return false;
00481 
00482   d->config->writeEntry( pluginId + QString::fromLatin1( "Enabled" ), enabled );
00483   d->config->sync();
00484 
00485   return true;
00486 }
00487 
00488 }
00489 
00490 #include "pluginmanager.moc"
KDE Logo
This file is part of the documentation for libkdepim Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Aug 2 09:53:36 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003