Mac OS X Reference Library Apple Developer
Search

Document Metadata

Spotlight provides users with a means of searching for files quickly and easily. To support this, you need to associate metadata with your documents. Core Data makes it easy to do this and to write the necessary importer.

Setting Metadata for a Store

Note that setting the metadata only queues up the information to be saved when the store is next saved—it is not written out immediately.

Steps

  1. You identify a store by its URL. Since there is more than one place that this code will be used, define a method in MyDocument to abstract the logic.

    - (BOOL)setMetadataForStoreAtURL:(NSURL *)url {
        // implementation continues...
        return NO;
    }
  2. You retrieve a store from the persistent store coordinator, using the URL as an identifier.

    NSPersistentStoreCoordinator *psc = [[self managedObjectContext] persistentStoreCoordinator];
    NSPersistentStore *pStore = [psc persistentStoreForURL:url];
  3. If pStore is not nil, then you can set the metadata. The metadata is a dictionary of key-value pairs, where a key may be either custom for your application, or one of the standard set of Spotlight keys such as kMDItemKeywords. Core Data automatically sets values for NSStoreType and NSStoreUUID, so make a mutable copy of the existing metadata, and then add your own keys and values. In this example, simply set the department name as a keyword, then return YES.

    Note that the metadata may be set before validation methods are invoked, so even though the Department name is not optional, although unlikely it is possible for the value to be nil at this stage. You should therefore guard against attempting to insert a nil value into the array.

    NSString *departmentName = self.department.name;
     
    if ((pStore != nil) && (departmentName != nil)) {
        NSMutableDictionary *metadata = [[psc metadataForPersistentStore:pStore] mutableCopy];
     
        if (metadata == nil) {
             metadata = [NSMutableDictionary dictionary];
        }
        [metadata setObject:[NSArray arrayWithObject:departmentName]
                forKey:(NSString *)kMDItemKeywords];
     
        [psc setMetadata:metadata forPersistentStore:pStore];
        return YES;
    }

Complete Code Listing

A complete listing for setMetadataForStoreAtURL: is shown in Listing 7-1.

Listing 7-1  Complete listing of the setMetadataForStoreAtURL: method

- (BOOL)setMetadataForStoreAtURL:(NSURL *)url {
 
    NSPersistentStoreCoordinator *psc = [[self managedObjectContext] persistentStoreCoordinator];
    NSPersistentStore *pStore = [psc persistentStoreForURL:url];
    NSString *departmentName = self.department.name;
 
    if ((pStore != nil) && (departmentName != nil)) {
        NSMutableDictionary *metadata = [[psc metadataForPersistentStore:pStore] mutableCopy];
        if (metadata == nil) {
            metadata = [NSMutableDictionary dictionary];
        }
        [metadata setObject:[NSArray arrayWithObject:departmentName]
                forKey:(NSString *)kMDItemKeywords];
        [psc setMetadata:metadata forPersistentStore:pStore];
        return YES;
    }
    return NO;
}

Set the Metadata for a New Store

When a new store is configured (whether for a new untitled document, or when an existing document is reopened), Core Data calls the NSPersistentDocument method configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error:. You can override this method to add metadata to a new store before it is saved.

