Last active
December 16, 2015 16:49
-
-
Save ifournight/5465792 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Cocoa/Cocoa.h> | |
@interface GetPathsOperation : NSOperation | |
- (id)initWithRootURL:(NSURL *)url queue:(NSOperationQueue *)qq scanCount:(NSInteger)scanCount; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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