Search code examples
cocoanstasknsprogressindicator

How to use a determinate NSProgressIndicator to check on the progress of NSTask? - Cocoa


What I have is NSTask running a long premade shell script and I want the NSProgressIndicator to check on how much is done. I've tried many things but just can't seem to get it to work. I know how to use it if the progress bar is indeterminate but i want it to load as the task goes on.

Here is how I am running the script:

- (IBAction)pressButton:(id)sender {
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/bin/sh"];
    [task setArguments:[NSArray arrayWithObjects:[[NSBundle mainBundle] pathForResource:@"script" ofType:@"sh"], nil]];
    [task launch];
}

I need to put a progress bar in that checks the progress of that task while it happens and update accordingly.


Solution

  • Here is an example of an async NSTask running a unix script. Within the Unix script there are echo commands that send back the current status to standard error like this:

    echo "working" >&2

    This is processed by notification center and sent to the display.

    To update a determinate progress bar just send status updates like "25.0" "26.0" and convert to float and send to the progress bar.

    note: I got this working after alot of experimenting and by using many tips from this site and other references. so I hope it is helpful to you.

    Here are the declarations:

    NSTask *unixTask;
    NSPipe *unixStandardOutputPipe;
    NSPipe *unixStandardErrorPipe;
    NSPipe *unixStandardInputPipe;
    NSFileHandle *fhOutput;
    NSFileHandle *fhError;
    NSData *standardOutputData;
    NSData *standardErrorData;
    

    Here are the main program modules:

        - (IBAction)buttonLaunchProgram:(id)sender {
    
        [_unixTaskStdOutput setString:@"" ];
        [_unixProgressUpdate setStringValue:@""];
        [_unixProgressBar startAnimation:sender];
    
        [self runCommand];
    }
    - (void)runCommand {
    
        //setup system pipes and filehandles to process output data
        unixStandardOutputPipe = [[NSPipe alloc] init];
        unixStandardErrorPipe =  [[NSPipe alloc] init];
    
        fhOutput = [unixStandardOutputPipe fileHandleForReading];
        fhError =  [unixStandardErrorPipe fileHandleForReading];
    
        //setup notification alerts
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    
        [nc addObserver:self selector:@selector(notifiedForStdOutput:) name:NSFileHandleReadCompletionNotification object:fhOutput];
        [nc addObserver:self selector:@selector(notifiedForStdError:)  name:NSFileHandleReadCompletionNotification object:fhError];
        [nc addObserver:self selector:@selector(notifiedForComplete:)  name:NSTaskDidTerminateNotification object:unixTask];
    
        NSMutableArray *commandLine = [NSMutableArray new];
        [commandLine addObject:@"-c"];
        [commandLine addObject:@"/usr/bin/kpu -ca"]; //put your script here
    
        unixTask = [[NSTask alloc] init];
        [unixTask setLaunchPath:@"/bin/bash"];
        [unixTask setArguments:commandLine];
        [unixTask setStandardOutput:unixStandardOutputPipe];
        [unixTask setStandardError:unixStandardErrorPipe];
        [unixTask setStandardInput:[NSPipe pipe]];
        [unixTask launch];
    
        //note we are calling the file handle not the pipe
        [fhOutput readInBackgroundAndNotify];
        [fhError readInBackgroundAndNotify];
    }
    -(void) notifiedForStdOutput: (NSNotification *)notified
    {
    
        NSData * data = [[notified userInfo] valueForKey:NSFileHandleNotificationDataItem];
        NSLog(@"standard data ready %ld bytes",data.length);
    
        if ([data length]){
    
            NSString * outputString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];  
            NSTextStorage *ts = [_unixTaskStdOutput textStorage];
            [ts replaceCharactersInRange:NSMakeRange([ts length], 0)
                              withString:outputString];
        }
    
        if (unixTask != nil) {
    
            [fhOutput readInBackgroundAndNotify];
        }
    
    }
    -(void) notifiedForStdError: (NSNotification *)notified
    {
    
        NSData * data = [[notified userInfo] valueForKey:NSFileHandleNotificationDataItem];
        NSLog(@"standard error ready %ld bytes",data.length);
    
        if ([data length]) {
    
            NSString * outputString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];  
            [_unixProgressUpdate setStringValue:outputString];
        }
    
        if (unixTask != nil) {
    
            [fhError readInBackgroundAndNotify];
        }
    
    }
    -(void) notifiedForComplete:(NSNotification *)anotification {
    
        NSLog(@"task completed or was stopped with exit code %d",[unixTask terminationStatus]);
        unixTask = nil;
    
        [_unixProgressBar stopAnimation:self];
        [_unixProgressBar viewDidHide];
    
        if ([unixTask terminationStatus] == 0) {
            [_unixProgressUpdate setStringValue:@"Success"]; 
        }
        else {
            [_unixProgressUpdate setStringValue:@"Terminated with non-zero exit code"];
        }
    }
    @end