File: programming/cocoa/Verpack.zip/Verpack/Verpack/UKVerpackAppDelegate.m


//
//  UKVerpackAppDelegate.m
//  VerpackIt
//
//  Created by Uli Kusterer on 15.09.04.
//  Copyright 2004 M. Uli Kusterer. All rights reserved.
//
 
#import "UKVerpackAppDelegate.h"
#import "PBXArchive.h"
#import "PBXProject.h"
#import "PBXGroup.h"
#import "PBXFileReference.h"
#import "NSString+PartialPaths.h"
 
 
@implementation UKVerpackAppDelegate
 
-(BOOL)	application:(NSApplication *)sender openFile:(NSString *)filename
{
	[projectPackage autorelease];
	projectPackage = filename;
	
	if( [[filename pathExtension] isEqualToString: @"xcode"]
		|| [[filename pathExtension] isEqualToString: @"pbproj"]
		|| [[filename pathExtension] isEqualToString: @"xcodeproj"] )
		filename = [filename stringByAppendingPathComponent: @"project.pbxproj"];
	else
		projectPackage = [filename stringByDeletingLastPathComponent];
	[dict autorelease];
	dict = [[NSMutableDictionary dictionaryWithContentsOfFile: filename] retain];
		
	[pbxArchive autorelease];
	pbxArchive = nil;
	pbxArchive = [[PBXArchive alloc] initWithDictionary: dict projectPath: projectPackage];
	
	[listView reloadData];
	
	[self printFilePathsInProject: [pbxArchive projectFolderPath]];	// Fills neededFiles array.
	
	[self copyFilesInProject: self];
	
	return YES;
}
 
