oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

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 
    // 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 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:

  1. Install the MacPython extensions found at the MacPython web site.
  2. Open the application PackageManager in the MacPython directory in Applications
  3. Choose 'Open URL...' from the File menu, and enter Bob's repository address:
  4. 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 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 =
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 =
    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)

    ..., "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.

Pages: 1, 2, 3, 4, 5

Next Pagearrow