3
* (c) 2008 Maciej Niedzielski
6
#include "pluginmanager.h"
2
10
#include <QPluginLoader>
5
#include "pluginmanager.h"
13
#include "xmpp_client.h"
14
#include "xmpp_task.h"
15
#include "xmpp_message.h"
8
17
#include "applicationinfo.h"
9
18
#include "psioptions.h"
11
#include "xmpp_client.h"
20
#include "pluginhost.h"
21
#include "psiplugin.h"
22
#include "psiaccount.h"
23
#include "stanzafilter.h"
24
#include "stanzasender.h"
26
#include "iqnamespacefilter.h"
27
#include "eventfilter.h"
28
#include "optionaccessor.h"
32
// - make sure PluginManager works correctly when changing profiles
33
// - use native separators when displaying file path
37
* Helper class used to process incoming XML in plugins.
39
class PluginManager::StreamWatcher: public XMPP::Task
42
StreamWatcher(Task* t, PluginManager* m, int a) : Task(t), manager(m), account(a) {}
43
bool take(const QDomElement& e) {
44
return manager->incomingXml(account, e);
46
PluginManager* manager;
14
52
* Function to obtain all the directories in which plugins can be stored
64
* Method for accessing the singleton instance of the class.
65
* Instanciates if no instance yet exists.
66
* \return Pointer to the plugin manager.
68
PluginManager* PluginManager::instance()
71
instance_ = new PluginManager();
26
77
* Default constructor. Locates all plugins, sets watchers on those directories to
27
78
* locate new ones and loads those enabled in the config.
29
80
PluginManager::PluginManager() : QObject(NULL)
31
83
loadEnabledPlugins();
32
84
foreach (QString path, pluginDirs()) {
33
85
QCA::DirWatch *dw = new QCA::DirWatch(path, this);
34
86
connect(dw, SIGNAL(changed()), SLOT(dirsChanged()));
35
87
dirWatchers_.append(dw);
37
connect( PsiOptions::instance(), SIGNAL(optionChanged(const QString&)), this, SLOT(optionChanged(const QString&)));
89
connect(PsiOptions::instance(), SIGNAL(optionChanged(const QString&)), this, SLOT(optionChanged(const QString&)));
93
* Updates list of known plugins by reading all plugin directories
95
void PluginManager::updatePluginsList()
97
foreach (QString d, pluginDirs()) {
99
foreach (QString file, dir.entryList()) {
100
file = dir.absoluteFilePath(file);
101
qWarning(qPrintable(QString("Found plugin: %1").arg(file)));
102
if (!pluginByFile_.contains(file)) {
103
PluginHost* host = new PluginHost(this, file);
104
if (host->isValid()) {
105
hosts_[host->name()] = host;
106
pluginByFile_[file] = host;
109
qWarning("Which we already knew about");
41
116
* This slot is executed when the contents of a plugin directory changes
42
117
* It causes the available plugin list to be refreshed.
43
119
* TODO: it should also load the plugins if they're on the autoload list
45
121
void PluginManager::dirsChanged()
54
130
void PluginManager::loadEnabledPlugins()
56
132
qDebug("Loading enabled plugins");
57
QStringList plugins=availablePlugins();
58
foreach(QString plugin, plugins) {
59
QString option=QString("%1.%2").arg(loadOptionPrefix).arg(shortNames_[plugin]);
60
if ( PsiOptions::instance()->getOption(option).toBool() ) {
61
qWarning(qPrintable(QString("Plugin %1 is enabled in config: loading").arg(plugin)));
62
loadPlugin(files_[plugin]);
133
foreach (PluginHost* plugin, hosts_.values()) {
134
QString option = QString("%1.%2").arg(loadOptionPrefix).arg(plugin->shortName());
135
if (PsiOptions::instance()->getOption(option).toBool()) {
136
qWarning(qPrintable(QString("Plugin %1 is enabled in config: loading").arg(plugin->shortName())));
88
166
//Now look for external plugins
89
QStringList plugins=availablePlugins();
90
foreach(QString d, plugins)
96
* Load a plugin file. Will fail if the plugin is already active.
97
* If a plugin has previously had unloadPlugin() called on it but
98
* Psi has been unable to unload it (such as on OSX) and it was
99
* instead disabled, this method will reactivate the plugin, instead
100
* of attempting to reload it.
101
* \param file File to load
102
* \return Success in loading the plugin
104
bool PluginManager::loadPlugin( const QString& file )
106
qDebug() << "Loading Plugin " << file;
107
//we can safely take the first key, as we won't have the same
108
// file belonging to multiple plugins
109
QList<QString> names = files_.keys(file);
110
if (! names.isEmpty() ) {
111
QString name = names.first();
112
if ( plugins_.contains(name) ) {
113
qWarning() << QString("Plugin %1 is already active, but this should never be.").arg(file);
117
QPluginLoader* loader=NULL;
118
if ( loaders_.contains(file) ) {
119
loader=loaders_[file];
122
loader=new QPluginLoader( file );
123
loaders_.insert( file , loader);
125
QObject* plugin = loader->instance();
126
if ( !loader->isLoaded() ) {
128
loaders_.remove( loaders_.keys(loader).first() );
132
if ( !loadPlugin(plugin) ) {
133
qWarning( qPrintable( QString("pluginmanager.cpp: Attempted to load %1, but it is not a valid plugin.").arg(file) ));
134
if ( loader->isLoaded() ) {
135
qWarning("File is a plugin but not for Psi");
139
loaders_.remove( loaders_.keys(loader).first() );
147
* Imports a Psi plugin loaded into a plugin object. Will fail if the plugin
148
* is not suitable (wrong Qt version, wrong debug setting, wrong Psi plugin
149
* interface version etc).
150
* \param pluginObject Plugin Object
153
bool PluginManager::loadPlugin( QObject* pluginObject )
157
qDebug( qPrintable( QString("Trying to load plugin") ) );
158
//Check it's the right sort of plugin
159
PsiPlugin* plugin = qobject_cast<PsiPlugin*> (pluginObject);
163
qDebug() << "loading plugin " << plugin->name();
164
plugins_.insert( plugin->name(), plugin );
166
qDebug() << "connecting to plugin " << plugin->name();
167
connect( plugin, SIGNAL(sendStanza(const PsiAccount*, const QDomElement&)), this, SLOT(sendStanza(const PsiAccount*, const QDomElement&)));
168
connect( plugin, SIGNAL(sendStanza(const PsiAccount*, const QString&)), this, SLOT(sendStanza(const PsiAccount*, const QString&)));
169
connect( plugin, SIGNAL(setPluginOption( const QString&, const QVariant& )), this, SLOT( setPluginOption( const QString&, const QVariant& )));
170
connect( plugin, SIGNAL(getPluginOption( const QString&, QVariant&)), this, SLOT( getPluginOption( const QString&, QVariant&)));
171
connect( plugin, SIGNAL(setGlobalOption( const QString&, const QVariant& )), this, SLOT( setGlobalOption( const QString&, const QVariant& )));
172
connect( plugin, SIGNAL(getGlobalOption( const QString&, QVariant&)), this, SLOT( getGlobalOption( const QString&, QVariant&)));
167
foreach (PluginHost* plugin, hosts_.values()) {
181
178
bool PluginManager::unloadAllPlugins()
183
180
qDebug("Unloading all plugins");
185
foreach (QString plugin, plugins_.keys()) {
186
if (!unloadPlugin(plugin))
182
foreach (PluginHost* plugin, hosts_.values()) {
183
if (!plugin->disable()) {
185
} else if (plugin->unload()) {
193
* Unloads the named plugin. If the plugin cannot be unloaded, the method
194
* returns false but the plugin is still removed from the active list
195
* and not used for further processing (This is most relevant on OSX
196
* where it seems we can't ever unload a plugin).
197
* \param plugin Name of the plugin to unload
198
* \return Plugin unloaded result.
200
bool PluginManager::unloadPlugin(const QString& plugin)
202
if ( !plugins_.contains(plugin) ) {
203
qWarning( qPrintable( QString("Plugin %1 wasn't found when trying to unload").arg(plugin) ) );
206
qDebug() << "attempting to disconnect " << plugins_[plugin]->name();
207
plugins_[plugin]->disconnect();
208
QString file=files_[plugin];
209
if ( !loaders_.contains(file) ) {
210
qWarning( qPrintable( QString("Plugin %1's loader wasn't found when trying to unload").arg(plugin) ) );
213
QPluginLoader* loader=loaders_[file];
215
if ( loader->unload() ) {
216
//if we're done with the plugin completely and it's unloaded
217
// we can delete the loader;
220
loaders_.remove(file);
222
//if the plugin was not unloaded, we remove it from the active
223
// list, but keep the loader so we can obtain future instances
224
plugins_.remove(plugin);
229
193
* Find the file which provides the named plugin. If the named plugin is not
230
194
* known, an empty string is provided.
231
195
* \param plugin Name of the plugin.
234
198
QString PluginManager::pathToPlugin(const QString& plugin)
236
if (! files_.contains(plugin) )
238
return files_[plugin];
201
if (hosts_.contains(plugin)) {
202
path = hosts_[plugin]->path();
242
* Find the file which provides the named plugin. If the named plugin is not
208
* Returns short name of the named plugin. If the named plugin is not
243
209
* known, an empty string is provided.
244
210
* \param plugin Name of the plugin.
245
211
* \return Path to the plugin file.
247
213
QString PluginManager::shortName(const QString& plugin)
249
if (! shortNames_.contains(plugin) )
251
return shortNames_[plugin];
255
* Method for accessing the singleton instance of the class.
256
* Instanciates if no instance yet exists.
257
* \return Pointer to the plugin manager.
259
PluginManager* PluginManager::instance()
262
instance_ = new PluginManager();
216
if (hosts_.contains(plugin)) {
217
name = hosts_[plugin]->shortName();
268
* Searches the relevant folders for plugins, queries them for their names
269
* and returns a list of available plugin names.
223
* Returns a list of available plugin names found in all plugin directories.
271
225
QStringList PluginManager::availablePlugins()
273
foreach(QString d, pluginDirs()) {
275
foreach(QString file, dir.entryList()) {
276
file=dir.absoluteFilePath(file);
277
qWarning(qPrintable(QString("Found plugin: %1").arg(file)));
278
if ( !loaders_.contains(file) ) {
280
if ( loaders_.contains(file) ) {
282
PsiPlugin* plugin=qobject_cast<PsiPlugin*> ( loaders_[file]->instance() );
283
files_.insert(plugin->name(), dir.absoluteFilePath(file) );
284
shortNames_.insert(plugin->name(), plugin->shortName());
285
unloadPlugin( plugin->name() );
289
qWarning("Which we already knew about");
293
return files_.keys();
227
return hosts_.keys();
299
233
* \param plugin Name of the plugin.
300
234
* \return Pointer to the options widget for the named plugin.
302
QWidget* PluginManager::getOptionsWidget( const QString& plugin )
236
QWidget* PluginManager::optionsWidget(const QString& plugin)
304
if (plugins_.contains(plugin))
305
return plugins_.value(plugin)->options();
239
if (hosts_.contains(plugin)) {
240
widget = hosts_[plugin]->optionsWidget();
307
242
qWarning(qPrintable(QString("Attempting to get options for %1 which doesn't exist").arg(plugin)));
312
* \brief Sets an option (local to the plugin)
313
* The options will be automatically prefixed by the plugin manager, so
314
* there is no need to uniquely name the options. In the same way as the
315
* main options system, a hierachy is available by dot-delimiting the
316
* levels ( e.g. "emoticons.show"). Use this and not setGlobalOption
317
* in almost every case.
318
* \param option Option to set
319
* \param value New option value
321
void PluginManager::setPluginOption( const QString& option, const QVariant& value)
323
PsiPlugin* plugin=NULL;
327
QString pluginName = plugin->name();
328
QString optionKey=QString("%1.%2.%3").arg(pluginOptionPrefix).arg(shortNames_[pluginName]).arg(option);
329
PsiOptions::instance()->setOption(optionKey,value);
333
* \brief Gets an option (local to the plugin)
334
* The options will be automatically prefixed by the plugin manager, so
335
* there is no need to uniquely name the options. In the same way as the
336
* main options system, a hierachy is available by dot-delimiting the
337
* levels ( e.g. "emoticons.show"). Use this and not getGlobalOption
338
* in almost every case.
339
* \param option Option to set
340
* \param value Return value
342
void PluginManager::getPluginOption( const QString& option, QVariant& value)
348
* \brief Sets a global option (not local to the plugin)
349
* The options will be passed unaltered by the plugin manager, so
350
* the options are presented as they are stored in the main option
351
* system. Use setPluginOption instead of this in almost every case.
352
* \param option Option to set
353
* \param value New option value
355
void PluginManager::setGlobalOption( const QString& option, const QVariant& value)
357
PsiOptions::instance()->setOption(option, value);
361
* \brief Gets a global option (not local to the plugin)
362
* The options will be passed unaltered by the plugin manager, so
363
* the options are presented as they are stored in the main option
364
* system. Use getPluginOption instead of this in almost every case.
365
* \param option Option to set
366
* \param value Return value
368
void PluginManager::getGlobalOption( const QString& option, QVariant& value)
370
value=PsiOptions::instance()->getOption(option);
372
qDebug("valid option");
374
qDebug("not valid option");
377
void PluginManager::message(PsiAccount* account, const XMPP::Jid& from, const UserListItem* ul, const QString& message)
379
QString fromString=QString("%1").arg(from.full());
380
qDebug() << "message from" << fromString;
381
foreach(PsiPlugin* plugin, plugins_.values() ) {
382
plugin->message( account, message , fromString , from.full() );
248
* \brief Give each plugin the opportunity to process the incoming message event
250
* Each plugin is passed the event in turn. Any plugin may then modify the event
251
* and may cause the event to be silently discarded.
253
* \param account Pointer to the PsiAccount responsible
254
* \param event Incoming event
255
* \return Continue processing the event; true if the event should be silently discarded.
257
bool PluginManager::processMessage(const PsiAccount* account, const QString& jidFrom, const QString& body, const QString& subject)
259
bool handled = false;
260
foreach (PluginHost* host, hosts_.values()) {
261
if (host->processMessage(accountIds_[account], jidFrom, body, subject)) {
392
275
* \param account Pointer to the PsiAccount responsible
393
276
* \param event Incoming event
394
* \return Continue processing the event; false if the event should be silently discarded.
396
bool PluginManager::processEvent( const PsiAccount* account, QDomElement &event )
399
QDomNode message = event.elementsByTagName("message").item(0);
400
//QString account = event.elementsByTagName("account").item(0).toText().data();
401
foreach(PsiPlugin* plugin, plugins_.values() ) {
402
ok = ok && plugin->processEvent( account, message ) ;
408
* \brief Sends a stanza from the specified account.
410
* \param account The account name, as used by the plugin interface.
411
* \param stanza The stanza to be sent.
413
void PluginManager::sendStanza( const PsiAccount* account, const QDomElement& stanza)
415
sendStanza(account,PsiPlugin::toString(stanza));
419
* \brief Sends a stanza from the specified account.
421
* \param account The account name, as used by the plugin interface.
422
* \param stanza The stanza to be sent.
424
void PluginManager::sendStanza( const PsiAccount* account, const QString& stanza)
426
qDebug() << "Want to send stanza to account " << (void*)account;
427
if (!clients_.contains(account) || !verifyStanza(stanza))
429
clients_[account]->send(stanza);
277
* \return Continue processing the event; true if the event should be silently discarded.
279
bool PluginManager::processEvent(const PsiAccount* account, QDomElement& event)
281
bool handled = false;
282
foreach (PluginHost* host, hosts_.values()) {
283
if (host->processEvent(accountIds_[account], event)) {
292
* \brief Give each plugin the opportunity to process the incoming xml
294
* Each plugin is passed the xml in turn using various filter interfaces
295
* (for example, see StanzaFilter or IqFilter).
296
* Any plugin may then modify the xml and may cause the stanza to be
297
* silently discarded.
299
* \param account Identifier of the PsiAccount responsible
300
* \param xml Incoming XML
301
* \return Continue processing the event; true if the event should be silently discarded.
303
bool PluginManager::incomingXml(int account, const QDomElement &xml)
305
bool handled = false;
306
foreach (PluginHost* host, hosts_.values()) {
307
if (host->incomingXml(account, xml)) {
316
* Called by PluginHost when its hosted plugin wants to send xml stanza.
318
* \param account Identifier of the PsiAccount responsible
319
* \param xml XML stanza to be sent
321
void PluginManager::sendXml(int account, const QString& xml)
324
// - think if it is better to ask plugin(host) for string or xml node
326
// - add id if missing
327
// - maybe use appropriate Task to send
328
// - make psi aware of things that are being send
329
// (for example, pipeline messages through history system)
331
if (account < clients_.size()) {
332
clients_[account]->send(xml);
337
* Returns unique stanza identifier in account's stream
339
* \param account Identifier of the PsiAccount responsible
340
* \return Unique ID to be used for when sending a stanza
342
QString PluginManager::uniqueId(int account)
345
if (account < clients_.size()) {
346
id = clients_[account]->genUniqueId();
433
352
* Tells the plugin manager about an XMPP::Client and the owning PsiAccount
435
void PluginManager::addAccount( const PsiAccount* account, XMPP::Client* client)
354
void PluginManager::addAccount(const PsiAccount* account, XMPP::Client* client)
437
clients_[account]=client;
356
clients_.append(client);
357
const int id = clients_.size() - 1;
358
accountIds_[account] = id;
359
new StreamWatcher(client->rootTask(), this, id);
441
363
* Performs basic validity checking on a stanza
442
* TODO : populate verifyStanza method
364
* TODO : populate verifyStanza method and use it
444
366
bool PluginManager::verifyStanza(const QString& stanza)