Creating Your Own Applets

Super Productivity Secrets

Personal Productivity Solutions

Get Instant Access

To fully appreciate the development methodology of applets, it's necessary to know a little about WARP. WARP is a system built on WebFace that abstracts the basic elements of web design to ensure that the control logic and presentation elements are strictly separated. This means that it's not possible to add a link in your page with the following:

<a href="process.php?bedroom_light=on">Switch Light On</a>

but instead you have to ask an applet manager to generate one for you:

$html = $appMan->getAppletLink($applet, "on", "bedroom_light", "Switch Light On");

You would then pick up the argument like this: $prm = $appMan->queryParameter($applet, "bedroom_light");

This approach has several benefits. Naturally, it forces a separation between logic and display, which encourages more structured code, and it means you can place the applet onto any page you like, since the page itself isn't mentioned. But more important, it allows for an easy upgrade path. WARP is based on something akin to a RESTful approach. Because HTTP is a stateless protocol, there is no way of remembering the user action from one page to the next. Some web sites do this by using lots of different pages, where the page itself is an implicit reminder of the state. Some will create server-side objects that remember the state and last for as long as your browser is looking at the page or until an arbitrary timeout. WARP does this by recording the state as part of the URL. And with several applets appearing on each page, each applet must therefore have its own part of the URL indicating its own state. A typical URL might appear as follows:

http://my.minervahome.net/minerva/wmm.php?WRP001X1_user=0&WRP001X1_day=0&WRP002X3_dpage^

=0&WRP002X3_fpage=0&WRP004X6_current=/&WRP004X6_dpage=0&WRP004X6_fpage=0&wintype^

=main&content=WRP002X&max=WRP002X

If you dissect this carefully, you can see that each parameter fits the pattern of WRP, followed by a three-digit applet code, an X separator and parameter index number, an underscore, and then a name/value pair. There are also some parameters that describe WARP's internal state in the form of wintype, content, and max. Naturally, attempting to generate or parse this URL manually would be foolhardy!

The WARP Directories

There are three directories of note inside minerva/warp:

applets: The code and assets for each of the applets, inside their own subdirectory conf: The configuration data for the earlier applets, inside an identically named subdirectory warplib: The base and operational classes for the applet and applet manager

The Components of WebFace

Each abstraction in WebFace covers one of the four areas in WARP code design. References to each can be retrieved from the applet manager with functions and functionality as follows:

getAuth(): Authorization. This module will let you know whether the user has logged in and been authenticated by the Apache server using the method getUser. Since the Apache user ID is administered manually to match the Minerva username, this can be used to present the TV guide and other user-specific data on a web page. This module also connects to the minuser code, through isUserValidFor, to provide fine-level control over the various applets. This makes it possible for many people to see the currently playing CD or MP3 track but for only a privileged few to change it (warp/warplib/appauth.inc).

getCtrl(): Control. Generates anchors for web links, although I'll cover this in detail next.

getView(): Viewport. This is used to combine multiple blocks of individual data into a single frame. The metaphor used here is pane, since they are combined to form windows. The default functionality allows you to combine these into horizontally or vertically, aligned windows of two, three, or four panes (webface/webview.inc).

getDbug(): Debug. This is a utility submodule that allows individual errors, warnings, and information lines to be logged. Once the HTML has been built, the complete list can be written out using dumpAll. This is done since output written to arbitrary parts of the web page can break the formatting badly and, in the case of cookies, prevent the page from loading altogether (webface/webdbug.inc).

The Basic Structure

Almost every applet begins the same way, with a directory inside warp/applets and a new applet class taken from the template as follows:

<?php require_once 'system/setup.conf'; require_once 'warp/warplib/applet.inc';

