Cog / Frameworks / Sparkle / SUUnarchiver.m

//
//  SUUnarchiver.m
//  Sparkle
//
//  Created by Andy Matuschak on 3/16/06.
//  Copyright 2006 Andy Matuschak. All rights reserved.
//

#import "SUUnarchiver.h"


@implementation SUUnarchiver

// This method abstracts the types that use a command line tool piping data from stdin.
- (BOOL)_extractArchivePath:archivePath pipingDataToCommand:(NSString *)command
{
	// Get the file size.
	NSNumber *fs = [[[NSFileManager defaultManager] fileAttributesAtPath:archivePath traverseLink:NO] objectForKey:NSFileSize];
	if (fs == nil) { return NO; }
		
	// Thank you, Allan Odgaard!
	// (who wrote the following extraction alg.)
	
	long current = 0;
	FILE *fp, *cmdFP;
	sig_t oldSigPipeHandler = signal(SIGPIPE, SIG_IGN);
	if ((fp = fopen([archivePath UTF8String], "r")))
	{
		setenv("DESTINATION", [[archivePath stringByDeletingLastPathComponent] UTF8String], 1);
		if ((cmdFP = popen([command UTF8String], "w")))
		{
			char buf[32*1024];
			long len;
			while((len = fread(buf, 1, 32 * 1024, fp)))
			{				
				current += len;
				
				NSEvent *event;
				while((event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES]))
					[NSApp sendEvent:event];
				
				fwrite(buf, 1, len, cmdFP);
				
				if ([delegate respondsToSelector:@selector(unarchiver:extractedLength:)])
					[delegate unarchiver:self extractedLength:len];
			}
			pclose(cmdFP);
		}
		fclose(fp);
	}	
	signal(SIGPIPE, oldSigPipeHandler);
	return YES;
}

- (BOOL)_extractTAR:(NSString *)archivePath
{
	return [self _extractArchivePath:archivePath pipingDataToCommand:@"tar -xC \"$DESTINATION\""];
}

- (BOOL)_extractTGZ:(NSString *)archivePath
{
	return [self _extractArchivePath:archivePath pipingDataToCommand:@"tar -zxC \"$DESTINATION\""];
}

- (BOOL)_extractTBZ:(NSString *)archivePath
{
	return [self _extractArchivePath:archivePath pipingDataToCommand:@"tar -jxC \"$DESTINATION\""];
}

- (BOOL)_extractZIP:(NSString *)archivePath
{
	return [self _extractArchivePath:archivePath pipingDataToCommand:@"ditto -x -k - \"$DESTINATION\""];
}

- (BOOL)_extractDMG:(NSString *)archivePath
{
	sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL);
	// First, we internet-enable the volume.
	NSTask *hdiTask = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/env" arguments:[NSArray arrayWithObjects:@"hdiutil", @"internet-enable", @"-quiet", archivePath, nil]];
	[hdiTask waitUntilExit];
	if ([hdiTask terminationStatus] != 0) { return NO; }
	
	// Now, open the volume; it'll extract into its own directory.
	hdiTask = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/env" arguments:[NSArray arrayWithObjects:@"hdiutil", @"attach", @"-idme", @"-noidmereveal", @"-noidmetrash", @"-noverify", @"-nobrowse", @"-noautoopen", @"-quiet", archivePath, nil]];
	[hdiTask waitUntilExit];
	if ([hdiTask terminationStatus] != 0) { return NO; }
	
	signal(SIGCHLD, oldSigChildHandler);
	
	return YES;
}

- (void)_unarchivePath:(NSString *)path
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

	// This dictionary associates names of methods responsible for extraction with file extensions.
	// The methods take the path of the archive to extract. They return a BOOL indicating whether
	// we should continue with the update; returns NO if an error occurred.
	NSDictionary *commandDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
																   @"_extractTBZ:", @"tbz",
																   @"_extractTGZ:", @"tgz",
																   @"_extractTAR:", @"tar", 
																   @"_extractZIP:", @"zip", 
																   @"_extractDMG:", @"dmg", nil];
	SEL command = NSSelectorFromString([commandDictionary objectForKey:[path pathExtension]]);
	
	BOOL result;
	if (command)
	{
		NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:command]];
		[invocation setSelector:command];
		[invocation setArgument:&path atIndex:2]; // 0 and 1 are private!
		[invocation invokeWithTarget:self];
		[invocation getReturnValue:&result];
	}
	else
		result = NO;
	
	if (result)
	{
		if ([delegate respondsToSelector:@selector(unarchiverDidFinish:)])
			[delegate performSelector:@selector(unarchiverDidFinish:) withObject:self];
	}
	else
	{
		if ([delegate respondsToSelector:@selector(unarchiverDidFail:)])
			[delegate performSelector:@selector(unarchiverDidFail:) withObject:self];
	}

	[pool release];
}

- (void)unarchivePath:(NSString *)path
{
	[NSThread detachNewThreadSelector:@selector(_unarchivePath:) toTarget:self withObject:path];
}

- (void)setDelegate:del
{
	delegate = del;
}

@end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.