Integrating Xgrid into Cocoa Applications, Part 2
Pages: 1, 2, 3, 4, 5
The entries in the array are set as the arguments of an NSTask,
using the method setArguments:. At the same time, other aspects
of the NSTask, such as the path of the executable to be launched
by the NSTask, which is /usr/bin/xgrid, and
the current directory, which is the directory containing the executable that
xgrid will run.
If you are wondering where I got these paths from, you can take a look at
the top of the method. There I make use of a few path manipulation methods from
the NSString class, like lastPathComponent and stringByDeletingLastPathComponent.
These methods are very useful, and used extensively throughout Photo Industry.
The Cocoa documentation will tell you more.
Returning to the bottom of the method, the NSTask is launched,
and we wait for it to complete, just like last time in the xgridcc
script. We also check the return value upon completion, and log any errors.
Whether the NSTask completes without error or not, we need to
return control to the main thread, informing the delegate that the subtask,
and possibly the whole DistributedTask is finished. To do that
we use the convenience method performSelectorOnMainThread:withObject:waitUntilDone:.
And it really is convenient, believe me.
The method called on the main thread looks like this:
-(void)subTaskDidFinishWithDictionary:(NSDictionary *)subTaskDict {
id identifier = [subTaskDict objectForKey:SubTaskIdKey];
[subTasksRunning removeObject:identifier];
// Notify delegate of subtask completion
if ( delegate &&
[delegate respondsToSelector:
@selector(distributedTask:didFinishSubTaskWithIdentifier:)] )
[delegate distributedTask:self
didFinishSubTaskWithIdentifier:identifier];
// If finished all subtasks, notify delegate
int subTasksRemaining = [subTasksRunning count];
if ( delegate &&
[delegate respondsToSelector:@selector(distributedTaskDidFinishSubTasks:)] &&
subTasksRemaining == 0 )
[delegate performSelector:@selector(distributedTaskDidFinishSubTasks:)
withObject:self afterDelay:0.0];
}
This method keeps track of which subtasks have completed, and informs the
delegate of developments. The subtask is first removed from the subTasksRunning
set. The delegate is then informed that the subtask has completed, and lastly
the delegate is informed that the distributed task has completed, if there are
no subtasks still running.
That concludes the DistributedTask code. This xgrid
wrapper is quite general, and you could very easily reuse it in your own Cocoa
software. I fully expect that in the course of time Apple will publish API that
includes a class very similar to DistributedTask, but in the meantime,
DistributedTask will at least give you a taste of Xgrid in Cocoa,
and hopefully teach you a few tricks with NSTask and multithreading.
A Not-so-Bitter PIL
Now I have a confession to make: Last time I promised to create a batch image processing app using the library Imagemagick. Well, after spending a few days trying to build something in Cocoa that would link to the Imagemagick libraries, or a command-line Imagemagick tool, and that did not have any dependencies on dynamic libraries, I began to despair. Then I remembered the Python Imaging Library (PIL).
PIL, as the name would suggest, is written in Python. Python is a great high-level scripting language, powerful, and yet extremely simple. It fills a similar role to Perl, but is easier to master, and is Object-Oriented to the core.
The Mac has attracted an enthusiastic clan of Python developers, and they
tend to congregate around the pythonmac.org
and MacPython web sites.
The Mac Python developers do a fantastic job and have managed to convince Apple
to include a Python framework in Panther. You can find it at /System/Library/Frameworks/Python.framework.
Compared to the pain I experienced trying to build a standalone version of Imagemagick, installing PIL was simplicity itself. A quick note to the MacPython mailing list turned up a standalone module built by Bob Ippolito. If you want to install this yourself:
- Install the MacPython extensions found at the MacPython web site.
- Open the application PackageManager in the MacPython directory in Applications
- Choose 'Open URL...' from the File menu, and enter Bob's repository address: http://undefined.org/python/pimp/darwin-7.2.0-Power_Macintosh.plist
- Selecting PIL in the list of modules, and click install.
This little exercise demonstrates one of the complications of grid computing,
namely that in general you need to be able to build a standalone version of
your software, with no dependencies on non-standard dylibs or frameworks. You
can check what libraries and frameworks an executable or library makes use of
using the command otool. Just issue this:
otool -L path/to/binary
on the command line.
I won't turn this into a Python lesson, but I do want to show you some parts
of the script that performs the image processing, so you get an idea of how
the agent-side code works, and how beautiful Python code is. The file agentrunscript.py
begins like this:
#!/usr/bin/env python
import sys
import os
import os.path
import string
# Add PIL directory to module search path
workingDir = os.getcwd()
pilPath = os.path.join(workingDir, "PIL")
sys.path.append( pilPath )
After importing some modules, the workingDir variable is set
to the current working directory, using the function getcwd from
the module os. A path is generated from this for the location of
PIL, which will be in the subdirectory PIL in the working directory.
The function join, from the module os.path, is used
to achieve this. The PIL directory path is then added to the search path used
by Python to find modules, using the sys.path.append function.
Python will now be able to find our copy of PIL when we come to use it.
Next, the script reads standard input. Standard input is used to send a list of the filters that should be applied to the images. The filter names are separated by colons.
# Read filters from standard input
filtersString = sys.stdin.read()
filters = string.split(filtersString, ":")
Standard input is read into the variable filtersString, and then
the function split from the module string is used
to split it into a list of filter names, which is put in the variable filters.
The split function takes an optional argument that is the separator
used for splitting the string; this has been set to a colon.
Various modules are then imported from the PIL library, and a list of JPEG files in the working directory created.
# Import PIL
# Filter any jpegs in the working directory
import Image
import ImageFilter
import ImageEnhance
import ImageOps
import glob
jpegFiles = glob.glob("*.[jJ][pP][gG]") + glob.glob("*.[jJ][eE][pP][gG]")
for infile in jpegFiles:
im = Image.open(infile).copy()
if "thumbnail" in filters:
im.thumbnail((128, 128), Image.ANTIALIAS)
if "blur" in filters:
im = im.filter(ImageFilter.BLUR)
if "emboss" in filters:
im = im.filter(ImageFilter.EMBOSS)
...
im.save(infile, "JPEG")
The function glob works much like globbing works in UNIX shell
scripting. The wildcard matches one or more of any character, and the letters
in the square brackets match exactly one letter in the filename.
A loop is used to iterate over the JPEG files; each one is opened, using the
open function from the PIL module Image, and copied.
if branches then check for each filter type in the filters
list. If the filter name is found in the list, the filter is applied. There
are several different ways of applying filters, and each filter tends to have
its own special arguments. You can learn more by reading the PIL
manual. The loop — and the script — end by saving the filtered
image under the original file name.
Hopefully this demonstrates to you that Python is not only an elegantly simple language, but also a powerful one. We have been able to build lists of files, split strings, search lists, and process images with amazing ease. If you need a scripting language for your Xgrid activities, Python comes highly recommended, not least because it is installed on every Mac sporting Panther or higher.

