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 <tqapplication.h> 00028 #include <tqfile.h> 00029 #include <tqregexp.h> 00030 #include <tqtimer.h> 00031 #include <tqvaluestack.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 TQValueList<KPluginInfo*> plugins; 00052 00053 // Dict of all currently loaded plugins, mapping the KPluginInfo to 00054 // a plugin 00055 TQMap<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 TQValueStack<TQString> pluginsToLoad; 00067 }; 00068 00069 PluginManager::PluginManager( TQObject *parent ) 00070 : TQObject( 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, TQT_SLOT( loadAllPlugins() ) ); 00083 00084 d->plugins = KPluginInfo::fromServices( 00085 KTrader::self()->query( TQString::fromLatin1( "Komposer/Plugin" ), 00086 TQString::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 TQMap<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 TQMap<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 TQValueList<KPluginInfo*> 00119 PluginManager::availablePlugins( const TQString &category ) const 00120 { 00121 if ( category.isEmpty() ) 00122 return d->plugins; 00123 00124 TQValueList<KPluginInfo*> result; 00125 TQValueList<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 TQMap<KPluginInfo*, Plugin*> 00136 PluginManager::loadedPlugins( const TQString &category ) const 00137 { 00138 TQMap<KPluginInfo*, Plugin*> result; 00139 TQMap<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 TQMap<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 TQMap<KPluginInfo*, Plugin*>::ConstIterator nextIt( it ); 00166 ++nextIt; 00167 it.data()->aboutToUnload(); 00168 it = nextIt; 00169 } 00170 } 00171 00172 TQTimer::singleShot( 3000, this, TQT_SLOT(slotShutdownTimeout()) ); 00173 } 00174 00175 void 00176 PluginManager::slotPluginReadyForUnload() 00177 { 00178 // Using TQObject::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<TQObject*>( 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 TQStringList remaining; 00202 for ( TQMap<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( TQString::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 TQMap<TQString, TQString> entries = d->config->entryMap( 00232 TQString::fromLatin1( "Plugins" ) ); 00233 00234 TQMap<TQString, TQString>::Iterator it; 00235 for ( it = entries.begin(); it != entries.end(); ++it ) 00236 { 00237 TQString key = it.key(); 00238 if ( key.endsWith( TQString::fromLatin1( "Enabled" ) ) ) 00239 { 00240 key.setLength( key.length() - 7 ); 00241 //kdDebug() << k_funcinfo << "Set " << key << " to " << it.data() << endl; 00242 00243 if ( it.data() == TQString::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 TQTimer::singleShot( 0, this, TQT_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 TQString 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 TQTimer::singleShot( 0, this, TQT_SLOT( slotLoadNextPlugin() ) ); 00283 } 00284 00285 Plugin* 00286 PluginManager::loadPlugin( const TQString &pluginId, 00287 PluginLoadMode mode /* = LoadSync */ ) 00288 { 00289 if ( mode == LoadSync ) { 00290 return loadPluginInternal( pluginId ); 00291 } else { 00292 d->pluginsToLoad.push( pluginId ); 00293 TQTimer::singleShot( 0, this, TQT_SLOT( slotLoadNextPlugin() ) ); 00294 return 0; 00295 } 00296 } 00297 00298 Plugin* 00299 PluginManager::loadPluginInternal( const TQString &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 TQString::fromLatin1( "Komposer/Plugin" ), 00314 TQString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId ), 00315 this, 0, TQStringList(), &error ); 00316 00317 if ( plugin ) { 00318 d->loadedPlugins.insert( info, plugin ); 00319 info->setPluginEnabled( true ); 00320 00321 connect( plugin, TQT_SIGNAL(destroyed(TQObject*)), 00322 this, TQT_SLOT(slotPluginDestroyed(TQObject*)) ); 00323 connect( plugin, TQT_SIGNAL(readyForUnload()), 00324 this, TQT_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 TQString &spec ) 00369 { 00370 TQMap<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( TQObject *plugin ) 00385 { 00386 TQMap<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 TQTimer::singleShot( 0, this, TQT_SLOT(slotShutdownDone()) ); 00401 } 00402 } 00403 00404 Plugin* 00405 PluginManager::plugin( const TQString &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 TQString 00418 PluginManager::pluginName( const Plugin *plugin ) const 00419 { 00420 TQMap<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 TQString::fromLatin1( "Unknown" ); 00428 } 00429 00430 TQString 00431 PluginManager::pluginId( const Plugin *plugin ) const 00432 { 00433 TQMap<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 TQString::fromLatin1( "unknown" ); 00441 } 00442 00443 TQString 00444 PluginManager::pluginIcon( const Plugin *plugin ) const 00445 { 00446 TQMap<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 TQString::fromLatin1( "Unknown" ); 00454 } 00455 00456 KPluginInfo* 00457 PluginManager::infoForPluginId( const TQString &pluginId ) const 00458 { 00459 TQValueList<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 TQString &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 + TQString::fromLatin1( "Enabled" ), enabled ); 00482 d->config->sync(); 00483 00484 return true; 00485 } 00486 00487 } 00488 00489 #include "pluginmanager.moc"