-
Notifications
You must be signed in to change notification settings - Fork 3.5k
/
NSTask+RACSupport.m
127 lines (101 loc) · 4.34 KB
/
NSTask+RACSupport.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
//
// NSTask+RACSupport.m
// ReactiveCocoa
//
// Created by Josh Abernathy on 5/10/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSTask+RACSupport.h"
#import "NSFileHandle+RACSupport.h"
#import "NSNotificationCenter+RACSupport.h"
NSString * const NSTaskRACSupportErrorDomain = @"NSTaskRACSupportErrorDomain";
NSString * const NSTaskRACSupportOutputData = @"NSTaskRACSupportOutputData";
NSString * const NSTaskRACSupportErrorData = @"NSTaskRACSupportErrorData";
NSString * const NSTaskRACSupportTask = @"NSTaskRACSupportTask";
NSString * const NSTaskRACSupportOutputString = @"NSTaskRACSupportOutputString";
NSString * const NSTaskRACSupportErrorString = @"NSTaskRACSupportErrorString";
NSString * const NSTaskRACSupportTaskArguments = @"NSTaskRACSupportTaskArguments";
const NSInteger NSTaskRACSupportNonZeroTerminationStatus = 123456;
@implementation NSTask (RACSupport)
- (RACSignal *)rac_standardOutput {
if(![[self standardOutput] isKindOfClass:[NSPipe class]]) {
[self setStandardOutput:[NSPipe pipe]];
}
return [self rac_signalForPipe:[self standardOutput]];
}
- (RACSignal *)rac_standardError {
if(![[self standardError] isKindOfClass:[NSPipe class]]) {
[self setStandardError:[NSPipe pipe]];
}
return [self rac_signalForPipe:[self standardError]];
}
- (RACSignal *)rac_signalForPipe:(NSPipe *)pipe {
NSFileHandle *fileHandle = [pipe fileHandleForReading];
return [fileHandle rac_readInBackground];
}
- (RACSignal *)rac_completion {
return [[[NSNotificationCenter.defaultCenter rac_addObserverForName:NSTaskDidTerminateNotification object:self] any] mapReplace:RACUnit.defaultUnit];
}
- (RACCancelableSignal *)rac_run {
return [self rac_runWithScheduler:[RACScheduler immediateScheduler]];
}
- (RACCancelableSignal *)rac_runWithScheduler:(RACScheduler *)scheduler {
NSParameterAssert(scheduler != nil);
RACReplaySubject *subject = [RACReplaySubject subject];
__block BOOL canceled = NO;
[[RACScheduler mainQueueScheduler] schedule:^{
NSMutableData * (^aggregateData)(NSMutableData *, NSData *) = ^(NSMutableData *running, NSData *next) {
[running appendData:next];
return running;
};
// TODO: should we aggregate the data on the given scheduler too?
RACConnectableSignal *outputSignal = [[self.rac_standardOutput aggregateWithStart:[NSMutableData data] combine:aggregateData] publish];
__block NSData *outputData = nil;
[outputSignal subscribeNext:^(NSData *accumulatedData) {
outputData = accumulatedData;
}];
RACConnectableSignal *errorSignal = [[self.rac_standardError aggregateWithStart:[NSMutableData data] combine:aggregateData] publish];
__block NSData *errorData = nil;
[errorSignal subscribeNext:^(NSData *accumulatedData) {
errorData = accumulatedData;
}];
// wait until termination's signaled and output and error are done
[[RACSignal merge:@[ outputSignal, errorSignal, self.rac_completion ]] subscribeNext:^(id _) {
// nothing
} completed:^{
if(canceled) return;
[scheduler schedule:^{
if(canceled) return;
if([self terminationStatus] == 0) {
[subject sendNext:outputData];
[subject sendCompleted];
} else {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
if(outputData != nil) {
[userInfo setObject:outputData forKey:NSTaskRACSupportOutputData];
NSString *string = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
if(string != nil) [userInfo setObject:string forKey:NSTaskRACSupportOutputString];
}
if(errorData != nil) {
[userInfo setObject:errorData forKey:NSTaskRACSupportErrorData];
NSString *string = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];
if(string != nil) [userInfo setObject:string forKey:NSTaskRACSupportErrorString];
}
if([self arguments] != nil) [userInfo setObject:[self arguments] forKey:NSTaskRACSupportTaskArguments];
[userInfo setObject:self forKey:NSTaskRACSupportTask];
[subject sendError:[NSError errorWithDomain:NSTaskRACSupportErrorDomain code:NSTaskRACSupportNonZeroTerminationStatus userInfo:userInfo]];
}
}];
}];
[outputSignal connect];
[errorSignal connect];
[self launch];
}];
__weak NSTask *weakSelf = self;
return [subject asCancelableWithBlock:^{
NSTask *strongSelf = weakSelf;
canceled = YES;
[strongSelf terminate];
}];
}
@end