Integrating Xgrid into Cocoa Applications, Part 2

In Part
1 of this two-part extravaganza we covered a lot of ground, with barely
a mention of Cocoa. We were witness to a future vision for Xgrid, installed
its present incarnation, played with it a bit, and then got down and dirty
on the command line. The title would suggest that there may actually be some
Cocoa in these pages, and I am here to assure you that it wasn't all just a
PR stunt to capture your attention. I will now deliver on the promise.

Photo Industry

In this part of our journey into Xgrid, we're going to develop a little Cocoa
application called Photo Industry. This will be an Xgrid-enabled app,
and what's more, it will be a standalone application, not an Xgrid client plugin.
To achieve this goal, we'll leverage the xgrid command-line tool
using a Cocoa class called NSTask. I hope that in the near future,
perhaps as early as WWDC, Apple will make this technique obsolete by publishing
an Xgrid client Cocoa API, but until that time we can at least appreciate the
potential of Xgrid by wrapping the xgrid tool.

Photo Industry is a JPEG image filtering program. The best way to see what
it does is to download the finished product, and try it out. Here is how:

Make sure you are running Mac OS X 10.3.x. Photo Industry will not work
on earlier versions of Mac OS X.

Download the Photo Industry app here, and put it somewhere
on your hard disk. (By the way, if you are wondering who created the swish
icon, you need look no further than
Bobby is
a young lad with a lot of talent, and will create polished icons for you at
a fraction of the price of the big boys.)

Start it up by double clicking.

Start an Xgrid controller on your computer in the System Preferences panel,
and start agents on one or more computers attached to your LAN or WLAN.

In Photo Industry, select one or more filters from the table shown.

In Finder, select a number of JPEG images (say <10), and drag them to
the top image view in Photo Industry. WARNING: PLEASE DON'T USE YOUR
SOLE COPY OF AUNTY JOAN'S WEDDING SNAPS. MAKE A COPY, OR USE IMAGES THAT YOU
DON'T MIND LOSING SHOULD ANYTHING GO WRONG.

When presented with an Open sheet, select a directory where you would like
to have the output images end up.

Wait. You can monitor progress on the progress bar, with the timer, and
from the display of processed images that appear in the lower image view.

If all goes well, your processed images should be in the output directory
upon completion.

Play around with the app for a few minutes. See what happens to the processing
time when you add an agent to your Xgrid "cluster" or take one away. And when
you are ready, we can start looking at how it all works.

Wrapping xgrid in Cocoa

To get started, download the Xcode project and source code here.
Unpack it, and open it in Xcode.

The most generic class in this project is called DistributedTask.
It is the class that wraps the xgrid tool, providing its functionality
to the rest of the Cocoa source. You could probably use this class directly
in your own projects without any modification at all.

DistributedTask is really nothing more than an Xgrid class, and
should an Apple Xgrid API ever appear, I would expect something very similar
to DistributedTask to be in it. The name has been inspired by similarities
with Cocoa's NSTask: DistributedTask is basically
the same as NSTask, but runs a series of commands on a distributed
Xgrid network, rather than just a single command on the local machine.

However, the name could cause some confusion, so let's clarify things before
we start. What we call a distributed task in Photo Industry, roughly
corresponds to a job in Xgrid. A distributed task is made up of a number
of subtasks, which are approximately the same as tasks in Xgrid
terminology. Hopefully I've made that confusing enough for you.

The DistributedTask class itself is pretty straightforward, though
it does involve multithreading. Here is the public interface:

As you can see, the class makes use of a delegate, which is a common technique
in Cocoa. The delegate is registered to receive information about the distributed
task when certain events occur. In this case, a delegate message is sent when
the distributed task is launched, when one of its subtasks completes, and when
all subtasks have finished.

The class interface block includes a number of attributes that are important
for the implementation, and we will discuss these as they come up.

The DistributedTask initializer takes a single argument, namely
the Xgrid controller to which it should connect. We just use localhost
in Photo Industry, but you could very easily implement a more complicated controller
location scheme involving Rendezvous.

The delegate accessor methods are also present in the interface, along with
an accessor for the controller URL. Note that there is only a getter for the
URL; you can't change the controller after you have initialized the class object,
so if later you want to use a different controller, you need to create a new
DistributedTask.

Two more rather important methods follow. The first is the method you use to
add subtasks to the distributed task. It takes a number of arguments, such as
the path to the command to launch; working directory path; output directory
path; and standard input and output paths. You may recognize these arguments
as corresponding to the command-line arguments of the xgrid command.
That's no coincidence, because DistributedTask is really nothing
more than a Cocoa xgrid command.

The last method, launch is pretty self explanatory. "Launch" is
used instead of "run," or another apt verb, in an attempt at consistency with
the terminology used in NSTask.

The implementation file of DistributedTask begins by declaring
some private methods, and a number of strings.

We'll address the private methods below. The strings are all keys for a dictionary.
Rather than defining a separate SubTask class, I have opted to
simply use dictionaries to store information pertaining to each subtask. Defining
the keys like this reduces the chances of making a spelling error in a string,
which would result in a run-time bug, and also makes explicit what entries a
subtask dictionary contains. It is really very similar to defining a struct
in C; after all, a dictionary is really no more than a dynamic struct.

The implementation proper begins with the initializer and deallocator.