-(void)	dealloc
{
	[neededFiles release];
	[dict release];
	[pbxArchive release];
	[projectPackage release];
	
	[super dealloc];
}
 
 
// Before using this, you must have called [self printFilePathsInProject: ...]; or it won't know what to copy.
-(void)	copyFilesInProject: (id)sender
{
	[[[textView textStorage] mutableString] appendString: @"\n\n--------------------\n"];
	
	// Now create folder(s) to copy files to, and make sure we're enough subfolders deep so relative paths can go back up:
	NSMutableString*	destFolder = [[[[pbxArchive projectFolderPath] stringByAppendingPathComponent: [[pbxArchive projectFolderPath] lastPathComponent]] mutableCopy] autorelease];
	int					maxPathDepth = [neededFiles maxUpwardsDepth],
						x;
	NSArray*			components = [[pbxArchive projectFolderPath] pathComponents];
	
	[[NSFileManager defaultManager] createDirectoryAtPath: destFolder attributes: nil];
 
	for( x = ([components count] -maxPathDepth); x < [components count]; x++ )
	{
		if( [destFolder characterAtIndex: [destFolder length] -1] != '/' )
			[destFolder appendString: @"/"];
		[destFolder appendString: [components objectAtIndex: x]];
		[[NSFileManager defaultManager] createDirectoryAtPath: destFolder attributes: nil];
	}
	
	[[[textView textStorage] mutableString] appendString: @"\nDest Folder: "];
	[[[textView textStorage] mutableString] appendString: destFolder];
	
	// Now copy files:
	NSEnumerator	*fenny = [neededFiles objectEnumerator];
	NSString		*currPath;
	
	while( (currPath = [fenny nextObject]) )
	{
		NSString*	currDest = [destFolder stringByCombiningWithPartialPath: currPath];
		NSString*	currSource = [[pbxArchive projectFolderPath] stringByCombiningWithPartialPath: currPath];
		
		[self ensureFolderExists: [currDest stringByDeletingLastPathComponent]];
		NSString*	errPath = [self copyPath: currSource toPath: currDest];
		if( errPath == nil )
		{
			[[[textView textStorage] mutableString] appendString: @"\nCopied File: "];
			[[[textView textStorage] mutableString] appendString: currDest];
		}
		else
		{
			[[[textView textStorage] mutableString] appendString: @"\nCouldn't Copy: "];
			[[[textView textStorage] mutableString] appendString: errPath];
		}
	}
	
	// Now copy project package:
	NSString*	newPackage = [destFolder stringByAppendingPathComponent: [projectPackage lastPathComponent]];
	NSString*	errPath = [self copyPath: projectPackage toPath: newPackage];
	if( errPath == nil )
	{
		[[[textView textStorage] mutableString] appendString: @"\nCopied File: "];
		[[[textView textStorage] mutableString] appendString: newPackage];
	}
	else
	{
		[[[textView textStorage] mutableString] appendString: @"\nCouldn't Copy: "];
		[[[textView textStorage] mutableString] appendString: errPath];
	}
}
 
 
NSString*	UKNoNilString( NSString* s )
{
	if( !s )
		return @"";
	else
		return s;
}
 
 
-(NSString*)	copyPath: (NSString*)sourcePath toPath: (NSString*)destPath
{
	BOOL		isDir = NO;
	
	if( ![[NSFileManager defaultManager] fileExistsAtPath: sourcePath isDirectory: &isDir] )
		return sourcePath;
	
	NSString*		currName = [sourcePath lastPathComponent];
	NSString*		currSuffix = [currName pathExtension];
	if( isDir && [currName isEqualToString: @".svn"] )			// Skip subversion meta-info directory.
		return nil;
	if( !isDir && [currName isEqualToString: @".DS_Store"] )	// Skip Finder's info store.
		return nil;
	if( !isDir && [currSuffix isEqualToString: @"mode2"] )		// Skip user-specific info in project files.
		return nil;
	if( !isDir && [currSuffix isEqualToString: @"pbxuser"] )	// Skip user-specific info in project files.
		return nil;
	
	if( isDir )
	{
		if( ![[NSFileManager defaultManager] createDirectoryAtPath: destPath attributes: nil] )
			return destPath;
		NSDirectoryEnumerator*	enny = [[NSFileManager defaultManager] enumeratorAtPath: sourcePath];
		NSString*				currSubPath = nil;
		
		while(( currSubPath = [enny nextObject] ))
		{
			NSString*	currSourcePath = [sourcePath stringByAppendingPathComponent: currSubPath];
			NSString*	currDestPath = [destPath stringByAppendingPathComponent: currSubPath];
			
			NSString*	errorPath = [self copyPath: currSourcePath toPath: currDestPath];
			if( errorPath )
				return errorPath;
			[enny skipDescendents];
		}
	}
	else
		[[NSFileManager defaultManager] copyPath: sourcePath toPath: destPath handler: nil];
	
	return nil;
}
 
 
-(void)	ensureFolderExists: (NSString*)path
{
	if( [[NSFileManager defaultManager] fileExistsAtPath: path] )
		return;	// All hunky-dory.
	
	// Otherwise, work our way up the path and ensure everything exists:
	NSMutableString*	workpath = [NSMutableString string];
	NSEnumerator*		enny = [[path pathComponents] objectEnumerator];
	NSString*			currComponent;
	
	while( (currComponent = [enny nextObject]) )
	{
		[workpath appendString: @"/"];
		[workpath appendString: currComponent];
		if( ![[NSFileManager defaultManager] fileExistsAtPath: workpath] )
			[[NSFileManager defaultManager] createDirectoryAtPath: workpath attributes: nil];
	}
}
 
 
-(void)	recursivelyPrintFilesInGroup: (PBXGroup*)grp atPath:(NSString*)path
{
	static NSMutableString*	str = nil;	// Not thread-safe!
	static int				ignoreCurrentGroup = 0;	// if 0, don't add to neededFiles, >0 do.
	int						x = 0;
	
	if( !str )
		str = [@"\n" mutableCopy];
	
	for( x = 0; x < [grp count]; x++ )
	{
		id			obj = [grp objectAtIndex: x];
		NSString*	subPath = UKNoNilString([obj path]);
		
		if( [[obj refType] isEqualToString: @"Relative to Enclosing Group"] )
			subPath = [path stringByCombiningWithPartialPath: subPath];
		else if( [[obj refType] isEqualToString: @"Relative to Project"] )
			subPath = [[pbxArchive projectFolderPath] stringByCombiningWithPartialPath: subPath];
		else if( [[obj refType] isEqualToString: @"Relative to Build Product"] )
			subPath = [[pbxArchive buildProductPath] stringByCombiningWithPartialPath: subPath];
 
		if( [obj isKindOfClass: [PBXGroup class]] )	// PBXGroup or PBXAlternateGroup
		{
			[[[textView textStorage] mutableString] appendString: str];
			[[[textView textStorage] mutableString] appendString: [obj name]];
			[str appendString: @"\t"];	// Indent one level.
			if( [[obj name] isEqualToString: @"Frameworks"]		// FIX ME! This and below shouldn't ignore *all* frameworks!
				|| [[obj name] isEqualToString: @"Products"] )
				ignoreCurrentGroup++;
			[self recursivelyPrintFilesInGroup: obj atPath: subPath];
			if( [[obj name] isEqualToString: @"Frameworks"]
				|| [[obj name] isEqualToString: @"Products"] )
				ignoreCurrentGroup--;
			[str deleteCharactersInRange: NSMakeRange([str length] -1,1)];	// Un-indent.
		}
		else if( [obj isKindOfClass: [PBXFileReference class]] )
		{
			[[[textView textStorage] mutableString] appendString: str];
			[[[textView textStorage] mutableString] appendString: subPath];
			if( ignoreCurrentGroup <= 0 )
				[neededFiles addObject: [subPath stringBySubtractingBasePath: [pbxArchive projectFolderPath]]];
		}
	}
}
 
 
// Just a little test to see whether we can extract the file list:
-(void)	printFilePathsInProject: (NSString*)path
{
	PBXProject*	project = [pbxArchive rootObject];
	PBXGroup*	currGroup = [project mainGroup];
	
	[neededFiles release];
	neededFiles = [[NSMutableArray alloc] init];
	
	[self recursivelyPrintFilesInGroup: currGroup atPath: path];
}
 
 
- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
	if( !item )
		return pbxArchive;
	
	return [((NSArray*)item) objectAtIndex: index];
}
 
 
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
	if( item == self || item == nil )
		return YES;
	return( [item respondsToSelector: @selector(count)] && [((NSArray*)item) count] > 0 );
}
 
 
- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
	if( item == self || item == nil )
		return 1;
	
	if( [item respondsToSelector: @selector(count)] )
		return( [((NSArray*)item) count] );
	else
		return 0;
}
 
 
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn
	byItem:(id)item
{
	NSString*	str = nil;
	
	if( [item respondsToSelector: @selector(description)] )
		str = [item description];
	
	if( !str )
		str = @"-";
	
	return str;
}
 
@end

This code uses the PclZip Zip File reading code, which is subject to the GNU LGPL. It also uses the GeSHi syntax highlighter, subject to the GPL. Ask if you want this for your own web site, it's free.