SIGPIPE 13

Programming, automation, algorithms, macOS, and more.

Progress Indicator for Unarchiving

I added a software updater to my application, and one of the steps was uncompressing the archive (after downloading it). Since the archive size is a few megabytes, and I use bzip2 as compression, this step takes a few seconds, and thus I want to show a determinate progress indicator while it is working on this.

As of such, I should really uncompress while downloading (since the CPU is idle most of the time waiting for network bytes), but I’m using NSURLDownload, and didn’t see any obvious way to do that (maybe give it a named pipe as the file to download into?).

Anyway, the problem is that extraction is done by running:

tar -jxf «archive» -C «destination»

Which gives us no output to parse for progress. So my solution is that instead of giving tar the archive as a filename, let it read it from stdin. Then I can feed it the archive 32 KB at a time, and thereby track the progress.

We can use popen to run a shell command with either input or output set to a normal C file pointer. So this is one way to do it:

// archivePath is pointing to our tbz archive
// progressBar is an NSProgressIndicator instance

[progressBar setIndeterminate:NO];
[progressBar setDoubleValue:0.0];
[progressBar setMaxValue:(double)«size of archive»];
[progressBar startAnimation:self];

current = 0;
if(FILE* fp = fopen([archivePath UTF8String], "r"))
{
    setenv("DESTINATION", [[archivePath stringByDeletingLastPathComponent] UTF8String], 1);
    if(FILE* cmdFP = popen("tar -jxC \"$DESTINATION\"", "w"))
    {
        std::vector<char> buf(32*1024);
        while(size_t len = fread(&buf[0], 1, buf.size(), fp))
        {
            current += len;
            [progressBar setDoubleValue:(double)current];

            while(NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
                [NSApp sendEvent:event];

            fwrite(&buf[0], 1, len, cmdFP);
        }
        pclose(cmdFP);
    }
    fclose(fp);
}

And that shows a very nice and steady progress indicator for uncompressing the tbz archive.

Notice that I let the command string use a shell variable as the destination directory, instead of e.g. sprintf‘ing the command. The command is handed to the shell interpreter (with option -c) so the variable will be expanded, and by doing it this way, I avoid the hassle of escaping special characters or spaces in the path (should there be any).

{{ numberOfCommentsTitle }}

{{ submitComment.success }}

Error Posting Comment

{{ submitComment.error }}