¿Cómo utilizar un determinado NSProgressIndicator para comprobar el progreso de NSTask?- Cacao

StackOverflow https://stackoverflow.com/questions/8889006

Pregunta

Lo que tengo es NSTask ejecutando un largo script de shell prefabricado y quiero que NSProgressIndicator verifique cuánto se ha hecho.He intentado muchas cosas, pero parece que no puedo hacer que funcione.Sé cómo usarlo si la barra de progreso es indeterminada, pero quiero que se cargue a medida que avanza la tarea.

Así es como estoy ejecutando el 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];
}

Necesito poner una barra de progreso que verifique el progreso de esa tarea mientras ocurre y actualizar en consecuencia.

¿Fue útil?

Solución

A continuación, se muestra un ejemplo de una NSTask asíncrona que ejecuta un script Unix.Dentro del script Unix hay comandos echo que devuelven el estado actual a un error estándar como este:

echo "working" >&2

Esto es procesado por el centro de notificaciones y enviado a la pantalla.

Para actualizar una determinada barra de progreso, simplemente envíe actualizaciones de estado como "25.0" "26.0" y conviértalas en flotante y envíelas a la barra de progreso.

nota: Conseguí que esto funcionara después de mucho experimentar y usando muchos consejos de este sitio y otras referencias.así que espero que te sea de ayuda.

Aquí están las declaraciones:

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

Estos son los módulos principales del programa:

    - (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

Otros consejos

Debe tener alguna forma de devolver la llamada o interrumpir el progreso de una tarea para saber cuánto ha avanzado.Si está hablando de un script de shell, puede dividir un script en varios scripts y, al completar una sección del script, actualice el indicador de progreso.Otras aplicaciones han hecho cosas como esta, iirc Sparkle hizo una lógica personalizada en su código de descompresión para descomprimir en trozos para poder actualizar un indicador de progreso.Si desea lograr el mismo efecto, tendrá que hacer algo similar.

Get the PID(process ID) for the command which u ran,using it in conjunction with PS(Process state) command you can get the state of your process use that value in your code to show it in the progress bar

a simple way to have your script (nstask) communicate with your obj c controller is by using standard error as a communication channel. Not saying this is perfect but works quite well. You have to setup your task as an asynchronous nstask and use notifications to read from standard input and standard error separately. I can post it later if you need it.

Wrap your script like this:

echo "now starting" >&2
for i in $(ls /tmp)
do 
echo $i 2>&1
done
echo "now ending" >&2

Process Your standard error through a pipe and filehandle and wire it to an outlet for textual status updates or convert it to a float for progress displays. I.e

echo "25.0" >&2. Can be captured and converted.

If you need to capture real std error then trap it and merge it to std out like my example. This is not a perfect approach but I use it frequently and it works well.

On my iPad and don't have code. Let me know if you need a better example.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top