0

I'm using core-data in my app, and I want to start iTunes filesharing in next update, but need to move my apps sqlite database first. I have tried with the code below, but the app crashes on launch. I thought I could just replace the old store url with a new one in NSPersistentStoreCoordinator, where "newDatabasePath" matches the new store url.

then replace the sqlite file with

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

//check app has run before and has a current db
if ([saveData boolForKey:@"hasRunBefore"]) {
NSString *oldDatabasePath = [[dirPaths objectAtIndex:0] stringByAppendingPathComponent:@"AppData.sqlite"];
        NSString *newDatabasePath = [privateDocsPath stringByAppendingPathComponent:@"AppData.sqlite"];
        NSError *error;
        if ([fileMgr fileExistsAtPath:newDatabasePath]) {

            [fileMgr removeItemAtPath:newDatabasePath error:&error];
            [fileMgr copyItemAtPath:oldDatabasePath toPath:newDatabasePath  error:&error];
        }

        [fileMgr removeItemAtPath:oldDatabasePath error:&error];

        BOOL databaseMoved = YES;
        [saveData setBool:databaseMoved forKey:@"databaseMoved"];
}
}

Thanks

I tried a new approach to solve this with some success, after reading a similar question on here. I tried resetting the coredata stack like so

- (void)resetDatabase {

NSPersistentStore* store = [[__persistentStoreCoordinator persistentStores] lastObject];

NSError *error = nil;
NSURL *storeURL = store.URL;

// release context and model
__managedObjectModel = nil;
__managedObjectContext = nil;

//[__persistentStoreCoordinator removePersistentStore:store error:nil];

__persistentStoreCoordinator = nil;

NSFileManager* fileMgr = [NSFileManager defaultManager];
[fileMgr removeItemAtPath:storeURL.path error:&error];
if (error) {
    NSLog(@"filemanager error %@", error);
}

NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

//create Private Documents Folder
NSArray *libPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libDir = [libPaths objectAtIndex:0];
NSString *privateDocsPath = [libDir stringByAppendingPathComponent:@"Private Documents"];
if (![fileMgr fileExistsAtPath:privateDocsPath])
    [fileMgr createDirectoryAtPath:privateDocsPath withIntermediateDirectories:YES attributes:nil error:nil];


NSString *oldDatabasePath = [[dirPaths objectAtIndex:0] stringByAppendingPathComponent:@"AppData.sqlite"];
NSString *newDatabasePath = [privateDocsPath stringByAppendingPathComponent:@"AppData.sqlite"];

BOOL removedItemAtPath = NO;
BOOL copiedItemToPath = NO;

if ([fileMgr fileExistsAtPath:newDatabasePath]) {
    DLog(@"DATABASE EXISTS AT PATH");
    removedItemAtPath = [fileMgr removeItemAtPath:newDatabasePath error:&error];
    if (removedItemAtPath) {
        DLog(@"ITEM REMOVED");
    }
    else
        DLog(@"FAILED TO REMOVE ITEM: %@", error);

    copiedItemToPath = [fileMgr copyItemAtPath:oldDatabasePath toPath:newDatabasePath  error:&error];
    if (copiedItemToPath) {
        DLog(@"ITEM COPIED");
    }
    else
        DLog(@"FAILED TO COPY ITEM: %@", error);
}

// recreate the stack
__managedObjectContext = [self managedObjectContext];

}

With this approach, the app still throws an exception when trying to load data from the coredata stack when I first launch it, but then subsequently reloads everything ok, and uses the sqlite file from it's new location in "Library/Private Documents"

2
  • Crashlog will be really helpful to answer your question?
    – Iducool
    Commented Aug 2, 2013 at 11:33
  • crash log isn't very helpful, just saying that my app can't load the data it needs which I know anyway, otherwise it wouldn't be crashing *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array' *** First throw call stack: Commented Aug 2, 2013 at 14:17

3 Answers 3

0

Since you said that the error message was:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array' *** First throw call stack:

That points the finger pretty strongly at this line:

NSString *oldDatabasePath = [[dirPaths objectAtIndex:0] stringByAppendingPathComponent:@"AppData.sqlite"];

The error message tells you why, too-- you're using index 0 on an empty array. So, dirPaths is empty. I don't know why, since you didn't post the code where you give it a value, but that's the cause of this crash.

2
  • Hi Tom, not sure if that is what's happening. The throw occurs when the root view controller tries to load it's original coredata through an NSFetchRequest. AS I understand it, when I change the NSPersistentStoreCoordinators storeUrl, it creates a new sqlite database in the empty directory as it should. Then I want to delete that sqlite database and copy over the original sqlite database from before the app update to this new directory. I have debugged the remove and copy operations and they are happening as I want them too. Commented Aug 2, 2013 at 17:00
  • Then you should post more information in your question, because with the details available there's no other reasonable conclusion. Try setting an exception breakpoint, that will tell you what line of code is causing the crash. Commented Aug 2, 2013 at 17:07
