macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Sweetening Your Xgrid with Cocoa
Pages: 1, 2, 3, 4, 5

Retrieving Results

When the XGJob state changes, a call is made to jobStateDidChange: (via observeValueForKeyPath:ofObject:change:context:). If the job failed, the user is informed; if it succeeded, beginRetrievingOutputStreamsForJob: is invoked to start retrieving the job output.

-(void)jobStateDidChange:(XGJob *)job {
    NSAlert *alert;
    XGResourceState state = [job state];
    switch (state) {
        case XGResourceStateFailed:
            [self setRunning:NO];
            alert = [NSAlert alertWithMessageText:@"Job failed." 
                defaultButton:@"OK" alternateButton:nil otherButton:nil
                informativeTextWithFormat:@"Try running the job again."];
            [alert runModal];
            [job removeObserver:self forKeyPath:@"state"];       
            break;
        case XGResourceStateFinished:
            [self beginRetrievingOutputStreamsForJob:job];       
            break;
    }
}

The Controller method beginRetrievingOutputStreamsForJob: requests information about the output produced by the job from the XGJob object, using the performGetOutputStreamsAction.

-(void)beginRetrievingOutputStreamsForJob:(XGJob *)job {
    // Stop monitoring job
    [job removeObserver:self forKeyPath:@"state"];

    // Begin getting output streams
    XGActionMonitor *newOutputRetrievalMonitor = [job performGetOutputStreamsAction];
    [[self outputRetrievalMonitor] removeObserver:self forKeyPath:@"outcome"]; // Stop monitoring previous
    [newOutputRetrievalMonitor addObserver:self forKeyPath:@"outcome" options:0 context:NULL];
    [self setOutputRetrievalMonitor:newOutputRetrievalMonitor];
}

Despite what you might think, this does not start downloading the output data; it simply starts retrieving information about the data that is available from the Xgrid controller. Again, KVO is used on the outcome attribute of the XGActionMonitor returned by performGetOutputStreamsAction to determine when the information becomes available. outputRetrievalStateDidChange is invoked when it does.

-(void)outputRetrievalStateDidChange {
    XGActionMonitor *monitor = [self outputRetrievalMonitor];
    NSAlert *alert;
    NSArray *streams;
    switch ( [monitor outcome] ) {
        case (XGActionMonitorOutcomeFailure):
            // Report error
            [self setRunning:NO];
            alert = [NSAlert alertWithError:[monitor error]];
            [alert runModal];
            break;
        case (XGActionMonitorOutcomeSuccess):
            streams = [[monitor results] objectForKey:XGActionMonitorResultsOutputStreamsKey];
            [self beginFileDownloadsForOutputStreams:streams];
            break;
    }
}

If the action was successful, the results dictionary of the XGActionMonitor yields an array of XGFile objects, representing the output streams we seek to download. (You are probably now starting to recognize a commonly-used pattern in XGridFoundation: invoke performBlah; monitor outcome attribute of XGActionMonitor returned with KVO; retrieve information from XGActionMonitor results dictionary.)

The last step in the process is to retrieve the stream data represented by the XGFile objects from the Xgrid controller. This is initiated in beginFileDownloadsForOutputStreams: using the XGFileDownload class.

-(void)beginFileDownloadsForOutputStreams:(NSArray *)streams {
    // Start up file downloads for standard output and standard error, if they exist.
    NSEnumerator *streamEnum = [streams objectEnumerator];
    XGFile *stream;
    while (stream = [streamEnum nextObject]) {
        if ( [[stream path] isEqualToString:XGFileStandardOutputPath] ) {
            XGFileDownload *dl = 
                [[[XGFileDownload alloc] initWithFile:stream delegate:self] autorelease];
            [self setOutputFileDownload:dl];
            [self setJobOutput:@""];
        }
        else if ( [[stream path] isEqualToString:XGFileStandardErrorPath] ) {
            XGFileDownload *dl = 
                [[[XGFileDownload alloc] initWithFile:stream delegate:self] autorelease];
            [self setErrorFileDownload:dl];
            [self setJobError:@""];
        }
    }

    // If there are no streams, the command entered probably did not exist.
    // Warn the user.
    if ( [streams count] == 0 ) {
        NSAlert *alert = [NSAlert alertWithMessageText:@"The command does not exist." 
            defaultButton:@"OK" alternateButton:nil otherButton:nil 
            informativeTextWithFormat:@"Check the path of the command you entered."];
        [alert runModal];
        [self setRunning:NO];
    }
}

The path attribute of the XGFile objects is used to establish which streams are available. The string constants XGFileStandardOutputPath and XGFileStandardErrorPath identify standard output and standard error, respectively. For each stream, an XGFileDownload object is initialized with the corresponding XGFile, and its delegate set to the Controller. The downloads' progress is monitored via calls to delegate methods in this case.

Note the last part of beginFileDownloadsForOutputStreams:; it checks to see if the streams array is empty. If it is, it is assumed that the command entered by the user did not exist. Note that this seems to be the only means of establishing this — no error will arise from running the job. There also doesn't seem to be any way to retrieve the exit code of a command using XGridFoundation. Hopefully these teething problems will be ironed out in future releases.

There are several XGFileDownload delegate methods. You have a few choices in this regard. You can wait until the files are downloaded in full, and stored somewhere on the local hard disk, or you can process the incoming data a little at a time. Central Command takes the latter option, because for the small amounts of output data being generated, it seems silly to store it in a temporary file.

-(void)fileDownload:(XGFileDownload *)fileDownload didReceiveData:(NSData *)data {
    NSString *newString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    if ( fileDownload == [self outputFileDownload] ) {
        newString = [[self jobOutput] stringByAppendingString:newString];
        [self setJobOutput:newString];
    }
    else if ( fileDownload == [self errorFileDownload] ) {
        newString = [[self jobError] stringByAppendingString:newString];
        [self setJobError:newString];
    }
}

-(void)fileDownloadDidBegin:(XGFileDownload *)fileDownload {
    numDownloadingFiles++;
}

-(void)fileDownloadDidFinish:(XGFileDownload *)fileDownload {
    if ( --numDownloadingFiles == 0 ) [self setRunning:NO];;
}

-(void)fileDownload:(XGFileDownload *)fileDownload didFailWithError:(NSError *)error {
    if ( --numDownloadingFiles == 0 ) [self setRunning:NO];;
    [[NSAlert alertWithError:error] runModal];
}

Most of these methods simply keep track of how many downloads are still in progress, for the purpose of updating the user interface. The method of most interest is the first: fileDownload:didReceiveData:. This accepts one of the downloaded chunks of data, converts it to a string, and adds it to the string data that has already been downloaded for the stream in question. This string then appears to the user via the magic of Bindings.

Where To Now?

Of course, you can't cover every aspect of a framework like XGridFoundation in an article like this one, but I hope you at least have a flavor of the main classes, how they interact at runtime, and some of the design patterns that recur when you use Xgrid in a Cocoa app.

I have skimped on many issues. For example, how do you upload additional files for a job, or retrieve generated files afterwards? How do you suspend or cancel a running job, or determine its progress? What about connecting to an XGController via Bonjour (the technology formerly known as Rendezvous)? (If you want to try figuring this one out yourself, I highly recommend Mike Beam's original Mac DevCenter articles[1,2].)

Unfortunately, there isn't space for any of this, but having established a basis here, it should not be too difficult for you to find answers by digging around in Apple's Xgrid example projects, which are located in /Developer/Examples/Xgrid.

Drew McCormack works at the Free University in Amsterdam, and develops the Cocoa shareware Trade Strategist.


Return to the Mac DevCenter