Skip to content

Instantly share code, notes, and snippets.

@ifournight
Last active December 16, 2015 16:49
Show Gist options
  • Save ifournight/5465792 to your computer and use it in GitHub Desktop.
Save ifournight/5465792 to your computer and use it in GitHub Desktop.
#import <Cocoa/Cocoa.h>
@interface GetPathsOperation : NSOperation
- (id)initWithRootURL:(NSURL *)url queue:(NSOperationQueue *)qq scanCount:(NSInteger)scanCount;
@end
#import "GetPathsOperation.h"
#import "LoadOperation.h"
@interface GetPathsOperation ()
{
NSURL *rootURL;
NSOperationQueue *queue;
NSInteger ourScanCount;
}
@property (retain) NSURL *rootURL;
@property (retain) NSOperationQueue *queue;
@end
@implementation GetPathsOperation
@synthesize rootURL, queue;
// -------------------------------------------------------------------------------
// initWithRootPath:
// -------------------------------------------------------------------------------
- (id)initWithRootURL:(NSURL *)url queue:(NSOperationQueue *)qq scanCount:(NSInteger)scanCount
{
self = [super init];
if (self)
{
self.rootURL = url;
self.queue = qq;
ourScanCount = scanCount;
}
return self;
}
// -------------------------------------------------------------------------------
// main:
// -------------------------------------------------------------------------------
- (void)main
{
NSDirectoryEnumerator *itr =
[[NSFileManager defaultManager] enumeratorAtURL:self.rootURL
includingPropertiesForKeys:nil
options:(NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsPackageDescendants)
errorHandler:nil];
for (NSURL *url in itr)
{
if ([self isCancelled])
{
break; // user cancelled this operation
}
// use NSOperation subclass "LoadOperation"
LoadOperation *op = [[LoadOperation alloc] initWithURL:url scanCount:ourScanCount];
[op setQueuePriority:2.0]; // second priority
[self.queue addOperation:op]; // this will start the load operation
}
}
@end
#import <Cocoa/Cocoa.h>
// key for obtaining the current scan count
extern NSString *kScanCountKey;
// key for obtaining the path of an image
extern NSString *kPathKey;
// NSNotification name to tell the Window controller an image file as found
extern NSString *kLoadImageDidFinish;
@interface LoadOperation : NSOperation
- (id)initWithURL:(NSURL *)url scanCount:(NSInteger)scanCount;
@end
#import "LoadOperation.h"
// key for obtaining the current scan count
NSString *kScanCountKey = @"scanCount";
// key for obtaining the path of an image fiel
NSString *kPathKey = @"path";
// key for obtaining the size of an image file
NSString *kSizeKey = @"size";
// key for obtaining the name of an image file
NSString *kNameKey = @"name";
// key for obtaining the mod date of an image file
NSString *kModifiedKey = @"modified";
// NSNotification name to tell the Window controller an image file as found
NSString *kLoadImageDidFinish = @"LoadImageDidFinish";
@interface LoadOperation ()
{
NSURL *loadURL;
NSInteger ourScanCount;
}
@property (retain) NSURL *loadURL;
@end
@implementation LoadOperation
@synthesize loadURL;
// -------------------------------------------------------------------------------
// initWithPath:path
// -------------------------------------------------------------------------------
- (id)initWithURL:(NSURL *)url scanCount:(NSInteger)scanCount
{
self = [super init];
if (self)
{
self.loadURL = url;
ourScanCount = scanCount;
}
return self;
}
// -------------------------------------------------------------------------------
// isImageFile:filePath
//
// Uses LaunchServices and UTIs to detect if a given file path is an image file.
// -------------------------------------------------------------------------------
- (BOOL)isImageFile:(NSURL *)url
{
BOOL isImageFile = NO;
NSString *utiValue;
[url getResourceValue:&utiValue forKey:NSURLTypeIdentifierKey error:nil];
if (utiValue)
{
isImageFile = UTTypeConformsTo((__bridge CFStringRef)utiValue, kUTTypeImage);
}
return isImageFile;
}
// -------------------------------------------------------------------------------
// main:
//
// Examine the given file (from the NSURL "loadURL") to see it its an image file.
// If an image file examine further and report its file attributes.
//
// We could use NSFileManager, but to be on the safe side we will use the
// File Manager APIs to get the file attributes.
// -------------------------------------------------------------------------------
- (void)main
{
if (![self isCancelled])
{
// test to see if it's an image file
if ([self isImageFile:loadURL])
{
// in this example, we just get the file's info (mod date, file size) and report it to the table view
//
NSNumber *fileSize;
[self.loadURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil];
NSDate *fileCreationDate;
[self.loadURL getResourceValue:&fileCreationDate forKey:NSURLCreationDateKey error:nil];
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setTimeStyle:NSDateFormatterNoStyle];
[formatter setDateStyle:NSDateFormatterShortStyle];
NSString *modDateStr = [formatter stringFromDate:fileCreationDate];
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
[self.loadURL lastPathComponent], kNameKey,
[self.loadURL absoluteString], kPathKey,
modDateStr, kModifiedKey,
[NSString stringWithFormat:@"%ld", [fileSize integerValue]], kSizeKey,
[NSNumber numberWithInteger:ourScanCount], kScanCountKey, // pass back to check if user cancelled/started a new scan
nil];
if (![self isCancelled])
{
// for the purposes of this sample, we're just going to post the information
// out there and let whoever might be interested receive it (in our case its MyWindowController).
//
[[NSNotificationCenter defaultCenter] postNotificationName:kLoadImageDidFinish object:nil userInfo:info];
}
}
}
}
@end
#import <Cocoa/Cocoa.h>
@interface MyWindowController : NSWindowController
{
IBOutlet NSTableView *myTableView; // the table holding the image paths
IBOutlet NSProgressIndicator *myProgressInd;
IBOutlet NSButton *myStartButton;
IBOutlet NSButton *myStopButton;
}
- (IBAction)startAction:(id)sender;
- (IBAction)stopAction:(id)sender;
@end
#import "MyWindowController.h"
#import "GetPathsOperation.h"
#import "LoadOperation.h"
@interface MyWindowController ()
{
NSMutableArray *tableRecords; // the data source for the table
NSOperationQueue *queue; // queue of NSOperations (1 for parsing file system, 2+ for loading image files)
NSTimer *timer; // update timer for progress indicator
NSMutableString *imagesFoundStr; // indicates number of images found, (NSTextField is bound to this value)
NSInteger scanCount;
}
@property (retain) NSTimer *timer;
@end
@implementation MyWindowController
@synthesize timer;
// -------------------------------------------------------------------------------
// awakeFromNib
// -------------------------------------------------------------------------------
- (void)awakeFromNib
{
// register for the notification when an image file has been loaded by the NSOperation: "LoadOperation"
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(anyThread_handleLoadedImages:)
name:kLoadImageDidFinish
object:nil];
// make sure double-click on a table row calls "doubleClickAction"
[myTableView setTarget:self];
[myTableView setDoubleAction:@selector(doubleClickAction:)];
}
// -------------------------------------------------------------------------------
// init
// -------------------------------------------------------------------------------
- (id)init
{
self = [super init];
if (self)
{
queue = [[NSOperationQueue alloc] init];
tableRecords = [[NSMutableArray alloc] init];
}
return self;
}
// -------------------------------------------------------------------------------
// setResultsString:string
// -------------------------------------------------------------------------------
- (void)setResultsString:(NSString *)string
{
[self willChangeValueForKey:@"imagesFoundStr"];
imagesFoundStr = [NSMutableString stringWithString:string];
[self didChangeValueForKey:@"imagesFoundStr"];
}
// -------------------------------------------------------------------------------
// updateCountIndicator
//
// Canned routine for updating the number of items in the table (used in several places).
// -------------------------------------------------------------------------------
- (void)updateCountIndicator
{
// set the number of images found indicator string
NSString *resultStr = [NSString stringWithFormat:@"Images found: %ld", [tableRecords count]];
[self setResultsString: resultStr];
}
// -------------------------------------------------------------------------------
// applicationShouldTerminateAfterLastWindowClosed:sender
//
// NSApplication delegate method placed here so the sample conveniently quits
// after we close the window.
// -------------------------------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return YES;
}
// -------------------------------------------------------------------------------
// mainThread_handleLoadedImages:note
//
// The method used to modify the table's data source on the main thread.
// This will cause the table to update itself once the NSArrayController is changed.
//
// The notification contains an NSDictionary containing the image file's info
// to add to the table view.
// -------------------------------------------------------------------------------
- (void)mainThread_handleLoadedImages:(NSNotification *)note
{
// Pending NSNotifications can possibly back up while waiting to be executed,
// and if the user stops the queue, we may have left-over pending
// notifications to process.
//
// So make sure we have "active" running NSOperations in the queue
// if we are to continuously add found image files to the table view.
// Otherwise, we let any remaining notifications drain out.
//
NSDictionary *notifData = [note userInfo];
NSNumber *loadScanCountNum = [notifData valueForKey:kScanCountKey];
NSInteger loadScanCount = [loadScanCountNum integerValue];
if ([myStopButton isEnabled])
{
// make sure the current scan matches the scan of our loaded image
if (scanCount == loadScanCount)
{
[tableRecords addObject:notifData];
[myTableView reloadData];
// set the number of images found indicator string
[self updateCountIndicator];
}
}
}
// -------------------------------------------------------------------------------
// anyThread_handleLoadedImages:note
//
// This method is called from any possible thread (any NSOperation) used to
// update our table view and its data source.
//
// The notification contains the NSDictionary containing the image file's info
// to add to the table view.
// -------------------------------------------------------------------------------
- (void)anyThread_handleLoadedImages:(NSNotification *)note
{
// update our table view on the main thread
[self performSelectorOnMainThread:@selector(mainThread_handleLoadedImages:) withObject:note waitUntilDone:NO];
}
// -------------------------------------------------------------------------------
// windowShouldClose:sender
// -------------------------------------------------------------------------------
- (BOOL)windowShouldClose:(id)sender
{
// are you sure you want to close, (threads running)
NSInteger numOperationsRunning = [[queue operations] count];
if (numOperationsRunning > 0)
{
NSAlert *alert = [NSAlert alertWithMessageText:@"Image files are currently loading."
defaultButton:@"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"Please click the \"Stop\" button before closing."];
[alert beginSheetModalForWindow:[self window] modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
return (numOperationsRunning == 0);
}
// -------------------------------------------------------------------------------
// loadFileURLs:fromURL
// -------------------------------------------------------------------------------
- (void)loadFileURLs:(NSURL *)fromURL
{
[queue cancelAllOperations];
// start the GetPathsOperation with the root path to start the search
GetPathsOperation *getPathsOp = [[GetPathsOperation alloc] initWithRootURL:fromURL queue:queue scanCount:scanCount];
[queue addOperation:getPathsOp]; // this will start the "GetPathsOperation"
}
#pragma mark - Actions
// -------------------------------------------------------------------------------
// doubleClickAction:sender
//
// Inspect our selected objects (user double-clicked them).
// -------------------------------------------------------------------------------
- (void)doubleClickAction:(id)sender
{
NSTableView *theTableView = (NSTableView *)sender;
NSInteger selectedRow = [theTableView selectedRow];
if (selectedRow != -1)
{
NSDictionary *objectDict = [tableRecords objectAtIndex: selectedRow];
if (objectDict != nil)
{
NSString *pathStr = [objectDict valueForKey:kPathKey];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:pathStr]];
}
}
}
// -------------------------------------------------------------------------------
// stopAction:sender
// -------------------------------------------------------------------------------
- (IBAction)stopAction:(id)sender
{
[queue cancelAllOperations];
[myStopButton setEnabled:NO];
[myStartButton setEnabled:YES];
[myProgressInd setHidden:YES];
[myProgressInd stopAnimation:self];
[self updateCountIndicator];
}
// -------------------------------------------------------------------------------
// startAction:sender
// -------------------------------------------------------------------------------
- (IBAction)startAction:(id)sender
{
NSOpenPanel *openPanel = [[NSOpenPanel alloc] init];
[openPanel setResolvesAliases:YES];
[openPanel setCanChooseDirectories:YES];
[openPanel setAllowsMultipleSelection:NO];
[openPanel setCanChooseFiles:NO];
[openPanel setPrompt:@"Choose"];
[openPanel setMessage:@"Choose a directory that has a large number image files:"];
[openPanel setTitle:@"Choose"];
[openPanel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
scanCount++;
// user has chosen a directory, start finding image files:
[tableRecords removeAllObjects]; // clear the table data
[myTableView reloadData];
[self updateCountIndicator];
[myStopButton setEnabled:YES];
[myStartButton setEnabled:NO];
[myProgressInd setHidden:NO];
[myProgressInd startAnimation:self];
[self loadFileURLs:[openPanel URL]]; // start the file search NSOperation
// schedule our update timer for our UI
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateProgress:)
userInfo:nil
repeats:YES];
}
}];
}
#pragma mark - Timer Support
// -------------------------------------------------------------------------------
// updateProgress:t
// -------------------------------------------------------------------------------
-(void)updateProgress:(NSTimer *)t
{
if ([[queue operations] count] == 0)
{
[timer invalidate];
self.timer = nil;
[myProgressInd stopAnimation:self];
[myProgressInd setHidden:YES];
[myStopButton setEnabled:NO];
[myStartButton setEnabled:YES];
// set the number of images found indicator string
[self updateCountIndicator];
}
}
#pragma mark - Data Source
// -------------------------------------------------------------------------------
// numberOfRowsInTableView:aTableView
// -------------------------------------------------------------------------------
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [tableRecords count];
}
// -------------------------------------------------------------------------------
// objectValueForTableColumn:aTableColumn:row
// -------------------------------------------------------------------------------
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
id theValue = nil;
if (tableRecords.count > 0)
{
id theRecord = [tableRecords objectAtIndex:rowIndex];
theValue = [theRecord objectForKey:[aTableColumn identifier]];
}
return theValue;
}
// -------------------------------------------------------------------------------
// sortWithDescriptor:descriptor
// -------------------------------------------------------------------------------
- (void)sortWithDescriptor:(id)descriptor
{
NSMutableArray *sorted = [[NSMutableArray alloc] initWithCapacity:1];
[sorted addObjectsFromArray:[tableRecords sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]]];
[tableRecords removeAllObjects];
[tableRecords addObjectsFromArray:sorted];
[myTableView reloadData];
}
// -------------------------------------------------------------------------------
// didClickTableColumn:tableColumn
// -------------------------------------------------------------------------------
- (void)tableView:(NSTableView *)inTableView didClickTableColumn:(NSTableColumn *)tableColumn
{
NSArray *allColumns=[inTableView tableColumns];
NSInteger i;
for (i=0; i<[inTableView numberOfColumns]; i++)
{
if ([allColumns objectAtIndex:i]!=tableColumn)
{
[inTableView setIndicatorImage:nil inTableColumn:[allColumns objectAtIndex:i]];
}
}
[inTableView setHighlightedTableColumn:tableColumn];
if ([inTableView indicatorImageInTableColumn:tableColumn] != [NSImage imageNamed:@"NSAscendingSortIndicator"])
{
[inTableView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn];
NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:[tableColumn identifier] ascending:YES];
[self sortWithDescriptor:sortDesc];
}
else
{
[inTableView setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn];
NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:[tableColumn identifier] ascending:NO];
[self sortWithDescriptor:sortDesc];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment