How To Write Modules (aka Plugins) for Olympus

If you are interested in writing a plugin for the Olympus client, 
its fairly easy and we welcome you to the development team! Before
starting it is recommended that you:

	* can write code in C++ and QT
	* read the README-codingstyle file in the tarball distribution
	* familiarize yourself with what modules are already available
	* join the olympus-dev@mountlinux.com email list

This will hopefully help avoid wasted and/or duplicated efforts.

Once you have decided WHAT you want to code and have an idea
of how you want it to look/work like, complete the following
four steps and you'll have a working Plugin.

Some definitions first.... Within the code these
drop-in shared object files are called modules. In the user
interface they are called plugins. Why the difference? Modules
seems to be clearer to developers while plugins clearer to 
end users. *shrug*


Step 1: Sub-classing plugin.h

In the include/ directory of the client source tree, you will
find a header file called plugin.h. You must #include this file
in your module source. Additionally, all modules MUST be subclasses
of the plugin object.

Directly after the class defintion in the header file you MUST include
the following line after the class declaration:

    DLC_Register(mod_myplugin);

where mod_myplugin is the classname of the plugin.

Once you have a subclass of plugin, you need to need to implement
6 virtual methods as defined in the plugin class:

	char* name()
	char* group()
	char* desc()
	char* credits()
	char* version(netmessage*)
	void getFocus()
	void exec(hostObj* thisHost, unsigned int MID, moduleList* modItem)
	void recv(netmessage *, int)
	
The first five, which return char pointers, are used to identify
the module to the client and to the user. You can not name your 
plugin the same as a previously existing plugin, as this will cause
name colisions (the user differentiates by name). Group returns
a the group it belongs to, e.g. servers. Look at what groups names
are currently in use by other plugins to see if yours fits in to
any of them. If not, you can define a new group. The client takes
care of arranging the groups, all you need to do is provide a name.

desc() returns a short description of what the plugin does. This is
for the users, so it should be brief, clear and not too technical.

credits() returns a string to be used as a by-line. Your name can/should
appear here.

version() returns a version number, which is used to make sure that
the user is using the correct version. Obviously. =)

getFocus() is called whenever the client needs to hand focus over to 
the plugin. Do whatever you need to do upon getting focus. It can be
assumed that when getFocus() is called, the plugin does not have focus.

exec() and recv() are a little more involved and important. exec() is 
called by the client whenever it needs to launch the module. The
module is handed a hostObj object which contains all the necessary 
information on the machine the module will be communicating with, a
MID (or Module IDentifier) number and the location the module
was launched from in the client in the form of a moduleList object.

As soon as exec() is started, the module should call init() like this:
	
	init(hostObj* thisHost, unsigned int MID, moduleList* modItem) 

init() is a method of the plugin class and takes the hostObj, module ID 
(MID) and moduleList object and does the necessary bookkeeping with them. 
Once init() is called, the plugin may go on doing as it may.

recv() is used to process a netmessage that has been received from a target
host with the plugin as its destination. This is usually a reply to a 
netmessage the plugin sent out. The plugin is handed a pointer to the 
netmessage and an int that that holds the netmessage commandID, which are used
to differentiate between different types of netmessages. These 
netmessage types are defined in: 
    
    olympus/src/common/include/commid.h
    
Once received, it is the responsibility of the plugin to delete the 
netmessage when it is no longer needed. Each netmessage goes to one and
only one plugin. If for some reason it needs to be passed between plugins
this is up to the plugins to orchestrate.

Note that error messages are all >COMMAND_ERROR. 

COMMAND_SUCCESS is returned by certain netmessages upon successful completion.

Look at olympus/src/client/ping/ping.cc for an example of a simple recv().

Finally, when the plugin is finished its life it needs to call:

    client->deleteModule(moduleID);
    
Without this call, not only will the client still think it exists and not
let the user relaunch it, but it will create a memory leak.

Step 2: Build the Interface

Once exec() is called, the module is on its own to do whatever it
needs/wants to do. This means setting up and building a GUI and handling
user interaction. 

The QT widget set is used, so the module's GUI must use QT as well. This 
allows the module to be entered into the same event loop as the program
which allows for non-blockng networking/modules/etc.

The module must also clean up after itself when it is closed, of course.


Step 3: Networking code

To send a message to the host the plugin is connected to, the plugin 
needs to create a netmessage. A netmessage is created with a call like the
following:

    nmName* myMessage = new nmName(host->getRawSocket(), moduleID, COMMAND_NAME)

host->getRawSocket() will return a low level socket that the netmessage 
needs. This is the ONLY place you should ever need access to it.

moduleID is, of course, the ID# of the sending module. This is how Olympus
knows who to give the netmessage back to.

The netmessages available for use are found in:

    olympus/src/client/netmessages/
    olympus/src/server/netmessages/
    
and the numeric types used as the third parameter in a netmessage constructor 
are found in: 
    
    olympus/src/common/include/commid.h

Refer to the README-netmessages for a rundown of what each netmessage does.

Once a netmessage has been built and is ready to go, call: 

    host->network->queueMessage(myMessage);

It will be sent ASAP to the host. Once sent, a signal is emitted from the network
that can be connected to. The signals prototype is:

    messageSent(unsigned long);
    
The value of the unsigned long is the moduleID of the plugin that the sent message
message belonged to. It doesn't say which message was sent, so it may be of 
limited use. If no plugins end up using it, it will be removed for performance.
However, if any plugins come to rely on it, it will stay.

When a reply is received it will be handed to the plugin's recv() method. There
are no promises as to the order of received messages. Although in 95% of the
cases replies will be received in the same order as they are sent, this is
not guarenteed behaviour. The result of this is that the plugin needs to check
what sort of netmessage it has received (not assuming) and try to avoid relying
on a specific ordering of netmessage replies. This is a GUI after all, so it
shouldn't be a problem.

A plugin may cancel all its currently pending netmessages at any time by calling

    host->network->cancelRequests(moduleID);

This is a good thing to do upon deletion as a performance thing. The plugin will 
still receive any incoming netmessages that are a reply to previously sent 
netmessages. There is currently no way of telling what type, if any, messages are 
currently on the queue.

All network activity is nonblocking.
 

Step 4: Merge it with the source tree

In the client source tree (olympus/src/client) there is a directory called
plugin/. Create a new directory in the plugin/ directory named the same as
your plugin. Create a makefile similar to the one in plugin/mod_admin to
have it built along with the client. Send the resulting directory in plugin/
tar.gz'd or bz2'd to developers@mountlinux.com and the core team of Olympus
developers will look it over and possibly include it in the regular 
distribution.



IMPORTANT: Naming Conventions
-----------------------------

A word on naming conventions. If your module is called "MYMOD", the class
must be "mod_MYMOD" and the name of the .so file created by doing a make 
in the client source tree must be: "mod_MYMOD.so". If these naming
conventions are not followed, the module will not be loadable by the client.
