Next Previous Contents

6. Programmer tutorial

This section guides a programmer in the process of writing a simple plugin for Comanche. The purpose is to describe the APIs that module authors should know and give examples of how they can be used. Although the module is written in Tcl, knowledge of Tcl is not strictly necessary or assumed. The code is extensively commented and explained to guide the reader.

6.1 Creating a module.

The main tasks that a Comanche module has to carry out are:

We will develop a simple module. This module queries the hostname of the machine and allows the user to change it. To do so, the plugin will rely on the "hostname" system command. In certain operating systems, like Red Hat Linux, changing the hostname permanently involves changing some text files that get read at startup. Since this is just a demonstration of how to write a simple module for Comanche, we will not worry about that. This simple plugIn will add a node to the Comanche console. When the user clicks on the node, a page on the right will appear that gives the current hostname. When the user right clicks on the node, a menu will appear that allows the user to pop up a property page to change the hostname value.

Every Comanche module should be designed as a [incr tcl] module (this is not necessary if it is done via the remote plugin interface, which is not implemented in this version and that allows plugins to be written in a variety of languages)

[incr Tcl] is an object oriented extension of Tcl. It allows you to create classes which define objects. Objects have functions that can be called on the object and that are called methods. We could define a class dog, which represents dogs in abstract. We could define a method, bark, that when invoked would print "Barf!" on the screen. In [incr tcl] this is done in the following fashion:

class dog {

   method barf {} {
       puts "Barf!"
   }    

}

We can create an object called scooby, which is an instance of the class dog.

dog scooby

Now we can tell scooby to bark:

scooby bark

and we get:

Barf!

The skeleton of the plug in looks something similar to the following:

class hostnamePlugIn {
    inherit plugIn
}

In a similar fashion to the above, we are going to be creating an object of the class plugin. When we have a plugin, we can tell it to do certain things for us: we can tell it to add nodes to the namespace, we can ask it information about nodes that belong to it, etc.

The kind of information that we ask is usually property pages for displaying/modifying the plugin settings. Most of the work in a plugin resides on the design of these property pages.

We are inheritting from the plugIn class, which implements the following methods:

  method init { args }
  method requestXuiDocument { xuiData }
  method answerXuiDocument  { xuiData }
  method deleteNodeRequest  { xuiData }
  method populateNodeRequest { xuiData }

From all these, the only ones that we need to implement are the first three ones, since we only have one node (populateNodeRequest is the way the namespace tell us to add nodes that are children of another) and we do not want to delete it. The remaining three functions (init, requestXuiDocument, answerXuiDocument), deal with initialization routines, and passing/getting information to/from the user.

6.2 init

This function will get called at initialization time. It gives our plugin a chance to initialize internal data structures, read external files, etc. and finally add nodes to the namespace if necessary. There are several helper objects that can be used when managing many nodes. Since we are adding a single node, it is easier to add it directly and keep track of where we added it in a variable.

# args contains the options that are passed to the plugIn at initialization
# time.
#
#  -namespace    contains the name of the name space server


method init { args } {

    # args is a list of pair/value options
    # The following is to convert the list to an array, called options

    array set options $args
    
    # This is the way Tcl assigns a variable value
    # Now namespace contains the value of the element -namespace
    # of the array options
    
    set namespace $options(-namespace)
    
    # The [] tell Tcl to treat the text contained in the brackets as a
    # command, execute it and substitute the result. So the sequence of
    # events is as follows:
    # - Ask the namespace for the root node (will return a xuiNode object)
    # - Get the unique id number for that node
    # - Assign that value to the hostnameNode variable
    #
    
    set parentNode [[ $namespace getRootNode] getId]
    
    # Add a node to the namespace, we need to tell the namespace:
    # - Who we are: $this
    # - Which namespace we want to hook up under: $namespace
    # - Which node we want to hook the new node under: $parentNode
    # - Several icons for open and closed
    # - Classes: List with node classes. Leaf means that it cannot have
    #            children. Hostname means that it belongs to our plugin.
    # - Label: Text that will be displayed next to the icon
    
    set hostnameNode [::plugInUtils::addNode $this $namespace $parentNode \
        -classes {hostname leaf} \
        -openIcon networkComputer \
        -closedIcon networkComputer \
        -label {Hostname settings}]
        
    # We remember the Id of the node that we just added
    
    set hostnameNodeId [$hostNameNode getId]            
}

We add a couple of variables to the plugIn, to also store the id for the node just added and the name of the namespace

With just the above, the plugin will add the node to the namespace:

 
 class hostnamePlugIn {
     inherit plugIn
     variable namespace
     variable hostnameNodeId
 }   

By declaring the variables in the plugin class, we make sure that they are persistent and accessible when other methods are called.

Next step is to implement the rest of the functions required for displaying menus, right pane contents and a pop up property page. When we receive/send a XML document using

requestXuiDocument
or
answerXuiDocument
we have to specify the kind of document we are receiving/transmitting (menu, property page, etc)

This involves processing xuiStructures for storing the answer, etc. We can save ourselves a lot of trouble if we directly inherit from the basePlugIn class, which already takes care of many of those details.

