Luky/System/TaskWrapper.m

00001 /*
00002  File:          TaskWrapper.m
00003 
00004  Description:   This is the implementation of a generalized process handling class that that makes asynchronous interaction with an NSTask easier.  Feel free to make use of this code in your own applications.  TaskWrapper objects are one-shot (since NSTask is one-shot); if you need to run a task more than once, destroy/create new TaskWrapper objects.
00005 
00006  Author:                EP & MCF
00007 
00008  Copyright:     © Copyright 2002 Apple Computer, Inc. All rights reserved.
00009 
00010  Disclaimer:    IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
00011  ("Apple") in consideration of your agreement to the following terms, and your
00012  use, installation, modification or redistribution of this Apple software
00013  constitutes acceptance of these terms.  If you do not agree with these terms,
00014  please do not use, install, modify or redistribute this Apple software.
00015 
00016  In consideration of your agreement to abide by the following terms, and subject
00017  to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
00018  copyrights in this original Apple software (the "Apple Software"), to use,
00019  reproduce, modify and redistribute the Apple Software, with or without
00020  modifications, in source and/or binary forms; provided that if you redistribute
00021  the Apple Software in its entirety and without modifications, you must retain
00022  this notice and the following text and disclaimers in all such redistributions of
00023  the Apple Software.  Neither the name, trademarks, service marks or logos of
00024  Apple Computer, Inc. may be used to endorse or promote products derived from the
00025  Apple Software without specific prior written permission from Apple.  Except as
00026  expressly stated in this notice, no other rights or licenses, express or implied,
00027  are granted by Apple herein, including but not limited to any patent rights that
00028  may be infringed by your derivative works or by other works in which the Apple
00029  Software may be incorporated.
00030 
00031  The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
00032  WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
00033  WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
00034  PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
00035  COMBINATION WITH YOUR PRODUCTS.
00036 
00037  IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
00038  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
00039                         GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00040  ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
00041  OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
00042  (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
00043  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00044  
00045   Version History: 1.1/1.2 released to fix a few bugs (not always removing the notification center,
00046                                                   forgetting to release in some cases)
00047                     1.3    fixes a code error (no incorrect behavior) where we were checking for
00048                            if (task) in the -getData: notification when task would always be true.
00049                            Now we just do the right thing in all cases without the superfluous if check.
00050  */
00051 
00052 
00053 
00054 #import "TaskWrapper.h"
00055 
00056 @implementation TaskWrapper
00057 
00058 // Do basic initialization
00059 - (id)initWithController:(id <TaskWrapperController>)cont arguments:(NSArray *)args
00060 {
00061     self = [super init];
00062     controller = cont;
00063     arguments = [args retain];
00064     
00065     return self;
00066 }
00067 
00068 // tear things down
00069 - (void)dealloc
00070 {
00071     [self stopProcess];
00072 
00073     [arguments release];
00074     [task release];
00075     [super dealloc];
00076 }
00077 
00078 // Here's where we actually kick off the process via an NSTask.
00079 - (void) startProcess
00080 {
00081     // We first let the controller know that we are starting
00082     [controller processStarted:self];
00083 
00084     task = [[NSTask alloc] init];
00085     // The output of stdout and stderr is sent to a pipe so that we can catch it later
00086     // and send it along to the controller; notice that we don't bother to do anything with stdin,
00087     // so this class isn't as useful for a task that you need to send info to, not just receive.
00088     [task setStandardOutput: [NSPipe pipe]];
00089     [task setStandardError: [task standardOutput]];
00090     // The path to the binary is the first argument that was passed in
00091     [task setLaunchPath: [arguments objectAtIndex:0]];
00092     // The rest of the task arguments are just grabbed from the array
00093     [task setArguments: [arguments subarrayWithRange: NSMakeRange (1, ([arguments count] - 1))]];
00094 
00095     // Here we register as an observer of the NSFileHandleReadCompletionNotification, which lets
00096     // us know when there is data waiting for us to grab it in the task's file handle (the pipe
00097     // to which we connected stdout and stderr above).  -getData: will be called when there
00098     // is data waiting.  The reason we need to do this is because if the file handle gets
00099     // filled up, the task will block waiting to send data and we'll never get anywhere.
00100     // So we have to keep reading data from the file handle as we go.
00101     [[NSNotificationCenter defaultCenter] addObserver:self 
00102         selector:@selector(getData:) 
00103         name: NSFileHandleReadCompletionNotification 
00104         object: [[task standardOutput] fileHandleForReading]];
00105     // We tell the file handle to go ahead and read in the background asynchronously, and notify
00106     // us via the callback registered above when we signed up as an observer.  The file handle will
00107     // send a NSFileHandleReadCompletionNotification when it has data that is available.
00108     [[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];
00109 
00110     // launch the task asynchronously
00111     [task launch];    
00112 }
00113 
00114 // If the task ends, there is no more data coming through the file handle even when the notification is
00115 // sent, or the process object is released, then this method is called.
00116 - (void) stopProcess
00117 {
00118 /*    // we tell the controller that we finished, via the callback, and then blow away our connection
00119     // to the controller.  NSTasks are one-shot (not for reuse), so we might as well be too.
00120     [controller processFinished];
00121     controller = nil;*/
00122     NSData *data;
00123     
00124     // It is important to clean up after ourselves so that we don't leave potentially deallocated
00125     // objects as observers in the notification center; this can lead to crashes.
00126     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object: [[task standardOutput] fileHandleForReading]];
00127     
00128     // Make sure the task has actually stopped!
00129     [task terminate];
00130 
00131    while ((data = [[[task standardOutput] fileHandleForReading] availableData]) && [data length])
00132    {
00133        [controller appendOutput: [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease] fromTask:self];
00134    }
00135 
00136    // we tell the controller that we finished, via the callback, and then blow away our connection
00137    // to the controller.  NSTasks are one-shot (not for reuse), so we might as well be too.
00138    [controller processFinished:self];
00139    controller = nil;
00140 }
00141 
00142 // This method is called asynchronously when data is available from the task's file handle.
00143 // We just pass the data along to the controller as an NSString.
00144 - (void) getData: (NSNotification *)aNotification
00145 {
00146     NSData *data = [[aNotification userInfo] objectForKey:NSFileHandleNotificationDataItem];
00147     // If the length of the data is zero, then the task is basically over - there is nothing
00148     // more to get from the handle so we may as well shut down.
00149     if ([data length])
00150     {
00151         // Send the data on to the controller; we can't just use +stringWithUTF8String: here
00152         // because -[data bytes] is not necessarily a properly terminated string.
00153         // -initWithData:encoding: on the other hand checks -[data length]
00154         [controller appendOutput: [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease] fromTask:self];
00155     } else {
00156         // We're finished here
00157         [self stopProcess];
00158     }
00159     
00160     // we need to schedule the file handle go read more data in the background again.
00161     [[aNotification object] readInBackgroundAndNotify];  
00162 }
00163 
00164 @end
00165 

Generated on Sat Aug 26 21:14:16 2006 for MacTrek by  doxygen 1.4.7