NSTask task

I seem to be making some small progress on NSTask.

So far:

property theFileHandle: missing value
property theTask: missing value

 set arraylist to {"-aHAXNx", "--fileflags", "--force-change", "--stats", "-v", "--progress", path1, path2}
        set thepipe to NSPipe's pipe()
	set theFileHandle to thepipe's fileHandleForReading()
	set theTask to (NSTask's alloc)'s init()
	theTask's setLaunchPath_(rsyncPath)
	theTask's setStandardOutput_(thepipe)
	theTask's setStandardError_(thepipe)
	theTask's setArguments_(arraylist)
	theTask's |launch|()

This launches the task for rsync and I can catch the readout at the end with this:

set dataString to theFileHandle's readDataToEndOfFile()
set newstring to ((current application's class "NSString")'s alloc)'s initWithData_encoding_(dataString, "NSUTF8StringEncoding")

log newstring as string

Which works but I want to get the flow as it happens:

so I set up the notification and the handler to read it in:

(current application's class "NSNotificationCenter")'s defaultCenter's addObserver__selector_name_object_(me, "readPipe:", "NSFileHandleReadCompletionNotification", null)

on readPipe_(aNotification)
		if aNotification's object ≠ theFileHandle then
			return
		else
			set dataString to aNotification's userInfo's objectForKey_("NSFileHandleNotificationDataItem")
			set newstring to ((current application's class "NSString")'s alloc)'s initWithData_encoding_(dataString, "NSUTF8StringEncoding")
			log newstring as string
		end if
		if theTask then
			theFileHandle's readInBackgroundAndNotify
		end if
	end readPipe_

but I get an error

-[NSNotificationCenter addObserver::selector:name:object:]: unrecognized selector sent to instance 0x200778660

Not sure if I translated the readPipe handler right - I am using the post on

http://macosx.com/forums/software-programming-web-scripting/4522-better-way-read-nstask.html

I really like this though and see the possibility for binding my progress fields to the values returned from the pipe

Thanks, Rob

Notice the double colon after addObserver? That means you have two underscores in your declaration. See if removing one of those fixes the issue.

Merry Christmas!

Craig

Hopefully I’ll get new glasses for Christmas!

Thanks Craig and Happy holidays to all,

Rob

Ok I got the NSTask working in my app:

set arraylist to {"-aHAXNx", "--fileflags", "--force-change", "--stats", "-v", "--progress", pos_path1, pos_path2}
	set thepipe to NSPipe's pipe()
	set theFileHandle to thepipe's fileHandleForReading()
	set theTask to (NSTask's alloc)'s init()
	theTask's setLaunchPath_(Cnewpath)
	theTask's setStandardOutput_(thepipe)
	theTask's setArguments_(arraylist)
	theTask's |launch|()
	set Nf to NSNotificationCenter's defaultCenter()
	Nf's removeObserver_(me)
	Nf's addObserver_selector_name_object_(me, "readPipe:", "NSFileHandleReadCompletionNotification", theFileHandle)
	Nf's addObserver_selector_name_object_(me, "endPipe:", "NSTaskDidTerminateNotification", theFileHandle)
									
	theFileHandle's readInBackgroundAndNotify()


on readPipe_(aNotification)
  log "read in"
  set dataString to aNotification's userInfo's objectForKey_("NSFileHandleNotificationDataItem")
  set newstring to (((current application's class "NSString")'s alloc)'s initWithData_encoding_(dataString,     "NSUTF8StringEncoding"))
  set parString to newstring as string
--set progress text field and progress bar to parsed results
end readPipe_


on endPipe_(aNotification)
  log "End Pipe"
end endPipe_

But I can’t seem to get the NSTaskDidTerminateNotification to register for the endPipe_ handler.

To stop the “Stream” I tried, in the readPipe Handler:

if parString is in {{}, "", missing value, null} then
	Nf's removeObserver_(me)
	my cancelitems_(me)
end if

which works to stop it but I should be able to get the NSTaskDidTerminateNotification to register.

Any thoughts appreciated.

rob

You’re trying to add this observer to the file handle, but you should be adding it to the task – that’s what sends NSTaskDidTerminateNotification:


	Nf's addObserver_selector_name_object_(me, "endPipe:", "NSTaskDidTerminateNotification", theTask)

That fixed it. Thanks.

One other thing I am seeing is an error in the

set dataString to aNotification's userInfo's objectForKey_("NSFileHandleNotificationDataItem")
set newstring to (((current application's class "NSString")'s alloc)'s initWithData_encoding_(dataString, "NSUTF8StringEncoding"))
set parString to newstring as string

-[NSPlaceholderString initWithData:encoding:]: unable to set argument 3 because the AppleScript value <NSAppleEventDescriptor: ‘utxt’(“NSUTF8StringEncoding”)> could not be coerced to type Q.
2009-12-29 03:36:32.699 backupC[2638:a0f] Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatiblity mapping behavior in the near future.

It doesn’t seem to cause any problems with the reading but is this important?

I am also noticing that using NSTask and reading the output this way is pretty slick but it is more CPU intensive than reading the output from an external file via “do shell script” - the old way. I am trying to find a way to slow down the reading of the pipe. If I don’t read the pipe and just run the task I notice that the task runs much faster of course but it seems this should be faster and more direct than reading from an external file where you are “polling” the data with do shell script “tail” etc…

I’ve spent a lot of time finding ways to read output in ASS apps and now with NSTask available in ASOC it seems that should be better but perhaps not. Maybe there is a way to bind the pipe output straight into the progress text fields but the data still needs to be coerced and parsed.

Thanks Shane, Rob

Rather than “NSUTF8StringEncoding”, you should be using “current application’s NSUTF8StringEncoding” (no quotes).

Interactive NSTask

I was able to get the working example to work with Mplayer in -slave -quiet mode, for an embedded media player.

The problem I am having is grasping to how send commands back to StdIn via ASOC.

Here is the an Objective-C example that I found:

http://lists.apple.com/archives/Cocoa-dev/2002/Jan/msg00073.html

The part that I know I am not doing correctly is passing an AppleScript String, convert to NSString and send it to the StdIn Pipe/write fileHandle.

Will try it again and post back where I am stuck.

BTW Thanks robdut for the working NSTask reading example. My plan is to create an open source embedded media player for internet streams using ASOC. (Still at the beginning stages as I have a lot of ground to cover learning the basics).

Any help is greatly appreciated.

Well this:

userInput = [[sender stringValue] stringByAppendingString:@“\n”];
if (inputPipe) {
myData = [userInput dataUsingEncoding:
NSMacOSRomanStringEncoding allowLossyConversion:YES];
[writeHandle writeData: myData];
}

should translate roughly as:

set userInput to (sender's stringValue()) as text & linefeed
set userInput to current application's class "NSSString"'s stringWithString_(userInput)
set myData to userInput's dataUsingEncoding_allowLossyConversion_(current application's NSMacOSRomanStringEncoding, true)
writeHandle's writeData_(myData)

Shane,

Greatly Appreciated! I’ll give that a shot tonight.

Update:

gave it a shot, got this to work:

-- test the input and output and get the audio bitrate
   on MyTest_(sender)
       set userinput to ("get_audio_bitrate") & linefeed
       set userinput to current application's class "NSString"'s stringWithString_(userinput)
       set myData to userinput's dataUsingEncoding_allowLossyConversion_(current application's NSMacOSRomanStringEncoding, true)
       writeHandle's writeData_(myData)
   end MyTest_

:slight_smile:

My entire working interactive NSTask script is in the next reply. It is based on the the NSTasks demonstrated in this thread.

Thanks to everyone who helped out in this NSTask thread.

I was able to get the IO working for Mplayer’s slave mode. Below is an example of an interactive NSTask using Mplayer (This can easily be adapted for other interactive NSTask’s like an ftp program. It demonstrates reading from StdOut and writing to StdIn from a CLI application.

In my first test, I got ‘pause’ working then adjusted it to read the mp3’s bitrate that’s playing (showing the interaction in the console log). This example requires a compiled Mac OS X version of mplayer and a path to an mp3 file.

For some reason after a notification is received, I have send a 2nd readInBackgroundAndNotify() within the read pipe subroutine. Otherwise it only gets the first message.

Overall this works much better than using FIFO with mplayer. It is interactive. If-then statements can be added when a nofication is received and how to handle it. (Get the bitrate and show the bitrate in the player’s UI, etc).

property parent : class "NSObject"
	property readHandle : missing value
	property writeHandle : missing value
	property outputpipe : missing value
	property inputpipe : missing value
	--property PauseCmd : "pause"
	property inputText : missing value
	property MyPlayer : missing value

-- update path to the open source Mplayer
	property PathToMplayer : "/path/to/bin/mplayer"
	
	property Nf : missing value
	
	on Playr_(sender)
		
		--stop previous task, close pipe and mplayer
		try
			tell MyPlayer
				terminate()
			end tell
		end try
		
-- update path to your MP3 file
		set arraylist to {"/path/to/audio.mp3", "-slave", "-quiet"}
		set outputpipe to current application's NSPipe's pipe()
		set inputpipe to current application's NSPipe's pipe()
		set readHandle to outputpipe's fileHandleForReading()
		set writeHandle to inputpipe's fileHandleForWriting()
		
		set MyPlayer to current application's NSTask's alloc's init()
		
		tell MyPlayer
			setStandardInput_(inputpipe)
			setStandardOutput_(outputpipe)
			setStandardError_(outputpipe)
			setLaunchPath_(PathToMplayer)
			setArguments_(arraylist)
			|launch|()
		end tell
		
		set Nofify to current application's NSNotificationCenter's defaultCenter()
		
		tell Nofify
			removeObserver_(me)
			addObserver_selector_name_object_(me, "readPipe:", "NSFileHandleReadCompletionNotification", readHandle)
			addObserver_selector_name_object_(me, "endPipe:", "NSTaskDidTerminateNotification", MyPlayer)
		end tell
		
		tell readHandle
			readInBackgroundAndNotify()
		end tell
	end Playr_
	
	on applicationWillFinishLaunching_(aNotification)
		
		-- stop mplayer incase it is running
		-- used this mainly when stopping the task from Xcode, can kill it back on launch
		-- if the app is shutdown properly, this hack is not needed
		try
			do shell script "killall mplayer"
		end try
	end applicationWillFinishLaunching_
	
	
	on readPipe_(aNotification)
		--log "Pipe In"
		set dataString to aNotification's userInfo's objectForKey_("NSFileHandleNotificationDataItem")
		set newstring to (((current application's class "NSString")'s alloc)'s initWithData_encoding_(dataString, current application's NSASCIIStringEncoding))
		set newstring to newstring as string
		log newstring
		
		--had to add another read-in-background, to get the next message, and so on...
		tell readHandle
			readInBackgroundAndNotify()
		end tell
	end readPipe_
	
	on endPipe_(aNotification)
		log "end pipe"
	end endPipe_
	
	-- test the input and output and get the audio bitrate (the get part is handled in the background, which is awesome!)
	on MyTest_(sender)
		set userinput to ("get_audio_bitrate") & linefeed
		set userinput to current application's class "NSString"'s stringWithString_(userinput)
		set myData to userinput's dataUsingEncoding_allowLossyConversion_(current application's NSMacOSRomanStringEncoding, true)
		writeHandle's writeData_(myData)
	end MyTest_
	
	on applicationShouldTerminate_(sender)
		try
			tell MyPlayer
				terminate()
			end tell
		end try
		-- Insert code here to do any housekeeping before your application quits 
		return current application's NSTerminateNow
	end applicationShouldTerminate_