The basePlugin class defines the following methods:

 method _inquiryForPropertyPages  { node }
 method _inquiryForMenu { node }
 method _inquiryForWizard { type node }
 method _receivedWizard { type node }
 method _inquiryForRightPaneContent { node }
 method _receivedPropertyPages { node xuiPropertyPages }
 method _receivedCommand { node command } 

We are going to provide content now for each one of the functions and we will be one step ahead in building our plugin

_inquiryForRightPaneContent 

This function takes as an argument the node for what the content is being requested. It must return the HTML-like text to be displayed in the right pane portion of the interface. Since our plugin only has one node, it is safe to assume that when the function is called the node is the right one, so we do not need to double-check it. If a plugin had more than one node, it would be necessary to distinguish between them.

The function is then, simply:

body hostnamePlugIn::_inquiryForRightPaneContent { node } {
    
    # Set the variable result to a snippet of HTMl-like code
    # The link, instead of a normal HTML link is a command directed
    # to the console. In this case it tells the console to show
    # the property pages for the selected node when clicked.
    
    set result {
        <h1>Hostname Settings<h1>
        <br> This is a small plug in that allows to display and
        <a href="command propertyPages"> change</a>the hostname value.<br>
        The current value is }
        
    # Current value is given by executing the system command hostname
        
    append result [exec hostname]
    return $result
}
The previous addition will show in the console as follows:

Menu generation is still not implemented, there is a generic menu in place. When the user clicks on the menu entries, nothing will happen except when the user selects "Configure node". This will trigger the

_inquiryForPropertyPages
method

For returning property pages, instead of creating a new property page object per request, we will keep a property page and update it every time it is requested.

We add the following to the plug in definition

variable hostnameXuiPP
variable hostnameEntry

constructor {} {

   # We create a global  object of type xuiPropertyPage
   # the #auto keyword will assign an arbitrary name.
   # This is necessary because if we hardcode the name, this would
   # prevent having two instances of the same plugin

   set hostnameXuiPP [xuiPropertyPage ::#auto]
   
   # Set default icon, title and name of the property page
   
   $hostnameXuiPP configure -icon network
   $hostnameXuiPP setLabel {Configuring hostname}
   $hostnameXuiPP setName hostnamePP
   
   # Create the xuiString object that will hold the hostname value for the
   # user to modify ...
   
   set hostnameEntry [xuiString ::#auto]
   $hostnameEntry setLabel "Hostname"
   $hostnameEntry setName hostname
   
   # ..and add it to the property page
   
   $hostnameXuiPP addComponent $hostnameEntry
}

The constructor method is called everytime a hostnamePlugIn object is created. It set ups a XUI property page object. Every time they ask us for a property page, we fill the current hostname and we return the property page back. When it comes back, it will contain the data, probably modified by the user.

The following methods perform just that:

body hostnamePlugIn::_inquiryForPropertyPages  { node } {
    
    # User is asking for a property page to display for this node 
    # We set the current hostname in the entry
    
    $hostnameEntry setValue [exec hostname]
    
    # We return the property page
    
    return $hostnameXuiPP
}

body hostnamePlugIn::_receivedPropertyPages { node xuiPropertyPages } {

    # We extract the appropriate property page from the xuiStructure
    # containing the property pages.
    # (there is only one page, but we ask it by name)
    
    set pp [$xuiPropertyPages getComponentByName hostnamePP]
    
    # From that property page, we get to the string containing the hostname
    # and get its value
    
    set newHostname [[$pp getComponentByName hostname] getValue]
    
    # Change the hostname to the one supplied by the user
    
    catch {exec hostname $newHostname}
}

And that is all, the plugIn is completed, we do not care about the rest of available functions by now (asking for wizards, etc...), since the plugIn is a simple one. We need now to package the plugIn in a certain way so Comanche can discover it and load it at start up. Comanche stores modules under the subdirectory modules/ Under modules, each directory contains a plugIn. For each plugIn, a special file called init.tcl will get sourced.

The module needs to define certain functions that will get called at the appropriate time:

modulename_init 
modulename_restart  
modulename_info 
modulename_unload

Using the module name is a convention. It will probably be replaced by use of Tcl namespace facility, just not yet.

By now we only define the modulename_init function, in this case hostname_init, that will get called with the following arguments -namespace namespaceObject

# This file will get sourced when Comanche starts to load the module
#  and declare the hostname_* functions

# Determine my current directory

set currentDir [file dirname [file join [pwd] [info script]]]

# Load the file containing the class definition

source [file join $currentDir hostname.tcl]

# Will get called each time we want to add a plugin. In this case, we are
# only adding one

proc hostname_init { args } {
    array set options $args
    set hostnameInstance [hostnamePlugIn ::#auto]
    
    # Hook up the plugin to the namespace
    
    $hostnamePlugIn init -namespace $options(-namespace)
}

# This function is used to provide information about the installed plugins

proc hostname_info {} {
    array set info {description {Example module that changes hostname}}
    array set info {name {hostname}}
    array set info {version {1.0}}
    array set info {icon network}
    return [array get info]
}

Where do you go from here? Have a look at the other documents at the docs/ subdirectory and at the source code for the modules at plugins/. Writing a XML definition to support an apache module (such as PHP) is really easy. have a look at plugins/apache/modules


Next Previous Contents