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