#import "AppDelegate.h" @interface AppDelegate() - (NSString*)applicationSupportFolder; @end @implementation AppDelegate - (IBAction)addImage:(id)sender; { NSManagedObject *recipe = [[recipeArrayController selectedObjects] lastObject]; if (!recipe) return; NSOpenPanel *openPanel = [NSOpenPanel openPanel]; [openPanel setCanChooseDirectories:NO]; [openPanel setCanCreateDirectories:NO]; [openPanel setAllowsMultipleSelection:NO]; [openPanel beginSheetForDirectory:nil file:nil modalForWindow:window modalDelegate:self didEndSelector:@selector(addImageSheetDidEnd:returnCode:contextInfo:) contextInfo:recipe]; } - (void)addImageSheetDidEnd:(NSOpenPanel*)openPanel returnCode:(NSInteger)returnCode contextInfo:(NSManagedObject*)recipe { if (returnCode == NSCancelButton) return; NSString *path = [openPanel filename]; //Build the path we want the file to be at NSString *destPath = [self applicationSupportFolder]; destPath = [destPath stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; [[NSFileManager defaultManager] copyPath:path toPath:destPath handler:nil]; [recipe setValue:destPath forKey:@"imagePath"]; } - (NSString *)applicationSupportFolder { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory(); return [basePath stringByAppendingPathComponent:@"GrokkingRecipes"]; } - (NSManagedObjectModel *)managedObjectModel { if (managedObjectModel) return managedObjectModel; NSString *path = [[NSBundle mainBundle] pathForResource:@"DataModel" ofType:@"momd"]; if (!path) { path = [[NSBundle mainBundle] pathForResource:@"DataModel" ofType:@"mom"]; } NSAssert(path != nil, @"Unable to find DataModel in main bundle"); NSURL *url = [NSURL fileURLWithPath:path]; managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:url]; return managedObjectModel; } //START:progressivelyMigrateURLMethodName - (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL ofType:(NSString*)type toModel:(NSManagedObjectModel*)finalModel error:(NSError**)error { //END:progressivelyMigrateURLMethodName //START:progressivelyMigrateURLHappyCheck NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type URL:sourceStoreURL error:error]; if (!sourceMetadata) return NO; if ([finalModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata]) { *error = nil; return YES; } //END:progressivelyMigrateURLHappyCheck //START:progressivelyMigrateURLFindModels //Find the source model NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata]; NSAssert(sourceModel != nil, ([NSString stringWithFormat: @"Failed to find source model\n%@", sourceMetadata])); //Find all of the mom and momd files in the Resources directory NSMutableArray *modelPaths = [NSMutableArray array]; NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd" inDirectory:nil]; for (NSString *momdPath in momdArray) { NSString *resourceSubpath = [momdPath lastPathComponent]; NSArray *array = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:resourceSubpath]; [modelPaths addObjectsFromArray:array]; } NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:nil]; [modelPaths addObjectsFromArray:otherModels]; if (!modelPaths || ![modelPaths count]) { //Throw an error if there are no models NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setValue:@"No models found in bundle" forKey:NSLocalizedDescriptionKey]; //Populate the error *error = [NSError errorWithDomain:@"Zarra" code:8001 userInfo:dict]; return NO; } //END:progressivelyMigrateURLFindModels //See if we can find a matching destination model //START:progressivelyMigrateURLFindMap NSMappingModel *mappingModel = nil; NSManagedObjectModel *targetModel = nil; NSString *modelPath = nil; for (modelPath in modelPaths) { targetModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]]; mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:targetModel]; //If we found a mapping model then proceed if (mappingModel) break; //Release the target model and keep looking [targetModel release], targetModel = nil; } //We have tested every model, if nil here we failed if (!mappingModel) { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setValue:@"No models found in bundle" forKey:NSLocalizedDescriptionKey]; *error = [NSError errorWithDomain:@"Zarra" code:8001 userInfo:dict]; return NO; } //END:progressivelyMigrateURLFindMap //We have a mapping model and a destination model. Time to migrate //START:progressivelyMigrateURLMigrate NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel]; NSString *modelName = [[modelPath lastPathComponent] stringByDeletingPathExtension]; NSString *storeExtension = [[sourceStoreURL path] pathExtension]; NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension]; //Build a path to write the new store storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath, modelName, storeExtension]; NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath]; if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:error]) { return NO; } //END:progressivelyMigrateURLMigrate //Migration was successful, move the files around to preserve the source //START:progressivelyMigrateURLMoveAndRecurse NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; guid = [guid stringByAppendingPathExtension:modelName]; guid = [guid stringByAppendingPathExtension:storeExtension]; NSString *appSupportPath = [storePath stringByDeletingLastPathComponent]; NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid]; NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager moveItemAtPath:[sourceStoreURL path] toPath:backupPath error:error]) { //Failed to copy the file return NO; } //Move the destination to the source path if (![fileManager moveItemAtPath:storePath toPath:[sourceStoreURL path] error:error]) { //Try to back out the source move first, no point in checking it for errors [fileManager moveItemAtPath:backupPath toPath:[sourceStoreURL path] error:nil]; return NO; } //We may not be at the "current" model yet, so recurse return [self progressivelyMigrateURL:sourceStoreURL ofType:type toModel:finalModel error:error]; //END:progressivelyMigrateURLMoveAndRecurse } //START:persistentStoreCoordinator - (NSPersistentStoreCoordinator*)persistentStoreCoordinator; { if (persistentStoreCoordinator) return persistentStoreCoordinator; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *applicationSupportFolder = [self applicationSupportFolder]; if ( ![fileManager fileExistsAtPath:applicationSupportFolder isDirectory:NULL] ) { [fileManager createDirectoryAtPath:applicationSupportFolder attributes:nil]; } NSURL *url = [NSURL fileURLWithPath:[applicationSupportFolder stringByAppendingPathComponent:@"GrokkingRecipes.xml"]]; NSManagedObjectModel *mom = [self managedObjectModel]; persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; //END:persistentStoreCoordinator //START:progressivelyMigrateCall NSError *error = nil; if (![self progressivelyMigrateURL:url ofType:NSXMLStoreType toModel:mom error:&error]) { [[NSApplication sharedApplication] presentError:error]; return nil; } //END:progressivelyMigrateCall //START:persistentStoreCoordinator if (![persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) { [[NSApplication sharedApplication] presentError:error]; return nil; } return persistentStoreCoordinator; } //END:persistentStoreCoordinator - (NSManagedObjectContext*)managedObjectContext { if (managedObjectContext) return managedObjectContext; NSPersistentStoreCoordinator *coord = [self persistentStoreCoordinator]; if (!coord) return nil; managedObjectContext = [[NSManagedObjectContext alloc] init]; [managedObjectContext setPersistentStoreCoordinator: coord]; NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; [request setEntity:[NSEntityDescription entityForName:@"Type" inManagedObjectContext:managedObjectContext]]; NSError *error = nil; NSArray *result = [managedObjectContext executeFetchRequest:request error:&error]; NSAssert(error == nil, [error localizedDescription]); if ([result count]) return managedObjectContext; //The types table has not been populated NSArray *types; types = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"RecipeTypes"]; for (NSString *type in types) { NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:@"Type" inManagedObjectContext:managedObjectContext]; [object setValue:type forKey:@"name"]; } return managedObjectContext; } - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window { return [[self managedObjectContext] undoManager]; } - (IBAction) saveAction:(id)sender { NSError *error = nil; if (![[self managedObjectContext] save:&error]) { [[NSApplication sharedApplication] presentError:error]; } } - (NSApplicationTerminateReply)applicationShouldTerminate:(id)sender { NSError *error = nil; if (!managedObjectContext) return NSTerminateNow; if (![managedObjectContext commitEditing]) { //Failed to commit editing. return NSTerminateCancel; } if (![managedObjectContext hasChanges]) return NSTerminateNow; if (![managedObjectContext save:&error]) { int alertReturn = NSRunAlertPanel(nil, @"Could not save changes while quitting. Quit anyway?" , @"Quit anyway", @"Cancel", nil); if (alertReturn == NSAlertAlternateReturn) NSTerminateCancel; } return NSTerminateNow; } - (void) dealloc { [managedObjectContext release], managedObjectContext = nil; [persistentStoreCoordinator release], persistentStoreCoordinator = nil; [managedObjectModel release], managedObjectModel = nil; [super dealloc]; } @end