class Warp_TODO_Applet extends Warp_Applet {

function Warp_TODO_Applet($caption = "TODO List") parent::__construct($caption);

function getDirectory() return "todo";

function init(&$appMan)

Warp_Applet::Init($appMan);

function renderPanel(&$appMan) return "";

function renderWindow(&$appMan) return "";

function renderInfo(&$app_man, $fast=false)

return "Instructions...";

You can easily see the blanks in which you fill the HTML code to form the panel, main window, and help screen. These are the easy bits. The interesting code is in init and the hitherto unseen getRefreshParams that control the parameters.

TODO: Controlling the Applet

With everything being controlled by parameters, you must take care to use them effectively. The best way to do this is to think of the GET request as featuring two sets of parameters. The first set reflects the refresh parameters and are those that you'd want to appear in the URL so that this state could be rebuilt at a later date. The second set is the command parameters, indicating how that state is to change when the page is reloaded.

In the TODO example, your applet should be able to list the to-do list for the current user or the public and optionally be able to sort it alphabetically.5 This will ultimately provide you with five potential parameters:

Refresh state parameter Which user is visible?

Refresh state parameter Is the list sorted?

Command argument Switch to user X.

Command argument Sort list.

Command argument Unsort list (aka, show in order).

This demonstrates the next point—there is redundancy here. It is technically possible to combine the refresh state and command parameter in one, since they both control the same data. However, when you are building a web page, you need to know all the refresh state parameters so that the other links on the page have the correct values. Unfortunately, that would require a lot of work to know which state parameter would be overridden later by the command parameters. You can simplify this by writing a refresh function that describes the current state and that every other applet will indirectly call when it requests a URL from the applet manager:

function getRefreshParams(&$appMan) {

return

$appMan->getArgument($this, "user", $this->_viewuser)."&". $appMan->getArgument($this, "sort", $this->_sortlist));

You next add links that contain command parameters, which are similar to those you've seen

"0", "Chronologically").' "1", "Alphabetically");

already:

$html =

"Show: "

$html.=

$appMan-

$html.=

$appMan-

$html.=

" For:

$html.=

$appMan-

$html.=

$appMan-

$user, $user)." "; "public", "Public");

5 To correctly delete an entry from the TODO list, you'd need to lock the file in case the file got corrupted when two people tried to delete at the same time. I have a truly marvelous solution to this, which this margin is too narrow to contain!

These parameters, by convention, are prefixed with do indicating that they should change the refresh state. That is, new state = old state + do changes. The applet manager generates a suitable link by gathering the refresh parameters from every applet present on the current page and appending these do links to the end.

When the page is loaded, a new state is built based on these parameters and done in two stages. The first is to retrieve the refresh arguments:

$this->_sortlist = $appMan->queryParameter($this, "sort", false); $this->_viewuser = $appMan->queryParameter($this, "user", "public");

The second is to look for any do parameters to change this state:

$this->_sortlist = $appMan->queryParameter($this, "dosort", $this->_sortlist); $this->_viewuser = $appMan->queryParameter($this, "douser", $this->_viewuser);

In both cases you're using a default argument to queryParameter that covers the case when the applet is first used and no parameters at all are available and for when there are no command parameters.

You can then flex your creative muscles in displaying the output from the Bearskin command todo (remember writing that all those pages ago?!) and write the list into the HTML:

exec("/usr/local/minerva/bin/todo list ".$this->_viewuser, $todolist);

