libkdepim

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     slotShutdownTimeout();
00093 #if 0
00094     kdWarning() << k_funcinfo
00095                 << "Destructing plugin manager without going through "
00096                 << "the shutdown process!"
00097                 << endl
00098                 << kdBacktrace(10) << endl;
00099 #endif
00100   }
00101 
00102   // Quick cleanup of the remaining plugins, hope it helps
00103   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00104   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); /* EMPTY */ )
00105   {
00106     // Remove causes the iterator to become invalid, so pre-increment first
00107     QMap<KPluginInfo*, Plugin*>::ConstIterator nextIt( it );
00108     ++nextIt;
00109     kdWarning() << k_funcinfo << "Deleting stale plugin '"
00110                        << it.data()->name() << "'" << endl;
00111     delete it.data();
00112     it = nextIt;
00113   }
00114 
00115   delete d;
00116 }
00117 
00118 QValueList<KPluginInfo*>
00119 PluginManager::availablePlugins( const QString &category ) const
00120 {
00121   if ( category.isEmpty() )
00122     return d->plugins;
00123 
00124   QValueList<KPluginInfo*> result;
00125   QValueList<KPluginInfo*>::ConstIterator it;
00126   for ( it = d->plugins.begin(); it != d->plugins.end(); ++it )
00127   {
00128     if ( ( *it )->category() == category )
00129       result.append( *it );
00130   }
00131 
00132   return result;
00133 }
00134 
00135 QMap<KPluginInfo*, Plugin*>
00136 PluginManager::loadedPlugins( const QString &category ) const
00137 {
00138   QMap<KPluginInfo*, Plugin*> result;
00139   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00140   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00141   {
00142     if ( category.isEmpty() || it.key()->category() == category )
00143       result.insert( it.key(), it.data() );
00144   }
00145 
00146   return result;
00147 }
00148 
00149 void
00150 PluginManager::shutdown()
00151 {
00152   d->shutdownMode = Private::ShuttingDown;
00153 
00154   // Remove any pending plugins to load, we're shutting down now :)
00155   d->pluginsToLoad.clear();
00156 
00157   // Ask all plugins to unload
00158   if ( d->loadedPlugins.empty() ) {
00159     d->shutdownMode = Private::DoneShutdown;
00160   } else {
00161     QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00162     for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); /* EMPTY */ )
00163     {
00164       // Remove causes the iterator to become invalid, so pre-increment first
00165       QMap<KPluginInfo*, Plugin*>::ConstIterator nextIt( it );
00166       ++nextIt;
00167       it.data()->aboutToUnload();
00168       it = nextIt;
00169     }
00170   }
00171 
00172   QTimer::singleShot( 3000, this, SLOT(slotShutdownTimeout()) );
00173 }
00174 
00175 void
00176 PluginManager::slotPluginReadyForUnload()
00177 {
00178   // Using QObject::sender() is on purpose here, because otherwise all
00179   // plugins would have to pass 'this' as parameter, which makes the API
00180   // less clean for plugin authors
00181   Plugin* plugin = dynamic_cast<Plugin*>( const_cast<QObject*>( sender() ) );
00182   if ( !plugin )
00183   {
00184     kdWarning() << k_funcinfo << "Calling object is not a plugin!" << endl;
00185     return;
00186 
00187   }
00188   kdDebug()<<"manager unloading"<<endl;
00189   plugin->deleteLater();
00190 }
00191 
00192 void
00193 PluginManager::slotShutdownTimeout()
00194 {
00195   // When we were already done the timer might still fire.
00196   // Do nothing in that case.
00197   if ( d->shutdownMode == Private::DoneShutdown )
00198     return;
00199 
00200 #ifndef NDEBUG
00201   QStringList remaining;
00202   for ( QMap<KPluginInfo*, Plugin*>::ConstIterator it = d->loadedPlugins.begin();
00203         it != d->loadedPlugins.end(); ++it )
00204     remaining.append( it.key()->pluginName() );
00205 
00206   kdWarning() << k_funcinfo << "Some plugins didn't shutdown in time!" << endl
00207               << "Remaining plugins: "
00208               << remaining.join( QString::fromLatin1( ", " ) ) << endl
00209               << "Forcing Komposer shutdown now." << endl;
00210 #endif
00211 
00212   slotShutdownDone();
00213 }
00214 
00215 void
00216 PluginManager::slotShutdownDone()
00217 {
00218   d->shutdownMode = Private::DoneShutdown;
00219 
00220   kapp->deref();
00221 }
00222 
00223 void
00224 PluginManager::loadAllPlugins()
00225 {
00226   // FIXME: We need session management here - Martijn
00227 
00228   if ( !d->config )
00229     d->config = KSharedConfig::openConfig( "komposerrc" );
00230 
00231   QMap<QString, QString> entries = d->config->entryMap(
00232     QString::fromLatin1( "Plugins" ) );
00233 
00234   QMap<QString, QString>::Iterator it;
00235   for ( it = entries.begin(); it != entries.end(); ++it )
00236   {
00237     QString key = it.key();
00238     if ( key.endsWith( QString::fromLatin1( "Enabled" ) ) )
00239     {
00240       key.setLength( key.length() - 7 );
00241       //kdDebug() << k_funcinfo << "Set " << key << " to " << it.data() << endl;
00242 
00243       if ( it.data() == QString::fromLatin1( "true" ) )
00244       {
00245         if ( !plugin( key ) )
00246           d->pluginsToLoad.push( key );
00247       }
00248       else
00249       {
00250         // FIXME: Does this ever happen? As loadAllPlugins is only called on startup
00251         //        I'd say 'no'. If it does, it should be made async
00252         //        though. - Martijn
00253         if ( plugin( key ) )
00254           unloadPlugin( key );
00255       }
00256     }
00257   }
00258 
00259   // Schedule the plugins to load
00260   QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
00261 }
00262 
00263 void PluginManager::slotLoadNextPlugin()
00264 {
00265   if ( d->pluginsToLoad.isEmpty() )
00266   {
00267     if ( d->shutdownMode == Private::StartingUp )
00268     {
00269       d->shutdownMode = Private::Running;
00270       emit allPluginsLoaded();
00271     }
00272     return;
00273   }
00274 
00275   QString key = d->pluginsToLoad.pop();
00276   loadPluginInternal( key );
00277 
00278   // Schedule the next run unconditionally to avoid code duplication on the
00279   // allPluginsLoaded() signal's handling. This has the added benefit that
00280   // the signal is delayed one event loop, so the accounts are more likely
00281   // to be instantiated.
00282   QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
00283 }
00284 
00285 Plugin*
00286 PluginManager::loadPlugin( const QString &pluginId,
00287                            PluginLoadMode mode /* = LoadSync */ )
00288 {
00289   if ( mode == LoadSync ) {
00290     return loadPluginInternal( pluginId );
00291   } else {
00292     d->pluginsToLoad.push( pluginId );
00293     QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) );
00294     return 0;
00295   }
00296 }
00297 
00298 Plugin*
00299 PluginManager::loadPluginInternal( const QString &pluginId )
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<Komposer::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 "
00353                 << "of the specified type."
00354                 << endl;
00355       break;
00356     }
00357 
00358     kdDebug() << k_funcinfo << "Loading plugin '" << pluginId
00359               << "' failed, KLibLoader reported error: '"
00360               << KLibLoader::self()->lastErrorMessage()
00361               << "'" << endl;
00362   }
00363 
00364   return plugin;
00365 }
00366 
00367 bool
00368 PluginManager::unloadPlugin( const QString &spec )
00369 {
00370   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00371   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00372   {
00373     if ( it.key()->pluginName() == spec )
00374     {
00375       it.data()->aboutToUnload();
00376       return true;
00377     }
00378   }
00379 
00380   return false;
00381 }
00382 
00383 void
00384 PluginManager::slotPluginDestroyed( QObject *plugin )
00385 {
00386   QMap<KPluginInfo*, Plugin*>::Iterator it;
00387   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00388   {
00389     if ( it.data() == plugin )
00390     {
00391       d->loadedPlugins.erase( it );
00392       break;
00393     }
00394   }
00395 
00396   if ( d->shutdownMode == Private::ShuttingDown && d->loadedPlugins.isEmpty() )
00397   {
00398     // Use a timer to make sure any pending deleteLater() calls have
00399     // been handled first
00400     QTimer::singleShot( 0, this, SLOT(slotShutdownDone()) );
00401   }
00402 }
00403 
00404 Plugin*
00405 PluginManager::plugin( const QString &pluginId ) const
00406 {
00407   KPluginInfo *info = infoForPluginId( pluginId );
00408   if ( !info )
00409     return 0;
00410 
00411   if ( d->loadedPlugins.contains( info ) )
00412     return d->loadedPlugins[ info ];
00413   else
00414     return 0;
00415 }
00416 
00417 QString
00418 PluginManager::pluginName( const Plugin *plugin ) const
00419 {
00420   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00421   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00422   {
00423     if ( it.data() == plugin )
00424       return it.key()->name();
00425   }
00426 
00427   return QString::fromLatin1( "Unknown" );
00428 }
00429 
00430 QString
00431 PluginManager::pluginId( const Plugin *plugin ) const
00432 {
00433   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00434   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00435   {
00436     if ( it.data() == plugin )
00437       return it.key()->pluginName();
00438   }
00439 
00440   return QString::fromLatin1( "unknown" );
00441 }
00442 
00443 QString
00444 PluginManager::pluginIcon( const Plugin *plugin ) const
00445 {
00446   QMap<KPluginInfo*, Plugin*>::ConstIterator it;
00447   for ( it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it )
00448   {
00449     if ( it.data() == plugin )
00450       return it.key()->icon();
00451   }
00452 
00453   return QString::fromLatin1( "Unknown" );
00454 }
00455 
00456 KPluginInfo*
00457 PluginManager::infoForPluginId( const QString &pluginId ) const
00458 {
00459   QValueList<KPluginInfo*>::ConstIterator it;
00460   for ( it = d->plugins.begin(); it != d->plugins.end(); ++it )
00461   {
00462     if ( ( *it )->pluginName() == pluginId )
00463       return *it;
00464   }
00465 
00466   return 0;
00467 }
00468 
00469 bool
00470 PluginManager::setPluginEnabled( const QString &pluginId, bool enabled /* = true */ )
00471 {
00472   if ( !d->config )
00473     d->config = KSharedConfig::openConfig( "komposerrc" );
00474 
00475   d->config->setGroup( "Plugins" );
00476 
00477 
00478   if ( !infoForPluginId( pluginId ) )
00479     return false;
00480 
00481   d->config->writeEntry( pluginId + QString::fromLatin1( "Enabled" ), enabled );
00482   d->config->sync();
00483 
00484   return true;
00485 }
00486 
00487 }
00488 
00489 #include "pluginmanager.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys