//
//  SUPipedUnarchiver.m
//  Sparkle
//
//  Created by Andy Matuschak on 6/16/08.
//  Copyright 2008 Andy Matuschak. All rights reserved.
//

#import "SUPipedUnarchiver.h"
#import "SUUnarchiver_Private.h"
#import "SULog.h"


@implementation SUPipedUnarchiver

+ (SEL)selectorConformingToTypeOfPath:(NSString *)path
{
	static NSDictionary *typeSelectorDictionary;
	if (!typeSelectorDictionary)
		typeSelectorDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:@"extractZIP", @".zip", @"extractTAR", @".tar",
								   @"extractTGZ", @".tar.gz", @"extractTGZ", @".tgz",
								   @"extractTBZ", @".tar.bz2", @"extractTBZ", @".tbz", nil] retain];

	NSString *lastPathComponent = [path lastPathComponent];
	NSEnumerator *typeEnumerator = [typeSelectorDictionary keyEnumerator];
	id currentType;
	while ((currentType = [typeEnumerator nextObject]))
	{
		if ([currentType length] > [lastPathComponent length]) continue;
		if ([[lastPathComponent substringFromIndex:[lastPathComponent length] - [currentType length]] isEqualToString:currentType])
			return NSSelectorFromString([typeSelectorDictionary objectForKey:currentType]);
	}
	return NULL;
}

- (void)start
{
	[NSThread detachNewThreadSelector:[[self class] selectorConformingToTypeOfPath:archivePath] toTarget:self withObject:nil];
}

+ (BOOL)canUnarchivePath:(NSString *)path
{
	return ([self selectorConformingToTypeOfPath:path] != nil);
}

// This method abstracts the types that use a command line tool piping data from stdin.
- (void)extractArchivePipingDataToCommand:(NSString *)command
{
	// *** GETS CALLED ON NON-MAIN THREAD!!!
	

	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	FILE *fp = NULL, *cmdFP = NULL;
    char *oldDestinationString = NULL;
	
	SULog(@"Extracting %@ using '%@'",archivePath,command);
    
	// Get the file size.
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
	NSNumber *fs = [[[NSFileManager defaultManager] fileAttributesAtPath:archivePath traverseLink:NO] objectForKey:NSFileSize];
#else
	NSNumber *fs = [[[NSFileManager defaultManager] attributesOfItemAtPath:archivePath error:nil] objectForKey:NSFileSize];
#endif
	if (fs == nil) goto reportError;
	
	// Thank you, Allan Odgaard!
	// (who wrote the following extraction alg.)
	fp = fopen([archivePath fileSystemRepresentation], "r");
	if (!fp) goto reportError;
	
    oldDestinationString = getenv("DESTINATION");
	setenv("DESTINATION", [[archivePath stringByDeletingLastPathComponent] fileSystemRepresentation], 1);
	cmdFP = popen([command fileSystemRepresentation], "w");
	size_t written;
	if (!cmdFP) goto reportError;
	
	char buf[32*1024];
	size_t len;
	while((len = fread(buf, 1, 32*1024, fp)))
	{				
		written = fwrite(buf, 1, len, cmdFP);
		if( written < len )
		{
			pclose(cmdFP);
			goto reportError;
		}
			
		[self performSelectorOnMainThread:@selector(notifyDelegateOfExtractedLength:) withObject:[NSNumber numberWithUnsignedLong:len] waitUntilDone:NO];
	}
	pclose(cmdFP);
	
	if( ferror( fp ) )
		goto reportError;
	
	[self performSelectorOnMainThread:@selector(notifyDelegateOfSuccess) withObject:nil waitUntilDone:NO];
	goto finally;
	
reportError:
	[self performSelectorOnMainThread:@selector(notifyDelegateOfFailure) withObject:nil waitUntilDone:NO];
	
finally:
	if (fp)
		fclose(fp);
    if (oldDestinationString)
        setenv("DESTINATION", oldDestinationString, 1);
    else
        unsetenv("DESTINATION");
	[pool release];
}

- (void)extractTAR
{
	// *** GETS CALLED ON NON-MAIN THREAD!!!
	
	return [self extractArchivePipingDataToCommand:@"tar -xC \"$DESTINATION\""];
}

- (void)extractTGZ
{
	// *** GETS CALLED ON NON-MAIN THREAD!!!
	
	return [self extractArchivePipingDataToCommand:@"tar -zxC \"$DESTINATION\""];
}

- (void)extractTBZ
{
	// *** GETS CALLED ON NON-MAIN THREAD!!!
	
	return [self extractArchivePipingDataToCommand:@"tar -jxC \"$DESTINATION\""];
}

- (void)extractZIP
{
	// *** GETS CALLED ON NON-MAIN THREAD!!!
	
	return [self extractArchivePipingDataToCommand:@"ditto -x -k - \"$DESTINATION\""];
}

+ (void)load
{
	[self registerImplementation:self];
}

@end
