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.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 21 19:46:29 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003