I used the Master-Detail-Application example at Xcode 4.3.3 to develope a data viewer. The app is downloading several files and append each file to a cell at the tableview. everything works fine. but if the user starts the download again, I need to remove all the previous data from the database and the tableview.
I ended up with the following steps:
remove the old store from coordinator
remove the old sqlite database file
copy the default ( empty ) sqlite db file from the bundle dir into the doc dir
update store coordinator
update tableview.
but now the problem is, that if I do so, the new data is not written into sqlite file.
Please, can someone check my code. The following code is located at the MasterViewController.m ! I working on this since several days and I am totally stuck !!!
Thank you!
NSLog(#"Delete existing database file and copy default from bundle dir!");
NSFileManager* fileManager = [[NSFileManager alloc] init];
fileManager = [[NSFileManager alloc] init];
NSError *error = nil;
//get the documents directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
// create database path documents dir
NSString *databasepath = [documentsDirectory stringByAppendingPathComponent:#"MyDatabase.sqlite"];
NSLog(#"Destination Database Path = %#", databasepath);
// create database path bundle dir
NSString *resourceDBFolderPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"MyDatabase.sqlite"];
NSLog(#"Recource Database Path = %#", resourceDBFolderPath);
// remove old store from coordinator
error = nil;
NSManagedObjectModel *managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MyDatabase" withExtension:#"momd"];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]];
NSURL *applicationDocumentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [applicationDocumentsDirectory URLByAppendingPathComponent:#"MyDatabase.sqlite"];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
if (error != nil){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
NSPersistentStore *persistentStore = [[persistentStoreCoordinator persistentStores] objectAtIndex:0];
[persistentStoreCoordinator removePersistentStore:persistentStore error:&error];
if (error)
if (error != nil){
NSLog(#"error removing persistent store %# %#", error, [error userInfo]);
}
// Remove old database file
[[NSFileManager defaultManager] removeItemAtPath: [documentsDirectory stringByAppendingPathComponent: #"MyDatabase.sqlite"] error: &error];
if (error != nil){
NSLog(#"delete Database status: %#", [error localizedDescription]);
}
// copy default empty database file into documents dir
[fileManager copyItemAtPath: resourceDBFolderPath toPath: databasepath
error: &error];
if (error != nil){
NSLog(#"copy default Database status: %#", [error localizedDescription]);
}
// then update storecoordinator
// add clean store file back by adding a new persistent store with the same store URL
if (![persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType
configuration: nil
URL: storeURL
options: nil
error: &error]) {
NSLog(#"failed to add db file, error %#, %#", error, [error userInfo]);
}
NSLog(#"Database replacement end!");
Related
I want to export my whole database from my app via email and on other device when i download that database file, it can be open directly in app and replace my existing database file with that database file.
I have exported my database file(.sqlite) via mail and i want to import that file from mail into my app. I have also implement the functionality which the file from mail can directly open in my app. But i want to import that database file directly from mail. So how can i do that?
Or Can i replace that file with my app database?
I think you are looking for below method,
- (BOOL)replaceItemAtURL:(NSURL *)originalItemURL withItemAtURL:(NSURL *)newItemURL backupItemName:(nullable NSString *)backupItemName options:(NSFileManagerItemReplacementOptions)options resultingItemURL:(NSURL * _Nullable * _Nullable)resultingURL error:(NSError **)error NS_AVAILABLE(10_6, 4_0);
you can call this method with NSFileManager's instance or object. You can pass source and destination path as fileUrl ([NSURL fileURLWithPath:#"path string"];) and it will replace data at destination path!
Couldn't understand your problem completely but here is a code that can give any clue how to replace your existing file in documents directory:
//Reference the path to the documents directory.
NSString *documentDir =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
//Get the path of old file.
NSString *filePath = [documentDir stringByAppendingPathComponent:#"File.sqlite"];
if([[NSFileManager defaultManager] fileExistsAtPath: filePath])
{
[fileManager removeItemAtPath:filePath error:&error] //Delete old file
}
//Copy New File.sqlite from Resource Bundle.
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"File" ofType:#"sqlite"];
[fileManager copyItemAtPath:resourcePath toPath:filePath error:&error];
This link is helps to all your questions in SQLITE,
Like Copy Sqlite File in to Docs directory,
and also Select queries, Insert, Delete, Update varius types of recotds etc.. etc...
Checkout + (NSString*) copyDBFile method in this link for ur requirement
SQLite Programing : Initial Steps - By iOSCodeGUIDE
//Commen method for DAtaBase Copy
+ (NSString*) copyDBFile
{
NSString *docsDirectoryPath;
NSArray *dirPaths;
NSString *databasePath;
NSFileManager *fileManager = [NSFileManager defaultManager];
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDirectoryPath = [dirPaths objectAtIndex:0];
// Build the path to the database file
databasePath = [[NSString alloc] initWithString: [docsDirectoryPath stringByAppendingPathComponent: #"SqliteTable.db"]];
BOOL isSuccess = [fileManager fileExistsAtPath: databasePath ];
if (!isSuccess)
{
NSError *error;
NSString *defaultDBPath=[[NSBundle mainBundle]pathForResource:#"SqliteTable" ofType:#"db"];
// NSLog(#"path :%#", defaultDBPath);
isSuccess = [fileManager copyItemAtPath:defaultDBPath toPath:databasePath error:&error];
if (! isSuccess)
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
NSLog(#"%#",databasePath);
return databasePath;
}
You should use iOS share extensions to first import the database file from mail app. Here is a tutorial that you can refer to.
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil)
{
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"yourSqlite.sqlite"];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"yourSqlite.sqlite" ofType:nil];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSError *error = nil;
if (![[NSFileManager defaultManager] fileExistsAtPath:[documentsDirectory stringByAppendingPathComponent:#"yourSqlite.sqlite"] ]) {
if([[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:[documentsDirectory stringByAppendingPathComponent:#"yourSqlite.sqlite"] error:&error]){
NSLog(#"Default file successfully copied over.");
} else {
NSLog(#"Error description-%# \n", [error localizedDescription]);
NSLog(#"Error reason-%#", [error localizedFailureReason]);
}
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
I have file in my App bundle on iOS. How can I move it to be in Document folder?
Can this be done through some configuration or I need to make custom code for it?
By custom code I mean:
check is this first run of app:
if so, move files to the Document folder.
Please check out NSFileManager which allows you to copy files within the file system as required. You should be aware that Apple will reject your App if the copied file is not flagged to NOT backup to iCloud.
I use the following methods to copy a sqlite database from the bundle to a folder; it should work for your purpose with minor modification.
+ (void)copyBundleFileToStoresDirectory:(NSString *)filename
{
NSError *error;
NSURL *fileURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:filename ofType:nil]];
NSURL *pathURL = [SVOFileSystemMethods documentsSubdirectory:#"Stores" skipBackup:YES];
if (fileURL)
{
if([[NSFileManager defaultManager] copyItemAtURL:fileURL toURL:pathURL error:&error])
{
// NSLog(#"File successfully copied");
}
else
{
[[[UIAlertView alloc]initWithTitle:NSLocalizedString(#"error", nil) message: NSLocalizedString(#"Failed to copy database from bundle.", nil)
delegate:nil cancelButtonTitle:NSLocalizedString(#"OK", nil) otherButtonTitles:nil] show];
NSLog(#"Error description-%# \n", [error localizedDescription]);
NSLog(#"Error reason-%#", [error localizedFailureReason]);
}
}
}
+ (NSURL *) documentsSubdirectory:(NSString *)subdirectoryName skipBackup:(BOOL) skipBackup
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:subdirectoryName]; //untested change here -- was literal #"/Stores"
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
{
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:YES attributes:nil error:&error]; //Create folder
NSURL *finalURL = [NSURL fileURLWithPath:dataPath];
if (skipBackup)
[SVOFileSystemMethods addSkipBackupAttributeToItemAtURL:finalURL]; // flag to exclude from backups.
return finalURL;
}
else
return [NSURL fileURLWithPath:dataPath]; // already existed
}
//
// flags URL to exclude from backup
//
+ (BOOL) addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success)
{
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
From what I understand, you can't "move" files from the app bundle to the app directory, since that requires deleting them from the bundle. You'll get an access denied, assuming your code is set up right.
You can "copy" files however, since that it simply a read.
In my case, I had a whole bunch of files that I wanted to move to the app's Library directory but was unable. Additionally, the copy operation was far too costly because of the quantity of files.
In this case, I changed the app logic to check for "fresh" information first (that I would save to the Library folder), & if it wasn't there then fall back to the App bundle to look for an old/original copy of the file.
This is the current initialisation code in my app, based on cs193p by Paul Hegarty:
UIManagedDocument *database = nil;
if (!database) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"stdDatabase"];
database = [[UIManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
database.persistentStoreOptions = options;
}
if (![[NSFileManager defaultManager] fileExistsAtPath:[database.fileURL path]]){
[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
completionBlock(database);
}];
} else if (database.documentState == UIDocumentStateClosed){
[database openWithCompletionHandler:^(BOOL success){
completionBlock(database);
}];
} else if (database.documentState == UIDocumentStateNormal) {
completionBlock(database);
}
This is the new initialisation code I want to use, based on the "Core Data" book by Marcus Zarra:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Corrida_de_Leitos" withExtension:#"momd"];
ZAssert(modelURL, #"Failed to find model URL");
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
ZAssert(mom, #"Failed to initialize model");
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
ZAssert(psc, #"Failed to initialize persistent store coordinator");
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setPersistentStoreCoordinator:psc];
[self setManagedObjectContext:moc];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *storeURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
storeURL = [storeURL URLByAppendingPathComponent:#"stdDatabase"];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
NSError *error = nil;
NSPersistentStoreCoordinator *coordinator = [moc persistentStoreCoordinator];
NSPersistentStore *store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
if (!store) {
NSLog(#"Error adding persistent store to coordinator %#\nUSERINFO:\n%#", [error localizedDescription], [error userInfo]);
}
dispatch_sync(dispatch_get_main_queue(), ^{
[self contextInitialized];
});
});
This is the error I get when adding the store to the coordinator:
'Error adding persistent store to coordinator The operation couldn’t be completed. (Cocoa error 256.)
USERINFO:
{
NSSQLiteErrorDomain = 14;
NSUnderlyingException = "unable to open database file";
}'
How do I fix the new code so it is able to open the old database file?
The UIManagedDocument will have created this sqlite file inside the UIManagedDocument package (a directory) so you need to get the URL to the original sqlite file and use this as the URL in the new version of the app, if necessary use the NSFileManager to move the file to another location, but that's not really necessary. See example directory structure from UIManagedDocument, one for local and one for iCloud synced store. If its not iOS7 then check because the structure/names may be different.
In theory you can get the actual store file name from UIManagedDocument.persistentStoreName and simply append that to UIManagedDocument.fileURL - but in practice this omits the StoreContent subdirectory I think.
Usually UIManagedDocument would have created the file at "stdDatabase/StoreContent/persistentStore". But to be sure run the original version of the app in the simulator and then check exactly what path is used to create the store file.
The actual sqlite file should work fine as long as you open using the same options.
The problem is that UIManagedDocument creates the stack for you and saves your database file with a format not compatible with a SQLite file, if you want to use a SQLite file you first have to migrate the store (file), the NSPersistentStoreCoordinator have a method called migratePersistentStore:toURL:options:withType:error: which can do this migration.
I've found lots of explanations and help here on stack but so far, no luck.
Pretty much, my myapp.sqlite (that I pre-populated) works fine on the simulator but when I run it on the iPad, it's empty.
So after trying different things, this is the closest I got :
I copy the sqlite db into the Bundle but I move it to the Library folder.
On my AppDelegate I do this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"Starting to save the DB to another location");
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
NSString *targetPath = [libraryPath stringByAppendingPathComponent:#"myDB.sqlite"];
if (![[NSFileManager defaultManager] fileExistsAtPath:targetPath]) {
// database doesn't exist in your library path... copy it from the bundle
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"myDB" ofType:#"sqlite"];
NSError *error = nil;
if (![[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:targetPath error:&error]) {
NSLog(#"Error: %#", error);
}
}
return YES;
}
then on the PersistentStoreCoordinator, I do this
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
NSString *targetPath = [libraryPath stringByAppendingPathComponent:#"myDB.sqlite"];
// NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"naccApp.sqlite"];
// NSURL *storeURLLocal = [[NSBundle mainBundle] URLForResource:#"myDB" withExtension:#"sqlite"];
NSURL *storeURL = [NSURL URLWithString:targetPath];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
When I try to use the DB, (fetchRequest), I get an error :
CoreData SQL stores only support file URLs (got /var/mobile/Applications/2EB2AADD-DF9D-475F-A05E-BB138502471F/Library/myDB.sqlite).
The message is clear but I've tried almost all the help here and still nothing.
Needless to say, I'm new to Core Data, so please forgive the ignorance.
Oh, I'm using xCode 5.
Thx
NSURL *storeURL = [NSURL URLWithString:targetPath];
Should be:
NSURL *storeURL = [NSURL fileURLWithPath:targetPath];
so that you generate a file URL instead of a web URL.
I've been using Core Data in my app, and suddenly I got en error message as follows:
Attempt to add read-only file at path file://localhost/var/mobile/Applications/xxx-xxx-xxx../.app/MyModel.sqlite read/write. Adding it read-only instead. This will be a hard error in the future; you must specify the NSReadOnlyPersistentStoreOption.
The error occurred in the method below:
-(NSPersistentStoreCoordinator*)persistentStoreCoordinator{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSString* path= [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"MyModel.sqlite"];
NSURL* storeURL = [[NSURL alloc] initFileURLWithPath:path];
NSError* error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Before today, I have never seen this error message before. Can any one tell me what went wrong? Thank you for your time.
The answer lies in
[[NSBundle mainBundle] resourcePath]
The mainBundle resourcePath is where to load data contained in your application IPA file, it is and will always be readonly, you need to create the sqllite file in the application document folder
you can replace it by
NSURL *storeURL = [[self applicationPrivateDocumentFolderURL] URLByAppendingPathComponent:#"MyModel.sqlite"];
- (NSURL *)applicationPrivateDocumentFolderURL
{
if (!m_privateDocumentFolderURL) {
NSString *applicationPrivateDocumentFolderPath = [[NSFileManager defaultManager] applicationSupportDirectory];
m_privateDocumentFolderURL = [[NSURL alloc] initFileURLWithPath:applicationPrivateDocumentFolderPath];
}
return [[m_privateDocumentFolderURL copy] autorelease];
}
For anyone landing here because they're trying to ship a read-only database with their app, here's the code to create the store
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #(true),
NSInferMappingModelAutomaticallyOption: #(true),
NSReadOnlyPersistentStoreOption: #(true))
// The NSReadOnlyPersistentStoreOption is the important one here
NSPersistentStore * seedStore =[coordinator
addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Seed"
URL:[NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"Seed" ofType:#".sqlite"]]
options:options
error:&error];
Also, when you create your database, you'll probably have your journal mode set to WAL, which is not compatible with a read-only db. You have 2 choices:
add NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"}} to your options
or open the seed database with a tool such as Liya and run "PRAGMA wal_checkpoint(RESTART)" followed by "PRAGMA journal_mode = DELETE