Registering for a NSFileHandleReadToEndOfFileCompletionNotification - objective-c

I'm trying to figure out how to make this piece of code from a previous question work, but I'm stuck on the part on how to 'register' a NSFileHandleReadToEndOfFileCompletionNotification.
This is my code:
NSTask *topTask = [NSTask new];
[topTask setLaunchPath:#"/usr/bin/top"];
[topTask setArguments:[NSArray arrayWithObjects:#"-s", #"1", #"-l", #"3600", #"-stats", #"pid,cpu,time,command", nil]];
NSPipe *outputPipe = [NSPipe pipe];
[topTask setStandardOutput:outputPipe];
[topTask launch];
... which runs fine until I add this:
[[outputPipe fileHandleForReading] readToEndOfFileInBackgroundAndNotify];
... which causes the program to freeze. And when I add this:
NSString *outputString = [[[NSString alloc] initWithData:[[notification userInfo] objectForKey:NSFileHandleNotificationDataItem] encoding:NSUTF8StringEncoding] autorelease];
... the code doesn't compile and I get the !warning
'notification' undeclared.
Any assistance on this matter earns copious amounts of gratitude on my behalf.

Running top with those parameters in the command line will cause it to continually print out stats and it will never write EOF. That's why -readToEndOfFileInBackgroundAndNotify runs forever.
It sounds like you may just want to read the first iteration and then kill the task.
As for the error, it sounds like you simply don't have a variable named notification in the method containing that line.

Related

NSTask with if statement?

I'm not really sure how to phrase this question, but I'll try my best. I have an NSTask that currently unzips an archive, and it needs to also support archives which might have a password. I have no idea how to go about pausing the task or how to detect if a password might be present, get the input if needed then proceed, or if this is even possible?
NSTask *unzip = [[NSTask alloc] init];
[unzip setLaunchPath:#"/usr/bin/unzip"];
[unzip setArguments:[NSArray arrayWithObjects:#"-u", #"-d",
destination, zipFile, nil]];
NSPipe *aPipe = [[NSPipe alloc] init];
[unzip setStandardOutput:aPipe];
[unzip launch];
// if statement here?
[unzip waitUntilExit];
[unzip release];
From terminal stdout looks like this:
Archive: encrypted.zip
creating: test
[encrypted.zip] test password:
skipping: test incorrect password
Also I wish not to limit this to zip files, since I also use a similar task to untar/gzip archives.
Is there are way to have the NSTask pop up a modal window if it detects an archive with a password or from certain stdout it might receive?

Run user defined command with NSTask

I would like to execute a terminal command specified by the user. For example, the user might write killall "TextEdit" or say "Hello world!" in a text field, and I want to execute that command.
NSTask is the way to go, except I have two problems with it:
First: the arguments. Right now I'm doing this:
NSArray* args = [commandString componentsSeparatedByString: #" "];
[task setArguments: [args subarrayWithRange: NSMakeRange(1, [args count] - 1)]]; // First one is the command name
Is this the way to do it? I don't think I've had problems with this yet, but I doesn't look like it's safe. Imagine this: the user writes killall 'Address Book' but the command receives as arguments 'Address and Book'?? That doesn't work. So, what should I do instead? How can I safely parse the arguments?
Second: the launch path. It's much more user-friendly to only have to write the name of the command, instead of the complete path to it. So I want to support that, which means finding out programmatically the full path for a command having only it's name. For that I wrote a category on NSTask like this:
+ (NSString*)completePathForExec: (NSString*)exec
{
NSTask* task = [[NSTask alloc] init];
NSPipe* pipe = [[NSPipe alloc] init];
NSArray* args = [NSArray arrayWithObject: exec];
[task setLaunchPath: #"/usr/bin/which"];
[task setArguments: args];
[task setStandardOutput: pipe];
[task setStandardError: pipe];
[task launch];
[task waitUntilExit];
NSFileHandle* file = [pipe fileHandleForReading];
NSString* result = [[NSString alloc] initWithData: [file readDataToEndOfFile] encoding: NSASCIIStringEncoding];
if ([result length]) {
if ([result hasSuffix: #"\n"]) { result = [result substringWithRange: NSMakeRange(0, [result length] - 1)]; }
return result;
}
else { return exec; }
}
This seems to works well. However, how can I be sure that this path: /usr/bin/which will always work? I mean: will it work on 10.6, 10.7, 10.8, etc? I think I had a problem once where the path to a shell command changed with the system version, and you can never be too careful.
If the path is guaranteed to stay the same, then this isn't a problem. If it changes, then how can I know the 'path to the path-finder'?
It'll be far easier for you to not re-invent the command line parsing wheel. But, of course, going down the route of executing arbitrary user entered code is a security nightmare (tempered by the fact that the user has access to the system and, thus, could probably just run Terminal directly).
Specifically, have NSTask wrap an invocation of one of the shells with the command line option to have it execute an arbitrary string.
sh -c "ls -alF"
This would allow you to pass the path to sh as your launch path, which is in a fixed location on every system. The #"-c" argument tells sh to parse the next argument as a script and, of course, the next argument is whatever the user entered.
Note, this will also give the user the ability to pipe stuff, too.

InterProcess Communication on MacOSX Lion

I'm trying to figure out how to set up IPC between my custom app and a pre-made program.
I'm using MacOSX Lion 10.7.2 and Xcode 4.2.1.
It doesn't matter actually what program exactly, since I believe that a similar reasoning may be applied to any kind of external process.
For testing purposes I'm using a simple bash script:
#test.sh
echo "Starting"
while read out
do
echo $out
done
What I would like to achieve is to redirect input and output of this script, using my app to send inputs to it and read its outputs.
I tried to use NSTask,NSPipe and NSFileHandle as follows:
-(void)awakeFromNib {
task = [[NSTask alloc] init];
readPipe = [NSPipe pipe];
writePipe = [NSPipe pipe];
[task setStandardOutput:readPipe];
[task setStandardInput:writePipe];
[task setLaunchPath:#"/path/test.sh"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(read:)
name:NSFileHandleReadCompletionNotification
object:nil];
[[readPipe fileHandleForReading] readInBackgroundAndNotify];
[task launch];
}
-(IBAction)write:(id)sender {
NSLog(#"Write called: %d %#\n",[task isRunning],writePipe);
NSFileHandle *writeHandle = [writePipe fileHandleForWriting];
NSString *message = #"someString";
[writeHandle writeData:[message dataUsingEncoding:NSUTF8StringEncoding] ];
}
-(void)read:(NSNotification*)notification {
NSString *output = [[NSString alloc] initWithData:[[notification userInfo] valueForKey: NSFileHandleNotificationDataItem]
encoding:NSUTF8StringEncoding];
NSLog(#"%#",output);
[output release];
[[notification object] readInBackgroundAndNotify];
}
but I'm able only to read the output of test.sh, not to send it any input.
Actually any other example I saw on the web is pretty similar to my code, so I'm not sure if this issue is due to some mistake(s) of mine or to other issues (like app's sandboxing of MacOS Lion).
I've checked XPC documentation, but, according to my researches, in order to use XPC API to IPC, both sides should connect to the same service.
That's not what I'm looking for since I don't want to alter the script in any way, I just want redirect its input and output.
Is my issue due to the lack of XPC and/or to app's sandboxing?
If yes, is there a way to use XPC without modifying the script?
If no, then may somebody explain me what I'm doing wrong?
You don't need XPC for this. Won't make any difference.
Is your script / external process able to read input when you pipe something to it on the command line
% echo "foobar" | /path/test.sh
?
How much data are you sending to it. Writing will be buffered. IIRC -synchronizeFile will flush buffers -- same as fsync(2).

Is there an Instruments API?

Is it possible to setup an Instruments run programmatically from my code? For instance, I'd like to structure my code something like this where startTrace might setup a specific probe for the current thread and start recording while stopTrace would stop recording. I would be writing the content of those routines using the Instruments API that is the subject of this question.
-(void)myInterestingMethod
{
[self startTrace];
// do something interesting and performance critical
[self stopTrace];
}
If the above isn't available, is setting up my own DTrace probe a viable alternative?
Doesn't look like there's anything straight-forward, but there is an instruments command-line tool. Here's some quick+dirty code that will invoke it and sample CPU usage for the calling process
static void sampleMe() {
// instruments -t '/Developer/Applications/Instruments.app/Contents/Resources/templates/CPU Sampler.tracetemplate' -p 26838 -l 5000
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/instruments"];
[task setArguments:[NSArray arrayWithObjects:
#"-t",
#"/Developer/Applications/Instruments.app/Contents/Resources/templates/CPU Sampler.tracetemplate",
#"-p",
[NSString stringWithFormat:#"%ld", getpid()],
#"-l",
#"5000",
nil]];
[task setCurrentDirectoryPath:NSHomeDirectory()];
[task setStandardInput:[NSPipe pipe]];
[task setStandardOutput:[NSPipe pipe]];
[task setStandardError:[NSPipe pipe]];
[task launch];
// purposely leak everything since I can't be bothered to figure out lifetimes
}
After invocation a file named instrumentscli0.trace will be in your home directory.
Update: Instruments 4.0 offers DTSendSignalFlag in the DTPerformanceSession for iOS apps.

How to insert some data into a string of AppleScript

I have a function that sends a string "theData". I want to insert that string here in this code. Would someone please tell me the correct syntax for this? Things get a bit hairy with the \"s and the "s. Thanks!
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/osascript"];
[task setArguments:[NSArray arrayWithObjects:#"-e", #"tell application \"System Events\"\n",
#"-e", #" keystroke \"" + theData + "\"",
#"-e", #"end tell", nil]];
[task launch];
icktoofay already gave the more correct answer, but let me just show how to insert a string in a string:
NSString* toBeInserted = #"for";
NSString* result = [NSString stringWithFormat:#"in%#mation",toBeInserted];
NSLog(#"%#",result);
This gives information. For more details, read Apple's doc.
I mean, Apple's doc is quite good, actually. Read it before asking a question here at SO.
By the way, you don't have to launch osascript to execute AppleScript. You can use NSAppleScript as in
NSAppleScript* script=[[NSAppleScript alloc] initWithSource:#"tell app \"Finder\" to activate "];
NSDictionary*error;
[script executeAndReturnError:&error];
[script release];
Well, NSAppleScript is an oddity which requires NSDictionary, not an NSError, to report an error ...
Or, you can use Scripting Bridge to map AppleScript objects to Objective-C objects.
I see you have another way of doing it, but you can use format strings to accomplish this
[NSString stringWIthFormat: #"part one %# part 2", theData];
Assuming the data is an nsstring containing "hello", this will give you:
#"part one hello part 2"