I tried to do (see this and this ) with the last NSUserScriptTask class and its subclasses, and so far I have solved some problems, but some others still have to be solved. As you can see from the docs , NSUserScriptTask does not allow canceling tasks. So, I decided to create a simple executable file that takes the path to the script as arguments and runs the script. That way, I can start the helper from my main application using NSTask and call [task terminate] when necessary. However, I need:
- The main application for receiving output and errors from the assistant created by him.
- Helper terminates only when NSUserScriptTask is executed
The code for the main application is simple: just run NSTask with the relevant information. Here is what I have now (for simplicity, I ignored the code for bookmarks with security protection, etc., which come out of the problem. But do not forget that this works in the sandbox):
// Create task task = [NSTask new]; [task setLaunchPath: [[NSBundle mainBundle] pathForResource: @"ScriptHelper" ofType: @""]]; [task setArguments: [NSArray arrayWithObjects: scriptPath, nil]]; // Create error pipe NSPipe* errorPipe = [NSPipe new]; [task setStandardError: errorPipe]; // Create output pipe NSPipe* outputPipe = [NSPipe new]; [task setStandardOutput: outputPipe]; // Set termination handler [task setTerminationHandler: ^(NSTask* task){ // Save output NSFileHandle* outFile = [outputPipe fileHandleForReading]; NSString* output = [[NSString alloc] initWithData: [outFile readDataToEndOfFile] encoding: NSUTF8StringEncoding]; if ([output length]) { [output writeToFile: outputPath atomically: NO encoding: NSUTF8StringEncoding error: nil]; } // Log errors NSFileHandle* errFile = [errorPipe fileHandleForReading]; NSString* error = [[NSString alloc] initWithData: [errFile readDataToEndOfFile] encoding: NSUTF8StringEncoding]; if ([error length]) { [error writeToFile: errorPath atomically: NO encoding: NSUTF8StringEncoding error: nil]; } // Do some other stuff after the script finished running <-- IMPORTANT! }]; // Start task [task launch];
Remember that I need the completion handler to be executed only when: (a) the task was canceled; (b) the task completed by itself, since the script was shutting down.
Now, on the side of the assistant, things are starting to get hairy, at least for me. Imagine for simplicity that the script is an AppleScript file (so I use a subclass of NSUserAppleScriptTask - in the real world, which I would have to place for three types of tasks). Here is what I got so far:
int main(int argc, const char * argv[]) { @autoreleasepool { NSString* filePath = [NSString stringWithUTF8String: argv[1]]; __block BOOL done = NO; NSError* error; NSUserAppleScriptTask* task = [[NSUserAppleScriptTask alloc] initWithURL: [NSURL fileURLWithPath: filePath] error: &error]; NSLog(@"Task: %@", task); // Prints: "Task: <NSUserAppleScriptTask: 0x1043001f0>" Everything OK if (error) { NSLog(@"Error creating task: %@", error); // This is not printed return 0; } NSLog(@"Starting task"); [task executeWithAppleEvent: nil completionHandler: ^(NSAppleEventDescriptor *result, NSError *error) { NSLog(@"Finished task"); if (error) { NSLog(@"Error running task: %@", error); } done = YES; }]; // Wait until (done == YES). How?? } return 0; }
Now I have three questions (which I want to ask with this SO entry). Firstly , the "Completed task" is never printed (the block is never called), because the task never starts. Instead, I get this on my console:
MessageTracer: msgtracer_vlog_with_keys:377: odd number of keys (domain: com.apple.automation.nsuserscripttask_run, last key: com.apple.message.signature)
I tried to run the same code from the main application, and it exits without fuss (but from the main application I lose the ability to cancel the script).
Secondly , I only want to go to the end of main ( return 0; ) after calling the completion handler. But I do not know how to do this.
Thridly , whenever an error or output from the assistant appears, I want to send this error / output back to the application, which will receive them through the errorPipe / outputPipe file. Something like fprintf(stderr/stdout, "string") does the trick, but I'm not sure if this is the right way to do this.
So, in short, any help regarding the first and second problems is appreciated. Third, I just want to make sure I have to do this.
thanks