-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathSSYAppleScripter.m
223 lines (204 loc) · 10.9 KB
/
SSYAppleScripter.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#import "SSYAppleScripter.h"
/* Constants from the Carbon SpenScripting Framework */
FourCharCode AppleScriptSuite = 'ascr';
FourCharCode AppleScriptSubroutineEvent = 'psbr';
FourCharCode AppleScriptSubroutineName = 'snam';
@implementation SSYAppleScripter
+ (void)executeScriptWithUrl:(NSURL* _Nullable)scriptUrl
handlerName:(NSString* _Nullable)handlerName
handlerParameters:(NSArray* _Nullable)handlerParameters
ignoreKeyPrefix:(NSString* _Nullable)ignoreKeyPrefix
userInfo:(NSObject* _Nullable)userInfo
blockUntilCompletion:(BOOL) blockUntilCompletion
failSafeTimeout:(NSTimeInterval)failSafeTimeout
completionHandler:(void (^)(
id payload,
id _Nullable userInfo,
NSError * _Nullable scriptError))completionHandler {
NSError* error = nil;
NSUserAppleScriptTask* script = [[NSUserAppleScriptTask alloc] initWithURL:scriptUrl
error:&error];
NSAppleEventDescriptor* requestEvent = nil;
if (handlerName) {
ProcessSerialNumber psn = { 0, kCurrentProcess };
NSAppleEventDescriptor* target = [NSAppleEventDescriptor descriptorWithDescriptorType:typeProcessSerialNumber
bytes:&psn
length:sizeof(ProcessSerialNumber)];
/* Weirdness: the handler name passed to Apple event must be
lowercase even if the name in the script has uppercase
characters! */
NSAppleEventDescriptor* handler = [NSAppleEventDescriptor descriptorWithString:[handlerName lowercaseString]];
requestEvent = [NSAppleEventDescriptor appleEventWithEventClass:AppleScriptSuite
eventID:AppleScriptSubroutineEvent
targetDescriptor:target
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];
[requestEvent setParamDescriptor:handler forKeyword:AppleScriptSubroutineName];
NSAppleEventDescriptor* parmListDescriptor = nil;
if (handlerParameters.count > 0) {
parmListDescriptor = [NSAppleEventDescriptor listDescriptor];
NSInteger i = 1; // AppleEvent list indexes start with 1d
for (id parm in handlerParameters) {
NSAppleEventDescriptor* parmDescriptor;
if ([parm isKindOfClass:[NSString class]]) {
parmDescriptor = [NSAppleEventDescriptor descriptorWithString:(NSString*)parm];
} else if ([parm isKindOfClass:[NSNull class]]) {
parmDescriptor = [NSAppleEventDescriptor nullDescriptor];
/* This branch does not work as expected.
See Note WhyEmptyString-WhyNotNull in SSYAppleScripter.h*/
/* TODO add more else if branches to support more classes
(NSNumber, etc.) here. */
} else {
NSString* errorDesc = [NSString stringWithFormat:
@"Unsupported handler parameter class %@ at index %ld. Easy to fix by adding a new branch to above code([parm isKindOfClass:[NSString class]]) {.",
[parm className],
(i-1)
];
error = [NSError errorWithDomain:@"SSYAppleScripterErrorDomain"
code:298578
userInfo:@{
NSLocalizedDescriptionKey : errorDesc
}];
break;
}
[parmListDescriptor insertDescriptor:parmDescriptor
atIndex:i];
i++;
}
}
if (!error) {
if (parmListDescriptor) {
[requestEvent setParamDescriptor:parmListDescriptor forKeyword:keyDirectObject];
}
}
}
if (error) {
if (completionHandler) {
completionHandler(nil, userInfo, error);
}
} else {
dispatch_semaphore_t semaphore = nil;
if (blockUntilCompletion) {
semaphore = dispatch_semaphore_create(0);
}
[script executeWithAppleEvent:requestEvent
completionHandler:^(NSAppleEventDescriptor * _Nullable replyEvent, NSError * _Nullable scriptError) {
NSInteger i;
id payload = nil;
if (replyEvent.descriptorType == typeAERecord) {
NSMutableDictionary* answersMutant = [[NSMutableDictionary alloc] init];
for (i=1; i<=[replyEvent numberOfItems]; i++) {
/* Reply events typically contain an even
number of items, arranged as key/value pairs */
NSAppleEventDescriptor* subdescriptor = [replyEvent descriptorAtIndex:i];
NSInteger nItems = [subdescriptor numberOfItems];
if ((nItems > 0) && (nItems%2 == 0)) {
NSUInteger j;
for(j=1; j<=[subdescriptor numberOfItems]/2; j++) {
NSString* key = [[subdescriptor descriptorAtIndex:(2*j-1)] stringValue];
if (ignoreKeyPrefix) {
if ([key hasPrefix:ignoreKeyPrefix]) {
key = [key substringFromIndex:ignoreKeyPrefix.length];
}
}
NSString* value = [[subdescriptor descriptorAtIndex:(2*j)] stringValue];
if (key && value) {
[answersMutant setObject:value
forKey:key];
}
}
break;
}
}
payload = [[NSDictionary alloc] initWithDictionary:answersMutant];
#if !__has_feature(objc_arc)
[answersMutant release];
[payload autorelease];
#endif
} else if (replyEvent.descriptorType == typeAEList) {
NSMutableArray* answersMutant = [[NSMutableArray alloc] init];
for (i=1; i<=[replyEvent numberOfItems]; i++) {
NSAppleEventDescriptor* subdescriptor = [replyEvent descriptorAtIndex:i];
[answersMutant addObject:subdescriptor.stringValue];
}
payload = [[NSArray alloc] initWithArray:answersMutant];
#if !__has_feature(objc_arc)
[answersMutant release];
[payload autorelease];
#endif
}
if (completionHandler) {
completionHandler(payload, userInfo, scriptError);
}
if (semaphore) {
dispatch_semaphore_signal(semaphore);
}
}];
if (semaphore) {
dispatch_time_t timeoutTime = (failSafeTimeout > 0.0) ? dispatch_time(DISPATCH_TIME_NOW, failSafeTimeout * NSEC_PER_SEC) : DISPATCH_TIME_FOREVER;
dispatch_semaphore_wait(
semaphore,
timeoutTime
);
#if !__has_feature(objc_arc)
dispatch_release(semaphore);
#endif
}
}
#if !__has_feature(objc_arc)
[script release];
#endif
}
+ (void)executeScriptSource:(NSString* _Nonnull)source
ignoreKeyPrefix:(NSString* _Nullable)ignoreKeyPrefix
userInfo:(NSObject* _Nullable)userInfo
blockUntilCompletion:(BOOL) blockUntilCompletion
failSafeTimeout:(NSTimeInterval)failSafeTimeout
completionHandler:(void (^ _Nullable)(
id _Nullable payload,
id _Nullable userInfo,
NSError* _Nullable scriptError))completionHandler {
CFUUIDRef cfUUID = CFUUIDCreate(kCFAllocatorDefault) ;
#if __has_feature(objc_arc)
NSString* uuid = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfUUID)) ;
#else
NSString* uuid = (NSString*)CFUUIDCreateString(kCFAllocatorDefault, cfUUID) ;
#endif
CFRelease(cfUUID) ;
NSString* scriptTempFilePath = [[NSTemporaryDirectory() stringByAppendingPathComponent:uuid] stringByAppendingPathExtension:@"scpt"];
#if !__has_feature(objc_arc)
[uuid release];
#endif
BOOL ok;
NSError* error = nil;
ok = [source writeToFile:scriptTempFilePath
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
if (!ok) {
error = [NSError errorWithDomain:@"SSYAppleScripterErrorDomain"
code:298577
userInfo:@{
NSLocalizedDescriptionKey : @"Could not write temporary script file",
NSUnderlyingErrorKey : error
}];
if (completionHandler) {
completionHandler(nil, nil, error);
}
} else {
NSURL* scriptUrl = [NSURL fileURLWithPath:scriptTempFilePath];
[self executeScriptWithUrl:scriptUrl
handlerName:nil
handlerParameters:nil
ignoreKeyPrefix:ignoreKeyPrefix
userInfo:userInfo
blockUntilCompletion:blockUntilCompletion
failSafeTimeout:failSafeTimeout
completionHandler:^(id _Nullable payload, id _Nullable userInfo, NSError * _Nullable scriptError) {
completionHandler(payload, userInfo, scriptError);
[[NSFileManager defaultManager] removeItemAtURL:scriptUrl
error:NULL];
}];
}
}
@end