0

The answer turned out to be simple as it usually does!

I just moved the code I needed to copy the apps original database into - (NSPersistentStoreCoordinator *)persistentStoreCoordinator before the store is added rather than trying to do it from - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions.

There goes another wasted day!

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{

if (__persistentStoreCoordinator != nil)
{
    return __persistentStoreCoordinator;
}

//test if app already run before, copy database over and remove old one, finally switch store url to new directory
if ([saveData boolForKey:@"hasRunBefore"]) {

    if ([saveData boolForKey:@"databaseUpdated"] != YES) {

        DLog(@"MOVING DATABASE");
        NSFileManager* fileMgr = [[NSFileManager alloc] init];
        fileMgr.delegate = self;
        NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *docsDir = [dirPaths objectAtIndex:0];
        //create Private Documents Folder
        NSArray *libPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
        NSString *libDir = [libPaths objectAtIndex:0];
        NSString *privateDocsPath = [libDir stringByAppendingPathComponent:@"Private Documents"];
        if (![fileMgr fileExistsAtPath:privateDocsPath])
            [fileMgr createDirectoryAtPath:privateDocsPath withIntermediateDirectories:YES attributes:nil error:nil];

        NSString *oldDatabasePath = [docsDir stringByAppendingPathComponent:@"AppData.sqlite"];
        NSString *newDatabasePath = [privateDocsPath stringByAppendingPathComponent:@"AppData.sqlite"];
        NSError *error;
        //BOOL removedItemAtPath = NO;
        BOOL copiedItemToPath = NO;

        copiedItemToPath = [fileMgr copyItemAtPath:oldDatabasePath toPath:newDatabasePath  error:&error];
        if (copiedItemToPath) {
            DLog(@"ITEM COPIED");
        }
        else
            DLog(@"FAILED TO COPY ITEM: %@", error);

        [fileMgr removeItemAtPath:oldDatabasePath error:&error];

        [saveData setBool:YES forKey:@"databaseUpdated"];
        [saveData synchronize];

    }

}


//NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"AppData.sqlite"];
NSURL *storeURL = [[self applicationHiddenDocumentsDirectory] URLByAppendingPathComponent:@"AppData.sqlite"];

NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
    DLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}    

return __persistentStoreCoordinator;
}
0

I had the same issue and found a very simple solution. Basically, before you initialize your NSPersistentStoreCoordinator, like at the top of - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions, move the database files from their current location to the new location. Make sure that you create the new location if it doesn't yet exist. In my sample code below I use a Database subdirectory under the Library/Application Support directory. I didn't show the creation of the "Database" directory; but, that also must occur before the files are moved. Make sure that your store initialization uses the new location.

NSURL *storeURL = [NSURL fileURLWithPath:[self getFullPrivatePath:@"<Database file name>"]];



Here's some code to help make this clear. I included my helper methods as well so there wasn't any confusion about where the methods came from or how they work. Note that there are 3 database files that must be moved which is why there is a loop. They are [database file name], [database file name]-wal, and [database file name]-shm.

- (void)convertPublicFilesToPrivate
{
    NSFileManager *fileManager = [NSFileManager defaultManager];

    // Move the database files
    NSArray<NSString *> *dbFiles = [self filesByPrefix:@"<database file name>" isPublic: YES];
    for (NSString *dbFile in dbFiles) {
        NSString *fileName = [dbFile lastPathComponent];
        NSError *error;
        NSString *privatePath = [self getFullPrivatePath:fileName];
        [fileManager moveItemAtPath: dbFile toPath: privatePath error:&error];
        NSLog(@"Move database file %@ returned %@", fileName, error);
    }
}

-(NSString *)getPublicPath
{
    return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
}

- (NSString *)getPrivatePath
{
    return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"Database"];
}

-(NSString*)getFullPublicPath:(NSString *)fileName
{
    NSString *documentsDirectory = [self getPublicPath];
    if (fileName != nil) {
        return [documentsDirectory stringByAppendingPathComponent:fileName];
    } else {
        return documentsDirectory;
    }
}

-(NSString*)getFullPrivatePath:(NSString *)fileName
{
    NSString *documentsDirectory = [self getPrivatePath];
    if (fileName != nil) {
        return [documentsDirectory stringByAppendingPathComponent:fileName];
    } else {
        return documentsDirectory;
    }
}

- (NSArray*)filesByPrefix:(NSString*)prefix isPublic:(BOOL)isPublic
{
    NSString *documentsDirectory = isPublic ? [self getPublicPath] : [self getPrivatePath];
    NSArray<NSString *> *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:NULL];
    NSMutableArray *fileList = [[NSMutableArray alloc] initWithCapacity:1];
    for (NSString *fileName in directoryContent) {
        if (prefix.length == 0 || [fileName hasPrefix:prefix]) {
            [fileList addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
        }
    }
    return fileList;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.