Files

General Notes

Everything centers around URLs; even files on the hard drive has a URL (NSURL *xmlURL = [NSURL fileURLWithPath:pathToFile];).

Many of the snippets below have been cleaned up and posted to GitHub as OCFileController. The primary goal of the class is to reduce file operations to one line code.

Selecting a folder

Just a basic folder selection that is the starting point for a lot of my automation projects.

//NSArray *fileTypes = [NSArray arrayWithObject:nil];
NSOpenPanel *sourceFolderPanel = [NSOpenPanel openPanel];

[sourceFolderPanel setTitle:@"Choose Folder"];
[sourceFolderPanel setMessage:@"Choose the source folder."];
[sourceFolderPanel setDelegate:self];

[sourceFolderPanel setAllowsMultipleSelection:NO];
[sourceFolderPanel setCanChooseDirectories:YES];
[sourceFolderPanel setCanChooseFiles:NO];

int result = [sourceFolderPanel runModalForDirectory:NSHomeDirectory() file:nil];

if (result == NSOKButton) {
	NSString *selectedSourceFolder = [sourceFolderPanel filename];
	NSLog(@"%@", selectedSourceFolder);
}

Opening an XML file and start parsing it

There are a few things going on here. This selects a file (as opposed to a folder as above), sets the file path to the new starting directory for the application to save for next time, and finally kicks off the parsing process.

- (void)openXMLFile {

    NSArray *fileTypes = [NSArray arrayWithObject:@"xml"];
    NSOpenPanel *oPanel = [NSOpenPanel openPanel];
    NSString *startingDir = [[NSUserDefaults standardUserDefaults] objectForKey:@"StartingDirectory"];
    if (!startingDir)
        startingDir = NSHomeDirectory();
    [oPanel setAllowsMultipleSelection:NO];
    [oPanel beginSheetForDirectory:startingDir file:nil types:fileTypes
      modalForWindow:[self window] modalDelegate:self
      didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
      contextInfo:nil];
}

- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
    NSString *pathToFile = nil;
    if (returnCode == NSOKButton) {
        pathToFile = [[[sheet filenames] objectAtIndex:0] copy];
    }
    if (pathToFile) {
        NSString *startingDir = [pathToFile stringByDeletingLastPathComponent];
        [[NSUserDefaults standardUserDefaults] setObject:startingDir forKey:@"StartingDirectory"];
        [self parseXMLFile:pathToFile];
    }
}

- (void)parseXMLFile:(NSString *)pathToFile {
    BOOL success;
    NSURL *xmlURL = [NSURL fileURLWithPath:pathToFile];
    if (addressParser) // addressParser is an NSXMLParser instance variable
        [addressParser release];
    addressParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
    [addressParser setDelegate:self];
    [addressParser setShouldResolveExternalEntities:YES];
    success = [addressParser parse]; // return value not used
                // if not successful, delegate is informed of error
}

Traverse Hierarchy of Files

I wrote this as a very, very beginning of porting my old Simple Cataloger application from REALbasic over to Cocoa. There is a way to point to a folder and get everything contained inside, but doing it this way would allow me to catalog items by preset parameters.

OCFileParser.h

//
//  OCFileParser.h
//  OCDefaultTemplate
//
//  Created by Philip Regan on 5/7/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import 

#import "Constants.h"

@class OCTextConverter;

@interface OCFileParser : NSObject {

	NSString *sourceFolder;
	NSString *destinationFolder;

}
- (void) parseFiles;
- (NSString *) chooseFolder:(NSString *)title WithMessage:(NSString *)message;
- (void) parseFolder:(NSString *)folder;
- (void) parseFile:(NSString *)file;

@end

OCFileParser.m

//
//  OCFileParser.m
//  OCDefaultTemplate
//
//  Created by Philip Regan on 5/7/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "OCFileParser.h"

#import "OCTextConverter.h"

@implementation OCFileParser

- (id) init
{
	self = [super init];
	if (self != nil) {
		sourceFolder = NSHomeDirectory();
		destinationFolder = NSHomeDirectory();
	}
	return self;
}

- (void) dealloc
{
	[sourceFolder release];
	[destinationFolder release];
	[super dealloc];
}

- (void) parseFiles {

	sourceFolder = [self chooseFolder:@"Choose Folder" WithMessage:@"Choose the source folder."];
	destinationFolder = [self chooseFolder:@"Choose Folder" WithMessage:@"Choose the destination folder."];

	[self parseFolder:sourceFolder];

}

- (NSString *) chooseFolder:(NSString *)title WithMessage:(NSString *)message {
	NSOpenPanel *mOpenPanel = [NSOpenPanel openPanel];

	if (title != nil) {
		[mOpenPanel setTitle:title];
	} else {
		[mOpenPanel setTitle:@"Choose Folder"];
	}

	if (message != nil) {
		[mOpenPanel setMessage:message];
	} else {
		[mOpenPanel setMessage:@"Please choose a folder."];
	}

	[mOpenPanel setDelegate:self];

	[mOpenPanel setAllowsMultipleSelection:NO];
	[mOpenPanel setCanChooseFiles:NO];
	[mOpenPanel setCanChooseDirectories:YES];

	int result = [mOpenPanel runModalForDirectory:NSHomeDirectory() file:nil];

	if (result == NSOKButton) {
		return [mOpenPanel filename];
	} else {
		return nil;
	}
}

- (void) parseFolder:(NSString *)folder {
	NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:folder];
	NSString *finderItem;

	while (finderItem = [directoryEnumerator nextObject]) {

		[directoryEnumerator skipDescendents];

		NSString *fullPath = [folder stringByAppendingPathComponent:finderItem];

		NSFileManager *fileManager = [NSFileManager defaultManager];
		NSError *error = nil;

		NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:fullPath error:&error];
		NSString *directoryFlag = [fileAttributes objectForKey:NSFileType];

		if ([directoryFlag isEqualToString:NSFileTypeDirectory]) {
			[self parseFolder:fullPath];
		} else {
			[self parseFile:fullPath];
		}
	}
}

- (void) parseFile:(NSString *)file {

	/*
	 open the file
	 run OCTextConverter
	 save the text to the destination folder
	 */
}

@end

Opening a text file

Another key starting point for my automation projects. Essentially, there are three ways:

  • NSString *fileContents = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:&error]; All NSString class or instance methods require an encoding as NSStringEncoding.

Opening a Text File contained in the project bundle

Basically, another variation of above, but this time with practical code

- (NSString *) stringFromTextFileInBundleWithName:(NSString *)fileName {

	NSBundle* myBundle = [NSBundle mainBundle];
	NSString *dataPath = [myBundle pathForResource:fileName ofType:@"txt"];
	NSURL *dataURL = [NSURL fileURLWithPath:dataPath];
	NSError *readError;
	NSString *dataString = [NSString stringWithContentsOfURL:dataURL encoding:NSUTF8StringEncoding error:&readError];
	if ( !dataString ) {
		NSLog(@"fileName:%@, targetReadError: %@", fileName, readError);
	}

	return dataString;
}

Load and run an Applescript from a Cocoa application

Be sure that the compiled script is added as a bundle resource in Targets.

- (IBAction) runScript:(id)sender {
	NSBundle *myBundle = [NSBundle mainBundle];
	NSString *scriptSourceFile = [myBundle pathForResource:@"TestScript" ofType:@"scpt"];
	NSDictionary *applescriptInitErrors;
	NSAppleScript *scriptToRun = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:scriptSourceFile] error:&applescriptInitErrors];
	NSDictionary *applescriptRunErrors;
	NSAppleEventDescriptor *appleEventDescriptor = [scriptToRun executeAndReturnError:&applescriptRunErrors];
}

Core code for drag and drop functionality in an NSView

Apple’s documentation is very robust, but sometimes too much so. This is taken pretty much verbatim from their docs but only after stripping out everything that wasn’t absolutely needed (and even then, I’m wondering if their isn’t more to be done). Drag and drop operations are easy to implement at their lowest level, and this code shows all that is needed for a simple NSView. This code does not take into consideration any modifier keys. To change the target area, just change the superclass to the class of choice.

.h

#import 

@interface OCReceiveFileMgr : NSView {
@private

}

@end

.m

#import "OCReceiveFileMgr.h"

@implementation OCReceiveFileMgr

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
    }

    return self;
}

- (void)dealloc
{
    [super dealloc];
}

- (NSDragOperation)draggingEntered:(id )sender {

    NSPasteboard *pboard;
    NSDragOperation sourceDragMask;

    sourceDragMask = [sender draggingSourceOperationMask];
	pboard = [sender draggingPasteboard];

    if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
			return NSDragOperationLink;
	}

    return NSDragOperationNone;

}

- (BOOL)performDragOperation:(id )sender {

    NSPasteboard *pboard;
    NSDragOperation sourceDragMask;

	sourceDragMask = [sender draggingSourceOperationMask];
    pboard = [sender draggingPasteboard];

   if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {

	NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];		

	   NSLog(@"files: %@", files); // Success!
    }

    return YES;
}

@end

Converting between HFS (colon-delimited) and POSIX (slash-delimited) paths

When working with Scripting Bridge, it is important to remember that Applescript prefers HFS paths over POSIX paths in almost all cases. These two methods should help make the flip easier.

- (NSString *) convertPosixPathtoHfsPath:(NSString *)posixPath isDirectory:(BOOL)isDirectory {

	NSString *firstChar = [posixPath substringToIndex:1];
	if ( ![firstChar isEqualToString:@"/"] ) {
		return posixPath;
	}

	CFURLRef myURL = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)posixPath, kCFURLPOSIXPathStyle, isDirectory);
	NSString *hfsPath = (NSString *)CFURLCopyFileSystemPath(myURL, kCFURLHFSPathStyle);
	CFRelease(myURL);
	return hfsPath;

}

- (NSString *) convertHfsPathtoPosixPath:(NSString *)hfsPath isDirectory:(BOOL)isDirectory {

	NSString *firstChar = [hfsPath substringToIndex:1];
	if ( [firstChar isEqualToString:@"/"] ) {
		return hfsPath;
	}

	CFURLRef myURL = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)hfsPath, kCFURLHFSPathStyle, isDirectory);
	NSString *posixPath = (NSString *)CFURLCopyFileSystemPath(myURL, kCFURLPOSIXPathStyle);
	CFRelease(myURL);
	return posixPath;

}