views:

1140

answers:

2

The data model for my Core Data document-based app (10.5 only) is in a framework, so automatic schema upgrades using a Core Data mapping model don't appear to work. It appears that the Core Data machinery doesn't find the appropriate data models or mapping model when they are not in the app's main bundle. So, instead of using the automatic migration, I'm running a migration manually in configurePersistentStoreCoordinatorForURL:ofType:... in my NSPersistenDocument subclass (code below). I migrate the persistent store to a temporary file and then overwrite the existing file if the migration succeeds. The document then presents an error with the message "This document's file has been changed by another application since you opened or saved it." when I try to save. As others on this list have pointed out, this is due to my modification of the document's file "behind its back". I tried updating the document's file modification date, as shown below, but I then get an error dialog with the message "The location of the document "test.ovproj" cannot be determined." when I try to save. I'm less sure of the reason for this error, but trading one unnecessary message (in this case) for an other isn't quite what I was going for.

Can anyone offer some guidance? Is there a way to manually upgrade the schema for a document's persistent store without triggering one of these (in this case unnecessary) warnings?

code for upgrading the data store in my subclasses -configurePersistentStoreCoordinatorForURL:ofType:... :

if(upgradeNeeded) {
           NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:VUIModelBundles() orStoreMetadata:meta];

           if(sourceModel == nil) {
               *error = [NSError errorWithDomain:VUIErrorDomainn ode:VUICoreDataErrorCode localizedReason:BWLocalizedString(@"Unable to find original data model for project.")];
               return NO;
           }

           NSManagedObjectModel *destinationModel = [self managedObjectModel];

           NSMigrationManager *migrationManager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:destinationModel];
           NSMappingModel *mappingModel = [NSMappingModel mappingModelFromBundles:VUIModelBundles() forSourceModel:sourceModel destinationModel:destinationModel];
           if(mappingModel == nil) {
               *error = [NSError errorWithDomain:VUIErrorDomain code:VUICoreDataErrorCode localizedReason:BWLocalizedString(@"Unable to find mapping model to convert project to most recent project format.")];
               return NO;
           }

           @try {
               //move file to backup
               NSAssert([url isFileURL], @"store url is not a file URL");

               NSString *tmpPath = [NSString tempFilePath];
               id storeType = [meta objectForKey:NSStoreTypeKey];
               if(![migrationManager migrateStoreFromURL:url
                                                    type:storeType
                                                 options:storeOptions
                                        withMappingModel:mappingModel
                                       toDestinationURL:[NSURLfileURLWithPath:tmpPath]
                                         destinationType:storeType
                                      destinationOptions:storeOptions
                                                   error:error]) {

                   return NO;
               } else {
                   //replace old with new
                   if(![[NSFileManager defaultManager] removeItemAtPath:[url path] error:error] ||
                      ![[NSFileManager defaultManager] moveItemAtPath:tmpPath toPath:[url path] error:error]) {
                       return NO;
                   }

                   // update document file modification date to prevent warning (#292)
                   NSDate *newModificationDate = [[[NSFileManager defaultManager] fileAttributesAtPath:[url path] traverseLink:NO] bjectForKey:NSFileModificationDate];
                   [self setFileModificationDate:newModificationDate];
               }
           }
           @finally {
               [migrationManager release];
           }
       }
   }

   return [super configurePersistentStoreCoordinatorForURL:url ofType:fileType modelConfiguration:configuration storeOptions:storeOptions error:error];
+1  A: 

I haven't run across this particular situation, but I have a few guesses. First, instead of using -removeItemAtPath: and -moveItemAtPath: when you want to switch files, use the FSExchangeObjects() function instead. NSDocument uses FSRefs to track the file and unless you use FSExchangeObjects(), it'll realize that it's looking at a completely different file.

Second, you can manually set your document's managed object model by overriding -managedObjectModel, in particular using the +mergedModelFromBundles: method to load the models from your framework. According to the docs, it should by default merge any models in the main bundle and in all linked frameworks, so this should only be necessary for dynamically loaded bundles. Don't know why that's not working for you, but I haven't tried this. To figure out what bundles to search, NSBundle's +bundleForClass: method is your friend.

Boaz Stuller
Interestingly, the readFromURL:ofType:error: did not solve the problem. Using FSExchangeObjects(), plus using [self setFileModificationDate:] with the new files modification date did do the trick. Thanks for the pointer.
Barry Wark
Good to know. I erased that one from my answer since it doesn't work.
Boaz Stuller
A: 

Beware FSExchangeObjects()! It does not support all volumes types, see bSupportsFSExchangeObjects. I'm looking for a replacement myself. Option seem to be MoreFilesX's FSExchangeObjectsCompat or 10.5's FSReplaceObjects().

Anan