Steps

  1. In MyDocument, add an implementation for configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error: . You first call the superclass’s implementation, and check the return value:

    - (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url
        ofType:(NSString *)fileType
        modelConfiguration:(NSString *)configuration
        storeOptions:(NSDictionary *)storeOptions
        error:(NSError **)error {
     
        BOOL ok = [super configurePersistentStoreCoordinatorForURL:url
                    ofType:fileType modelConfiguration:configuration
                    storeOptions:storeOptions error:error];
        if (ok) {
            // implementation continues...
        }
        return ok;
    }
  2. If the return value for the superclass’s implementation is YES, then retrieve the persistent store for the specified URL from the persistent store coordinator:

    NSPersistentStoreCoordinator *psc = [[self managedObjectContext] persistentStoreCoordinator];
    NSPersistentStore *pStore = [psc persistentStoreForURL:url];
  3. Since the configure method is also called when a document is reopened, you should check for existing custom metadata to avoid overwriting it unnecessarily. If your metadata is not present, set it using setMetadataForStoreAtURL:.

    id existingMetadata = [[psc metadataForPersistentStore:pStore]
             objectForKey:(NSString *)kMDItemKeywords];
    if (existingMetadata == nil) {
        ok = [self setMetadataForStoreAtURL:url];
    }

Complete Code Listing

A complete listing for configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error: is shown in Listing 7-2.

Listing 7-2  Complete listing of the configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error: method

- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url
    ofType:(NSString *)fileType
    modelConfiguration:(NSString *)configuration
    storeOptions:(NSDictionary *)storeOptions
    error:(NSError **)error {
 
    BOOL ok = [super configurePersistentStoreCoordinatorForURL:url
                ofType:fileType modelConfiguration:configuration
                storeOptions:storeOptions error:error];
    if (ok) {
        NSPersistentStoreCoordinator *psc = [[self managedObjectContext]
                persistentStoreCoordinator];
        NSPersistentStore *pStore = [psc persistentStoreForURL:url];
        id existingMetadata = [[psc metadataForPersistentStore:pStore]
                objectForKey:(NSString *)kMDItemKeywords];
        if (existingMetadata == nil) {
            ok = [self setMetadataForStoreAtURL:url];
        }
    }
    return ok;
}

Set the Metadata for an Existing Store

When a document is saved, Core Data calls the NSPersistentDocument method writeToURL:ofType:forSaveOperation:originalContentsURL:error:. You can override this method to add metadata to the new store before it is saved. (Recall that setting the metadata for a store does not change the information on disk until the store is saved.)

Steps

  1. In MyDocument, add an implementation for writeToURL: ofType: forSaveOperation: originalContentsURL: error:. The final step is to invoke and return the superclass’s implementation.

    - (BOOL)writeToURL:(NSURL *)absoluteURL
        ofType:(NSString *)typeName
        forSaveOperation:(NSSaveOperationType)saveOperation
        originalContentsURL:(NSURL *)absoluteOriginalContentsURL
        error:(NSError **)error {
     
        // implementation continues...
     
        return [super writeToURL:absoluteURL
                          ofType:typeName
                forSaveOperation:saveOperation
             originalContentsURL:absoluteOriginalContentsURL
                           error:error];
    }
  2. If the document’s URL is not nil, then it is possible to retrieve the persistent store for that URL from the persistent store coordinator. Invoke setMetadataForStoreAtURL: to set the metadata for the store.

    if ([self fileURL] != nil) {
            [self setMetadataForStoreAtURL:[self fileURL]];
    }

    Note that this also takes account of Save As operations. The metadata is associated with the persistent store before it is written to a new file.

Complete Code Listing

A complete listing for writeToURL:ofType:forSaveOperation:originalContentsURL:error: is shown in Listing 7-3.

Listing 7-3  Complete listing of the writeToURL:ofType:forSaveOperation:originalContentsURL:error: method

- (BOOL)writeToURL:(NSURL *)absoluteURL
    ofType:(NSString *)typeName
    forSaveOperation:(NSSaveOperationType)saveOperation
    originalContentsURL:(NSURL *)absoluteOriginalContentsURL
    error:(NSError **)error {
 
    if ([self fileURL] != nil) {
        [self setMetadataForStoreAtURL:[self fileURL]];
    }
    return [super writeToURL:absoluteURL
                      ofType:typeName
            forSaveOperation:saveOperation
         originalContentsURL:absoluteOriginalContentsURL
                       error:error];
}

Build and Test

Build and run the application again. Create and save several documents, giving the department a different name in each. Close and then reopen some of the documents, and save some to new locations.

If you open a document in a text editor (such as TextEdit), you should see that the correct metadata is appended to the file.

What Happened?

You used methods defined by NSPersistentDocument to set metadata for a store as it is saved. It is up to you to decide what information to store as metadata, and what keys to specify. You use the keys when writing your importer.

Writing a Spotlight Importer for Core Data

Details of how in general to write an importer are given in Spotlight Importer Programming Guide. This section deals with aspects that are specific to writing an importer for Core Data.

To implement an importer, you first create a new Metadata Importer project in Xcode. Since a Core Data importer uses Objective-C, you should change the file type of the GetMetadataForFile.c file from sourcecode.c.c to sourcecode.c.objc using the Xcode inspector (Info window).

An important aspect of a Spotlight importer is that it should be efficient. A user may have many thousands of files, so any small inefficiency in an importer may have a serious impact on the time it takes to index their disk drive. One of the more expensive tasks in Core Data is creating the persistence stack—the object stores, the object store coordinator, the managed object context, and so on. So that you can avoid this overhead when reading metadata, NSPersistentStoreCoordinator provides a convenience method—metadataForPersistentStoreWithURL:—that retrieves the dictionary containing the metadata stored in an on-disk persistent store without initializing a persistence stack.

The main task when you write an importer is to implement the function GetMetadataForFile. The function must populate a mutable dictionary—supplied as one of the arguments—with the metadata for the specified file. Given the NSPersistentStoreCoordinator convenience method, the code is trivial, as shown here:

Boolean GetMetadataForFile(void* thisInterface,
    CFMutableDictionaryRef attributes,
    CFStringRef contentTypeUTI,
    CFStringRef pathToFile) {
 
    NSURL *url = [NSURL fileURLWithPath:(NSString *)pathToFile];
    NSDictionary *metadata = [NSPersistentStoreCoordinator metadataForPersistentStoreWithURL:url error:nil];
 
    if (metadata != nil) {
        [(NSMutableDictionary *)attributes addEntriesFromDictionary:metadata];
        return TRUE;
    }
    return FALSE;
}

In addition to implementing the GetMetadataForFile function, you must (as with all importers) modify the CFBundleDocumentTypes entry in the importer project’s Info.plist file to contain an array of uniform type identifiers (UTIs) for the LSItemContentTypes that your importer can handle, and (if you have defined new attributes) update the schema.xml file. These are explained in detail in Spotlight Importer Programming Guide.




Last updated: 2009-02-04

Did this document help you? Yes It's good, but... Not helpful...