forked from pypt/FreeDMG
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFDTaskWrapper.m
173 lines (138 loc) · 6.58 KB
/
FDTaskWrapper.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
File: FDTaskWrapper.m
Description: This is the implementation of a generalized process handling class that that makes
asynchronous interaction with an NSTask easier.
FDTaskWrapper objects are one-shot (since NSTask is one-shot);
if you need to run a task more than once, destroy/create new TaskWrapper objects.
Copyright (C) 2004-2008 Eddie Kelley <[email protected]>
This file is part of FreeDMG
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Author: eK
Version History: 1.1/1.2 released to fix a few bugs (not always removing the notification center,
forgetting to release in some cases)
1.3 fixes a code error (no incorrect behavior) where we were checking for
if (task) in the -getData: notification when task would always be true.
Now we just do the right thing in all cases without the superfluous if check.
*/
#import "FDTaskWrapper.h"
@implementation FDTaskWrapper
// Do basic initialization
- (id)initWithController:(id <FDTaskWrapperController>)cont arguments:(NSArray *)args
{
self = [super init];
controller = cont;
arguments = [args retain];
return self;
}
// tear things down
- (void)dealloc
{
[self stopProcess];
[arguments release];
[task release];
[super dealloc];
}
// Here's where we actually kick off the process via an NSTask.
- (void) startProcess
{
// We first let the controller know that we are starting
[controller processStarted];
task = [[NSTask alloc] init];
// The output of stdout and stderr is sent to a pipe so that we can catch it later
// and send it along to the controller; notice that we don't bother to do anything with stdin,
// so this class isn't as useful for a task that you need to send info to, not just receive.
[task setStandardOutput: [NSPipe pipe]];
[task setStandardError: [task standardOutput]];
// The path to the binary is the first argument that was passed in
[task setLaunchPath: [arguments objectAtIndex:0]];
// The rest of the task arguments are just grabbed from the array
[task setArguments: [arguments subarrayWithRange: NSMakeRange (1, ([arguments count] - 1))]];
// Here we register as an observer of the NSFileHandleReadCompletionNotification, which lets
// us know when there is data waiting for us to grab it in the task's file handle (the pipe
// to which we connected stdout and stderr above). -getData: will be called when there
// is data waiting. The reason we need to do this is because if the file handle gets
// filled up, the task will block waiting to send data and we'll never get anywhere.
// So we have to keep reading data from the file handle as we go.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(getData:)
name: NSFileHandleReadCompletionNotification
object: [[task standardOutput] fileHandleForReading]];
// We tell the file handle to go ahead and read in the background asynchronously, and notify
// us via the callback registered above when we signed up as an observer. The file handle will
// send a NSFileHandleReadCompletionNotification when it has data that is available.
[[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];
// launch the task asynchronously
[task launch];
}
// If the task ends, there is no more data coming through the file handle even when the notification is
// sent, or the process object is released, then this method is called.
- (void) stopProcess
{
/* // we tell the controller that we finished, via the callback, and then blow away our connection
// to the controller. NSTasks are one-shot (not for reuse), so we might as well be too.
[controller processFinished];
controller = nil;*/
NSData *data;
// It is important to clean up after ourselves so that we don't leave potentially deallocated
// objects as observers in the notification center; this can lead to crashes.
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object: [[task standardOutput] fileHandleForReading]];
// Make sure the task has actually stopped!
[task terminate];
// if we are waiting for the task to terminate, continue waiting...
if([task isRunning])
[task waitUntilExit];
while ((data = [[[task standardOutput] fileHandleForReading] availableData]) && [data length])
{
[controller appendOutput: [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]];
}
// we tell the controller that we finished, via the callback, and then blow away our connection
// to the controller. NSTasks are one-shot (not for reuse), so we might as well be too.
[controller processFinished];
controller = nil;
}
// This method is called asynchronously when data is available from the task's file handle.
// We just pass the data along to the controller as an NSString.
- (void) getData: (NSNotification *)aNotification
{
NSData *data = [[aNotification userInfo] objectForKey:NSFileHandleNotificationDataItem];
// If the length of the data is zero, then the task is basically over - there is nothing
// more to get from the handle so we may as well shut down.
if ([data length])
{
// Send the data on to the controller; we can't just use +stringWithUTF8String: here
// because -[data bytes] is not necessarily a properly terminated string.
// -initWithData:encoding: on the other hand checks -[data length]
[controller appendOutput: [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]];
} else {
// We're finished here
[self stopProcess];
}
// we need to schedule the file handle go read more data in the background again.
[[aNotification object] readInBackgroundAndNotify];
}
-(int) terminationStatus
{
return [task terminationStatus];
}
-(int) processID
{
return [task processIdentifier];
}
-(BOOL) isRunning
{
return [task isRunning];
}
-(void) waitUntilExit
{
[task waitUntilExit];
}
@end