Hi all,

Some thoughts on the plugin infrastructure. Feel free to bash them :)


# PluginManager
I've been looking at the licq_*plugin.h files, and it seems a bit awkward that 
a plugin should have to be loaded before you can get the version, description 
etc. I propose moving that information to to separate text files (see 
attached icq.protocol and encryption.plugin for examples) and then creating 
CPluginManager to retrieve that information and handle loading and unloading 
of plugins. The plugin manager would also be responsible for loading 
dependencies of plugins requested to be loaded.

A suggestion for how the interface could look is in plugin_manager.h. If you 
think this seems like a good idea, I'm willing to implement it.


# Plugins...
With the addition of CPluginManager, the base plugin classes will be 
simplified. I've created an example of CGeneralPlugin (see 
licq_generalplugin.h), but CProtocolPlugin should be similar. This also adds 
a macro to make it easier for plugin developers. An example plugin is in 
myplugin.h.

My opinion is that plugin developers shouldn't have to know that the plugin is 
loaded with dlopen, nor that the plugin is run in a separate thread. I mean, 
they should be aware of it, but they shouldn't have to care. Thread 
programming isn't easy, so it should be hidden as much as possible. We don't 
want to intimidate new plugin developers.


# ... and threads
This brings me to my last topic: How will the callbacks be called? If they are 
called from the daemon's thread, access to data in the plugin has to be 
protected with locks which forces the plugin developer to care about threads 
=> bad (see above). 

An alternative is to implement an event loop in CPlugin (which CGeneralPlugin 
and CProtocolPlugin inherits from, perhaps private). Since the callbacks 
should be called synchronous (or?), it could be implemented something like 
this:

The daemon calls the plugin's addToEventQueue function, passing along the 
callback and CEventData pointer. The function locks a list and appends the 
data to it. After unlocking the list the daemon goes to sleep. 

The plugin's event loop checks this list, finds the callback and executes it. 
After storing the result, it wakes up the daemon thread. The daemon reads the 
result and continues with the next callback.

This way, plugin developers don't have to care about threads since all gory 
details are hidden in CPlugin => good.

Of course, there are some problems (or issues):
- If the plugin gets stuck, the daemon also hangs (a timeout on the sleep 
should solve this: pthread_cond_timedwait).
- how to integrate this event loop with qt's or gtk's?
- sending signals plugin => daemon. Should the daemon poll plugins?
- other stuff...

If I've overlooked something basic, please tell me. My thread programming 
experience is limited ;)

// Erik

-- 
Thinking of Emacs as just an editor is like thinking of the space
shuttle as just another part of the public transportation system.
  -- Dave Kenny, comp.emacs

Erik Johansson
http://ejohansson.se
#ifndef LICQ_GENERALPLUGIN_H
#define LICQ_GENERALPLUGIN_H

class CLicqDaemon;

#define LICQ_GENERAL_PLUGIN(className) \
  extern "C" { \
    CGeneralPlugin *createGeneralPlugin(CLicqDaemon &daemon) \
      { return new className (daemon); } \
    void destroyGeneralPlugin(CGeneralPlugin *instance) \
      { delete instance; } \
    void *startGeneralThread(void *_plugin) { \
      CGeneralPlugin *plugin = static_cast<CGeneralPlugin *>(_plugin); \
      plugin->m_exitStatus = plugin->run(); \
      return 0; \
    } \
  }

class CGeneralPlugin
{
  public:
    CGeneralPlugin(CLicqDaemon& daemon) : m_exitStatus(-1), m_daemon(daemon) {}
    virtual ~CGeneralPlugin();

    std::string getBuildDate() const;
    std::string getBuildTime() const;

    /// Returning from this will stop the plugin.
    virtual int run() = 0;

    /// Automatically set to return value from run().
    int m_exitStatus;

  protected:
    CLicqDaemon& m_daemon;
};

#endif // LICQ_GENERALPLUGIN_H

// kate: space-indent on; indent-width 2; indent-mode cstyle;
// kate: remove-trailing-space on; replace-trailing-space-save on;
[Licq Plugin]
Name = Encryption
Version = 2.0
Library = licq_encryption_plugin.so
Author = Mr Anonymous
Dependencies = ICQ | MSN, GPG
Short Description = Encrypt your ICQ or MSN communication
Long Description = This plugin can be used with ICQ and/or MSN. You also need 
GPG. Remember, NSA is _always_ watching you...
#ifndef PLUGIN_MANAGER_H
#define PLUGIN_MANAGER_H

#include <boost/noncopyable.hpp>

#include <vector>
#include <string>

typedef std::vector<std::string> TStringList;

/**
 * Keeps a register over available (general) plugins and protocols.
 * Implemented as a singleton. An alternative would be to make
 * CLicqDaemon a friend to allow it to call the constructor.
 *
 * CLicqDaemon should implement a method getPluginManager() to enable
 * plugins to retrive information about other plugins.
 */
class CPluginManager : public boost::noncopyable
{
  public:
    ~CPluginManager();
    static CPluginManager& instance();

    /// Reloads all *.plugin and *.protocol files.
    bool reloadRegister();

    /// Returns the names of all available plugins.
    const TStringList& getAvailablePluginsList();
    const TStringList& getAvailableProtocolsList();

    /// Returns e.g. the version or the author for the given plugin.
    std::string getPluginInformation(const std::string& pluginName, const std::string& property);
    std::string getProtocolInformation(const std::string& protocolName, const std::string& property);

    /// Returns the names of all loaded plugins.
    const TStringList& getLoadedPluginsNames();
    const TStringList& getLoadedProtocolsNames();

    /// Returns the number of instances loaded for the given plugin.
    int getLoadedPluginInstances(const std::string& pluginName);
    int getLoadedProtocolInstances(const std::string& protocolName);

    /// Loads the given plugin and returns instance number (or 0 if the load failed).
    /// This implies loading the library unless a instance of the plugin already exists,
    /// creating the plugin class instance and running it in a new thread.
    int loadPlugin(const std::string& pluginName);
    int loadProtocol(const std::string& protocolName);

    /// Unloads the given instance (or all) of the given plugin.
    /// This implies stoping the thread, deleting the plugin class instance and unloading the library
    /// if no other instances from the plugin is running.
    bool unloadPlugin(const std::string& pluginName, int instance = 0); // 0 == all instances
    bool unloadProtocol(const std::string& protocolName, int instance = 0); // 0 == all instances

  private:
    CPluginManager();
    class CPluginManagerPrivate *d;
};

#endif // PLUGIN_MANAGER_H

// kate: space-indent on; indent-width 2; indent-mode cstyle;
// kate: remove-trailing-space on; replace-trailing-space-save on;
#include "licq_generalplugin.h"

LICQ_GENERAL_PLUGIN(MyPlugin);

class MyPlugin : public CGeneralPlugin
{
  public:
    MyPlugin(CLicqDaemon& daemon) : CGeneralPlugin(daemon)
    {
      // Load settings...
      // Setup callbacks...
      // Do some other fancy stuff...
    }
    ~MyPlugin();

    int run()
    {
      // How should this be implemented?
      while (1) ;
      return 0;
    }
};
[Licq Protocol]
Name = ICQ
Version = 0.1
Library = licq_icq.so
Multiple Instances = true
Author = Homer Simpson <[EMAIL PROTECTED]>, Friendly Guy
Short Description = Connect to the ICQ network
Long Description = A long description talking about how superior ICQ is to 
MSN...

Reply via email to