$html .= "<ul>"; foreach($todolist AS $item) { $html .= "<li>$item</li>";

To add a layer of polish to these, you could move the exec call into Zinc, but that can be left for another day!

Global Configuration

There are a small number of configuration files used in the web portion of Minerva to cope with the different directories structures you might adopt, as detailed in Table 7-1.

Table 7-1. Web Configuration Directories

Include Filename Function

Default Directory

Description minerva.conf getMinervaRoot /usr/local/minerva system/setup.conf getURLRoot

/minerva system/setup.conf getPathRoot /var/www/sites/homecontrol system/utils.inc getServerName system/utils.inc getServerPort system/utils.inc getRemotePort system/utils.inc getIPAddress

The base of the Minerva system itself.

The name used by Minerva web components. Can be changed for protection against bots that attempt to break any web pages held in a Minerva-named directory.

The filesystem path of the web root. Used when you need to access files in the conventional manner.

Varies. Use this, instead of IP dotted quad if virtual servers are used.

Varies by client.

Determine by client. Might actually be IP of router.

Applet Configuration

There are two different types of directory you, as an applet writer, need to consider. The first are those that are used to serve web data to the client, such as images, configuration data, or supplementary files. There are several methods inside each applet class to retrieve this, each accepting a filename and returning its full path, such as getConfFileName (taken from the configuration directory), getAppletFileName (the applet code directory), and getImageURL (the images directory inside the applet folder).

The second type of directory is one that refers to a location in the filesystem and is referenced with getFilesystemPathStub and concatenated with the relative filename. In reality, any relative web path can be converted into a filesystem path by joining it with WarpSetup::getPathRoot, but these methods provide a clean way of writing code.

There is also an intriguing method called getRefreshTime, which causes the current web page (with all its applets) to automatically reload itself every n seconds. This allows the applet to more easily reflect changes to data without needing to implement specific push protocols. If more than one applet supports getRefreshTime, then the shortest time is used. This is provided as an alternative to the use of Ajax (as demonstrated in the Bluetooth, currency, and recipe applets) that asynchronously responds to requests from the main server. Remember that most browsers support only two concurrent Ajax requests, so their issue should be staggered with a timeout.

Utilities

Various utility methods are included as part of the applet manager, as well as the individual applet base class itself. Indeed, there are even full classes that can be derived from to create near-complete applets with very little work. Warp_Browser_Applet, as used by the MP3 player and video streamer, lets you traverse an entire directory structure without writing a single line of code; you only need to overload the renderFileLine and renderDirectoryLine methods to generate appropriate actionable links. Additionally, Warp_Static_Text_Applet can be used select and render one of many given HTML files, as demonstrated with the cooking applet.

Caching is one of many utilities provided by the appletUtils class, located in warp/warplib/appletutils.inc. Code like this will download the contents of $url to the local data file but only if the file doesn't exist or is older than 6,000 seconds:

$contents = appletUtils::getContents($url, "local_data_file", 6000);

The cache contents are stored in /var/log/minerva/cache.

Release

If you're developing an applet for yourself, then the job is now done! Otherwise, you should package it ready for others. The addminervaapplet script is used to install new applets into the correct locations. Since there can be several components to an applet (Bearskin, WARP, and Zinc), you should create directories for each so that it matches those used already. Here's an example of the FM radio applet:

fmradio/example.php fmradio/Readme fmradio/version fmradio/fmradio/bearskin/fmradio fmradio/fmradio/install/install.sh fmradio/fmradio/warp/app/ [contents of applet directory go here] fmradio/fmradio/zinc/conf/ [Zinc configuration here] fmradio/fmradio/zinc/cmd

Manifest

The Manifest system is a method of presenting multiple elements in a sequential pattern in a way that can be interactively terminated, interrupted, or extended, with the commands stop, next, and more, respectively. This is better explained by working through the two supplied examples, News and Music, whose audio-based output is typical of the usage of Manifest.

The news manifest reads headlines from a given news feed one at a time. If the more command is given at any point during the headline, the full story is then read, before continuing with the next headline. (In the case of the music manifest, the more command is a null operation but could be used to speak the title and artist of the previous track.)

The manifests can be invoked with a simple command like the following:

manifest default start music 10

and, since the current manifest is known, can be controlled without naming it: manifest default next

Note that the start command is synchronous and doesn't return until all the items have been played, which will be either when there is no news left or the maximum number of items have been read, in this case 10.

Every manifest has the same set of driver commands, based in a suitably named directory under $MINBASE/etc/manifest. These commands are held in files:

onstart: This is an optional script that triggers an introduction to the manifest as a whole. This could be an initial "here is the news" kind of announcement. The first element of the manifest should not be played here, however.

onmore: This is another optional script, covering the additional information to be played. The script should exit with an error code of 1 to terminate the playback.

onnext: This is obligatory and called once at the start to initiate the first piece of information and repeated for each element in the manifest. Like onmore, it should return an exit code of 1 to prevent any future results.

onstop: This is called, optionally, at the end of the sequence and usually initiates a chime or conferment that the manifest has completed. This happens regardless of whether it ended naturally or by forcible termination.

terminate: This kills any process spawned from an onnext output. It is optional and needed only for those scripts that launch additional programs, such as the media player that must invoke mp3player default stop. If this doesn't exist, the process is killed using the standard Linux command.

■ Note You can connect the music manifest to Cosmic in order to trigger a few random songs at bedtime or read the news in the morning.

The news manifest is programmed, by default, to read the top headlines from the BBC news site, while the music one will randomly search a given directory and play music it finds there.

Marple

Marple stands for the Minerva Appliance Routing and ProtocoL Engine. This is a mechanism whereby you can control a device, such as a TV card, from a command on one machine while using the command and resources of another. This allows you to spread the hardware load between machines or to distribute commands to remote servers that service peripherals that are ineffective in other locations— X10 gateways, notably.

You can use the same mechanism to invoke software-only devices, such as media players and Festival, allowing music to be streamed into locations where physical speaker cabling is not possible. The system is bidirectional, so you can also get a remote machine to send any commands it gets to the main server for processing.

Marple was introduced in version 2.2.0 of Minerva and is now supported by all the current Bearskin commands and used transparently to the user.

Was this article helpful?

0 0
Productivity

Productivity

Discover Strategies for Getting Things Done. You do not need to continue wasting valuable time in your work day with trial-and-error. In a very short period of time, you can learn how to develop positive strategies to make every hour of your work day count.

Get My Free Ebook


Post a comment