Re: NSTask with unzip
Re: NSTask with unzip
- Subject: Re: NSTask with unzip
- From: Ben Haller <email@hidden>
- Date: Sun, 28 Nov 2010 08:52:33 -0500
Yes, this looks good. I like your category on NSFileHandle (not a subclass!); it's cleaner than the code at the link I sent you, since it doesn't just eat the error, and it's better as a category.
Four things I would mention:
1) Checking that the pipe could be created and actually has a file handle for reading would be a good idea; [NSPipe pipe] is documented as being allowed to return nil
2) Checking -terminationStatus is a good idea once the task completes (after you're done pulling data out, you can then safely call -waitUntilExit on the task to be certain it has completed before calling -terminationStatus, AFAIK)
3) You only use a pipe for standard out, not for standard in, but it's worth noting that a pipe for standard in needs to receive a -closeFile call or the file descriptor for that pipe doesn't get deleted correctly. As a reminder to myself about this issue, I just send -closeFile to all of the pipes I'm using with a task, so that I don't forget to do it. But your code is correct; I mention this just in case someone reading the archives adapts this code to a task that requires a standard in pipe.
4) -launch can raise, so it is good to think about that; but as long as you're comfortable with your method raising, your code seems fine to me.
Good stuff! If anybody on the list knows whether the bug that the -availableDataOrError: hack circumvents has been fixed, and in what OS X release, I'd love to know that so I know whether it's safe to delete that rather unpleasant hack from my code.
Ben Haller
McGill University
On 2010-11-27, at 3:43 PM, Leonardo wrote:
> Ben, thank you so much! I have successfully done it.
> I post the code here for anyone to use it. I love this list.
>
> - (NSData*)UnzipFile:(NSString*)sourcePath
> extractFileName:(NSString*)extractFileName
> {
> NSTask *unzip = [[[NSTask alloc] init] autorelease];
> NSPipe *aPipe = [NSPipe pipe];
> [unzip setStandardOutput:aPipe];
> [unzip setLaunchPath:@"/usr/bin/unzip"];
> [unzip setArguments:[NSArray arrayWithObjects:@"-p", sourcePath,
> extractFileName, nil]];
> [unzip launch];
>
> NSMutableData *dataOut = [NSMutableData data];
> NSData *dataIn = nil;
> NSException *error = nil;
>
> while((dataIn = [[aPipe fileHandleForReading]
> availableDataOrError:&error]) && [dataIn length] && error == nil){
> [dataOut appendData:dataIn];
> }
>
> if([dataOut length] && error == nil){
> return dataOut;
> }
>
> return nil;
> }
>
>
> // Then I subclassed NSFileHandler this way
>
> @implementation NSFileHandle (MyOwnAdditions)
> - (NSData*)availableDataOrError:(NSException**)returnError
> {
> for(;;){
> @try{
> return [self availableData];
> }@catch (NSException *e) {
> if ([[e name] isEqualToString:NSFileHandleOperationException]) {
> if ([[e reason] isEqualToString:@"*** -[NSConcreteFileHandle
> availableData]: Interrupted system call"]) {
> continue;
> }
> if (returnError)
> *returnError = e;
> return nil;
> }
> @throw;
> }
> }
> }
> @end
>
>
>> Da: Ben Haller <email@hidden>
>> Data: Sat, 27 Nov 2010 12:12:39 -0500
>> A: Dave DeLong <email@hidden>
>> Cc: "gMail.com" <email@hidden>, Cocoa List
>> <email@hidden>
>> Oggetto: Re: NSTask with unzip
>>
>> Here's a post that I found useful:
>>
>> http://dev.notoptimal.net/2007/04/nstasks-nspipes-and-deadlocks-when.html
>>
>> Dave, not sure what you mean here. NSPipe uses NSFileHandle. Does using an
>> NSFileHandle directly change things somehow? If so, why? I think this is an
>> avenue I haven't explored; once I (finally) figured out the right magic
>> incantations to get things to work reliably with NSPipe, I now recycle that
>> code everywhere I need an NSTask :->.
>>
>> Ben Haller
>> McGill University
>>
>>
>> On 2010-11-27, at 11:48 AM, Dave DeLong wrote:
>>
>>> The way I get around this is to use an NSFileHandle for standard out instead
>>> of an NSPipe. It's a bit less efficient, but slightly more convenient.
>>>
>>> Dave
>>>
>>> Sent from my iPhone
>>>
>>> On Nov 27, 2010, at 7:59 AM, Ben Haller <email@hidden> wrote:
>>>
>>>> On 2010-11-26, at 7:33 AM, gMail.com wrote:
>>>>
>>>>> Hi, I can properly unzip a zip file launching a NSTask with /usr/bin/unzip
>>>>> The task saves the unzipped file to the disk, then a I read the unzipped
>>>>> file in a NSData. Well. My question is:
>>>>> Can I do the same job without saving the unzipped file to the disk?
>>>>>
>>>>> I have tried to set the standard output to a pipe - which works well with
>>>>> other tasks - but here it doesn't work. The task never exits. Here's the
>>>>> wrong code:
>>>>>
>>>>> NSTask *unzip = [[[NSTask alloc] init] autorelease];
>>>>> [unzip setLaunchPath:@"/usr/bin/unzip"];
>>>>> [unzip setArguments:[NSArray arrayWithObjects:@"-p", zipfile,
>>>>> @"filetounzip", nil]];
>>>>>
>>>>> NSPipe *aPipe = [NSPipe pipe];
>>>>> [unzip setStandardOutput:aPipe];
>>>>> [unzip launch];
>>>>> [unzip waitUntilExit];
>>>>>
>>>>> if([unzip terminationStatus] == noErr){
>>>>> dictData = [NSMutableData data];
>>>>> while((dataOut = [aPipe availableData]) && [dataOut length]){
>>>>> [dictData appendData:dataOut];
>>>>> }
>>>>> }
>>>>
>>>> If I recall correctly, the problem is likely to be your use of
>>>> -waitUntilExit. That API should apparently have a large red label on it
>>>> ("Warnin', lark's vomit!") since everybody wants to use it this way. The
>>>> problem is that the task's output pipe fills up because it isn't being
>>>> serviced, and then things get locked up. You need to go with asynchronous
>>>> reads to service the pipe as output gets stuffed into it. There should be
>>>> lots of examples of this on this list, now that you know what to look for.
>>>>
>>>> What would be great would be a new call, along the lines of
>>>> -dataFromWaitingUntilExit or some such, that does all this for you, since
>>>> this is so commonly what people want to do.
>>>>
>>>> Ben Haller
>>>> McGill University
>>>>
>>>> _______________________________________________
>>>>
>>>> Cocoa-dev mailing list (email@hidden)
>>>>
>>>> Please do not post admin requests or moderator comments to the list.
>>>> Contact the moderators at cocoa-dev-admins(at)lists.apple.com
>>>>
>>>> Help/Unsubscribe/Update your Subscription:
>>>>
>>>> This email sent to email@hidden
>>
>
>
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden