Programming with Spotlight
Pages: 1, 2, 3, 4
Query the Spotlight Server
So far, we've displayed the Spotlight search window and examined a specific file's metadata. While interesting and useful, these features still leave plenty to be sought. Querying the Spotlight server helps to fill this void and is one of the ways Spotlight really struts its stuff. In our example application, we'll use Spotlight to query the entire file system for mail messages the same way we could in the Spotlight search window. Take a moment to review the docs on NSMetadataQuery and NSPredicate if you haven't already; these are the cornerstones. Everything else is from the standard Model View Controller repertoire.
Let's update your controller class again. Changes are in bold.
For "Controller.h"
// Controller.h
// SpotlightExamples
#import <Cocoa/Cocoa.h>
@interface Controller : NSObject {
IBOutlet NSButton* openSearchWindowButton;
IBOutlet NSTextView* metadataInfoView;
IBOutlet NSButton* displayMetadataButton;
IBOutlet NSButton* startSearchButton;
IBOutlet NSButton* stopSearchButton;
IBOutlet NSTextField* numHitsField;
NSMetadataQuery* q;
NSTimer* t;
}
- (IBAction)openSearchWindowAction:(id)sender;
- (IBAction)displayMetadataAction:(id)sender;
- (id)init;
- (void)awakeFromNib;
- (void)dealloc;
- (IBAction)startSearchAction:(id)sender;
- (IBAction)stopSearchAction:(id)sender;
- (void)stopSearching;
- (void)updateResults:(NSTimer*)timer;
@end
For "Controller.m"
// Controller.m
// SpotlightExamples
#import "Controller.h"
@implementation Controller
- (IBAction)openSearchWindowAction:(id)sender
{
OSStatus resultCode=noErr;
//Replace "Search Text" with user input
resultCode=
HISearchWindowShow((CFStringRef)@"Search Text", kNilOptions);
if (resultCode != noErr) {
NSLog(@"Failed to open search window");
//Could use NSAlert class to display interactive dialog
}
}
- (IBAction)displayMetadataAction:(id)sender
{
//create a CF-compliant object representing
//a file and its metadata using a Carbon level call
//add in a path to an existing file on your system
CFStringRef path = CFSTR("/Users/matthew/temp.txt");
MDItemRef item = MDItemCreate(kCFAllocatorDefault, path);
//pull out the metadata attribute names
CFArrayRef attributeNames = MDItemCopyAttributeNames(item);
//use toll-free bridging to load up an NSArray for convenience
NSArray* array = (NSArray*)attributeNames;
NSEnumerator *e = [array objectEnumerator];
id arrayObject;
//placeholders
NSMutableString *info =
[NSMutableString stringWithCapacity:50];
CFTypeRef ref;
while ((arrayObject = [e nextObject]))
{
ref =
MDItemCopyAttribute(item, (CFStringRef)[arrayObject description]);
//cast to get an NSObject for convenience
NSObject* tempObject = (NSObject*)ref;
[info appendString:[arrayObject description]];
[info appendString:@" = "];
[info appendString:[tempObject description]];
[info appendString:@"\n"];
}
//set the info in the text view
[metadataInfoView insertText:info];
}
- (id)init
{
if (self = [super init])
{
//release in dealloc
q = [[NSMetadataQuery alloc] init];
}
return self;
}
- (void)awakeFromNib
{
[q setDelegate: self];
//remember to unregister for notifications
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(stopSearching)
name:NSMetadataQueryDidFinishGatheringNotification
object:nil];
}
- (void)dealloc
{
[q release];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:nil];
[super dealloc];
}
- (IBAction)startSearchAction:(id)sender
{
//whatever query you want. emlx corresponds
//to mail messages. you could easily
//configure search terms from user interaction.
NSPredicate *p =
[NSPredicate predicateWithFormat:@"kMDItemKind == 'emlx'", nil];
[q setPredicate:p];
//optionally set search scopes
//[q setSearchScopes:
// [NSArray arrayWithObject:@"/Users/matthew/Library/Mail/"]];
//start the query and use the run loop
//to process the search progress.
if ([q startQuery])
{
t =
[NSTimer scheduledTimerWithTimeInterval:0.25
target:self
selector:@selector(updateResults:)
userInfo:q
repeats:YES];
//NSRunLoop retains the timer
[[NSRunLoop currentRunLoop]
addTimer:t
forMode:NSDefaultRunLoopMode];
}
else
{
NSLog(@"Error. Could not start query. Weird.");
}
}
- (IBAction)stopSearchAction:(id)sender
{
[self stopSearching];
}
//called via the NSMetadataQueryDidFinishGatheringNotification
//and/or the stopSearchAction: method
- (void)stopSearching
{
//don't invalidate a timer more than once
if (!([q isStopped]))
{
//NSLog(@"Finito. Num results = %d", [q resultCount]);
[self updateResults:t];
[q stopQuery];
[t invalidate];
}
}
- (void)updateResults:(NSTimer*)timer
{
NSString *tempString =
[NSString stringWithFormat:@"%d", [[timer userInfo] resultCount], nil];
[numHitsField setStringValue:tempString];
//NSLog(@"%d", [[timer userInfo] resultCount]);
}
@end
Like last time, drag and drop your header file onto Interface Builder's palette so that your instantiated controller reflects the changes. On your application's main menu, you'll need to:
- Add two NSButtons and an NSTextField and connect their outlets
- Rename one NSButton "Start Search"
- Rename one NSButton "Stop Search"
- Add the NSTextField
- Connect their outlets as you've been previously doing with Ctrl-click drags.
- Set the actions for the "Start Search" button and "Stop Search" button
- Connect "Start Search" to
startSearchAction: - Connect "Stop Search" to
stopSearchAction:
- Connect "Start Search" to
- Do any finishing touches (optional)
- Group controls together with an NSBox
- Title the main window
If you decide to group controls using an NSBox, you have to delete your existing controls, drag "fresh" controls from Interface Builder's palette over onto the box, and then re-establish the outlets and connections. This only takes a few moments, and makes things look a lot less chaotic. Although the application you've developed is fairly pedagogical, the concepts and code snippets used are the same that you'd use in more sophisticated circumstances.

Your project can now query the entire filesystem.
You can get the project file for this final portion here.
Command Line Tools
Before creating a Spotlight plugin next time, you might like to know that you don't necessarily have to be a Cocoa programmer to benefit from Spotlight. Apple provides several very useful command line tools that you can use in shell scripts to query and manipulate metadata. Here's a few you might find handy:
mdls: lists the metadata attributes for the specified filemdfind: finds files matching a given querymdimport: imports file hierarchies into the metadata datastoremdutil: manages the metadata stores used by Spotlight
Since this is a Cocoa-oriented tutorial, we won't work through command-line examples. You'll have no problems getting acquainted by using the man pages or Apple's documentation. These command-line tools are very useful for debugging an importer (as we'll see next time) or for use in your Perl, Bash, or other scripting routines that can benefit from the metadata awareness.
On a final note about command line tools, realize that you aren't constrained to the ones Apple provides. You can create your own custom tools using Spotlight's Carbon-level API available to you. In fact, the Cocoa-level API we've primarily been using is simply a wrapper around these Carbon-level calls. The concepts are exactly the same, and you might even enjoy the simplicity gained from the absence of human interaction (every once in a while).
Next Time
For next time, you'll want to skim the documentation on Introduction to Spotlight Importers and ponder how in the world we'll get Spotlight to be aware of notes in Stickies. Until then.
Matthew Russell is a computer scientist from middle Tennessee; and serves Digital Reasoning Systems as the Director of Advanced Technology. Hacking and writing are two activities essential to his renaissance man regimen.
Return to the Mac DevCenter
-
Metadata request
2010-03-09 08:30:17 tiger_champ [View]
-
Metadata request
2010-06-22 04:05:37 sciencekris [View]
-
What about Spotlight in Leopard?
2007-12-15 08:29:39 mehlkelm [View]
-
Memory Leaks
2006-02-06 05:07:47 Tim.Scheffler [View]
-
Nice tutorial
2005-07-31 18:16:20 HiRez [View]
-
spotlight programming
2005-07-19 07:23:54 hcvermeulen [View]
-
spotlight programming
2005-07-19 18:24:03 Matthew Russell |
[View]
-
Great Tutorial
2005-07-13 01:27:43 zipman [View]

