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
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.
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.
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).
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)
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_
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_