I'm trying to write two applications (iphone and desktop) to achieve what's been described in the following link:
core-data-is-it-possible-to-build-a-desktop-app-to-create-the-data-model-for-an
Ok. So I've created a very simple desktop app the has a single entity named Client with a string attribute field called name. I've also generated the corresponding Model class.
I've run the app added a couple of client names to the list and saved the file (as Testing.sqlite).
Now in my equivalent iphone app I'm attempting to load the file. I've generated the app initially using one of the application templates and included Core Data. NB: I've mirrored the Client entity and generated the corresponding Model class.
I've gone into my "application delegate" class and amended the persistentStoreCoordinator method to reference my "Testing.sqlite" file i.e.
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Testing.sqlite"]];
I've also copied the saved desktop app file into the expected location i.e.
~/Library/Application Support/IPhone Simulator/User/... etc.
So now in theory at least each of the two apps should be the same.
However when I'm attempting to load the data from the it always seems to be empty. My code looks a little like this:
// fetch the delegate.
TestingAppDelegate *app = (TestingAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = [app managedObjectContext];
// construct the request.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Client" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// execute the request.
NSError *error;
NSArray *results = [managedObjectContext executeFetchRequest:request error:&error];
if (results == nil) {
// Handle the error.
NSLog(#"No data loaded");
}
NSLog(#"Returned: %#", results);
// finally release
[results release];
[request release];
I can't seem to figure out what's going wrong. Any tips or suggestions would be totally appreciated.
When I've looked at the instance of the persistanceStoreCoordinator, managedObjectContext, resulting array (NSArray) whilst debugging I can see that it seems to contain 0 records for all of these. So I'm confused.
NB: The Testing.sqlite file contains entries.
Thanks in advance,
Matt
Simply copying the sqlite database file will not work when using Core Data.
You need to fully replicate the persistent store you created in your desktop application.
However, this may be a problem related to the fact that Core Data is not seeing your database file even though you copied it. Try the following:
1) add your database file to your project in the resources group
2) use this method to actually copy your database file in place
- (NSString *) initialize_db {
NSString *DATABASE_RESOURCE_NAME = #"testing";
NSString *DATABASE_RESOURCE_TYPE = #"sqlite";
NSString *DATABASE_FILE_NAME = #"testing.sqlite";
// copy the database from the bundle if necessary
// look to see if DB is in known location (~/Documents/$DATABASE_FILE_NAME)
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolderPath = [searchPaths objectAtIndex: 0];
NSString *dbFilePath = [documentFolderPath stringByAppendingPathComponent: DATABASE_FILE_NAME];
[dbFilePath retain];
if (! [[NSFileManager defaultManager] fileExistsAtPath: dbFilePath]) {
// didn't find db, need to copy
NSString *backupDbPath = [[NSBundle mainBundle]
pathForResource:DATABASE_RESOURCE_NAME
ofType:DATABASE_RESOURCE_TYPE];
if (backupDbPath == nil) {
// couldn't find backup db to copy, bail
NSLog (#"couldn't init db");
return NULL;
} else {
BOOL copiedBackupDb = [[NSFileManager defaultManager]
copyItemAtPath:backupDbPath
toPath:dbFilePath
error:nil];
if (! copiedBackupDb) {
// copying backup db failed, bail
NSLog (#"couldn't init db");
return NULL;
}
}
}
return dbFilePath;
}
Once you have copied the database file, you have its file path returned, and you use it to actually open the database.
Related
I want do download a PDF to my documents directory.
I do it like this:
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.test.com/test.pdf"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadDestinationPath:[self documentsDirectory]];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSLog(#"requestFinished");
NSError *error;
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self documentsDirectory] error:&error];
if (array == nil) {
NSLog(#"array == nil");
}
NSLog(#"Array count: %d", [array count]);
}
- (NSString *)documentsDirectory {
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
return [paths objectAtIndex:0];
}
But my array is always empty, I don't know why...
It works now, made a new project and it works.
A bit weird.
This is happening because the code as written is actually replacing the Documents directory with your file instead of putting it in the Documents directory.
If you look in the Simulator directory in the Finder (~/Library/Application Support/iPhone Simulator/4.3/Applications/...), you'll see something like this:
Notice how the "Kind" of the Documents directory is no longer a "Folder" but a "Document" after your download completes.
If you append .pdf to the Documents file in the Finder, it should be your downloaded PDF. I tried your code and that's what it did for me. You are overwriting the Documents directory with your download.
Your code will work if you add an explicit file name to the download path. So something like this:
NSString *filePath = [[[self documentsDirectory] stringByAppendingPathComponent:#"localFile"] stringByAppendingPathExtension:#"pdf"]];
[request setDownloadDestinationPath:filePath];
If you check out the example in the HTTPRequest documentation, you'll see that a file path is specified, and not just a download directory.
As a side note, you should also implement the - (void)requestFailed:(ASIHTTPRequest *)request delegate method and check/print out the NSError when the contentsOfDirectoryAtPath: array is nil (in your requestFinished: code). That would have made it easier for you to track down the issue.
NOTE: Make sure to delete the app from your simulator before you fix your code, or you'll get an error when you try to write to that Documents directory, as it's now no longer a directory!
I'm not sure where your dir variable is coming from but it looks like this is where your problem lies.
You can return the array correctly like so:
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self documentsDirectory] error:&error];
From what i can tell your logging the array correctly before you assign it to *array but then you are using the dir variable. There is also no need to alloc a FileManger object as you can simply use [NSFileManger defaultManager] as you have done when assigning *array.
Also, when you say your array is empty, do you mean nil and your array == nil log is firing, or that you are using [array count]; and it is returning 0?
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
iPhone - file properties
Hi all. i m creating an application which makes the iphone work as a pendrive for easy file sharing purpose.
In the first stage, i have some files(png, pdf, jpg, zip) in a directory and i made them display in the tableview in the form of mutable array without the extensions of each file.
In the second stage i have a detailedViewController which then displays the detailed view of the files like
file size
file type
if it is a image, it should open in imageView
if it is a song, it should play it
So i need to retrieve the properties like filePath, fileType, fileSize.. of each files. Now i got stuck in getting those properties like fileSize and fileType... Please help me proceed with a sample source code.
Here is my code.
- (void)listFiles {
NSFileManager *fm =[NSFileManager defaultManager];
NSError *error = nil;
NSString *parentDirectory = #"/Users/akilan/Documents";
NSArray *paths = [fm contentsOfDirectoryAtPath:parentDirectory error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
error = nil;
}
directoryContent = [[NSMutableArray alloc] init];
for (NSString *path in paths){
documentsDirectory = [[path lastPathComponent] stringByDeletingPathExtension];
NSLog(#"%#", documentsDirectory);
[directoryContent addObject:documentsDirectory];
}
Thanks in advance..
you should use the method attributesOfItemAtPath:error: of your filemanager instance.
Have a look at the documentation.
So first, this question helped a lot with getting on the right track toward working core data versioning. So I added a new version for my model, and now I'm trying to get the automatic migration working, but I have a problem. I can't remember what my old version looked like! I'm trying to run the app on my phone, but I've been using the simulator for a while and made a few changes to the schema. The version on the phone is from quite a while ago. So each time I try to modify the old version to what I think is on the phone, but I still get the "can't find model for source store" error. I'm guessing it's because I got the old schema wrong.
Is there any way for me to figure out what the schema looks like on the phone? Barring that, how could I just wipe the sqlite store off the phone so I can start over from version 1?
I was tearing my hair out over the "Can't find model for source store" error for a whole day. Here is an elaboration of learner2010's answer for googlers:
Your sqlite database's model hash MUST match one of the mom or momd created by your xcdatamodel when you build your app. You can see the hashes in the momd's VersionInfo.plist in the built app's bundle. See below for code to find your database's model hash.
So if you change your xcdatamodel instead of creating a new version under Xcode->Editor->Add Model Version... then your model's hash will be different, and addPersistentStoreWithType won't be able to use your old database, which used the old model. That's what causes the "Can't find model for source store" error.
To make matters worse, the sqlite database is stored in something like "/private/var/mobile/Library/Mobile Documents/YOU_APP_ID/Data.nosync/YOUR_DB.sqlite" and this can stick around even if you remove the app from the device and reinstall it! So you will think there's something wrong with your code, when in reality you just have a stale database that needs to be deleted. Usually this is during debugging so there's no real data in it anyway.
So the proper workflow to allow migrations in the future is to make your model, run your app to build the database, and then create NEW VERSIONS of the model anytime you need to make changes. Everything will "just work" if you keep the changes minor. Then, when you're ready to release your app, select the final model and delete the rest. Then delete your database from "/private/var/mobile/Library/Mobile Documents". Then on future releases, include all the models from previous releases along with your newest model (if it's changed) and users will be able to migrate each time.
Here is my code so far. The important line is:
[fileManager removeItemAtPath:iCloudData error:&error];
But it's only to be used during debugging to delete your old database. Here is the production code in AppDelegate.m:
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil)
{
return __managedObjectModel;
}
//NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
//__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//NSArray *testArray = [[NSBundle mainBundle] URLsForResourcesWithExtension:#"momd"subdirectory:nil];
NSString *path = [[NSBundle mainBundle] pathForResource:#"Model" ofType:#"momd"];
if( !path ) path = [[NSBundle mainBundle] pathForResource:#"Model" ofType:#"mom"];
NSURL *modelURL = [NSURL fileURLWithPath:path];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//__managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return __managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if((__persistentStoreCoordinator != nil)) {
return __persistentStoreCoordinator;
}
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator;
// Set up iCloud in another thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// ** Note: if you adapt this code for your own use, you MUST change this variable:
NSString *iCloudEnabledAppID = #"RW6RS7HS69.com.zsculpt.soaktest";
// ** Note: if you adapt this code for your own use, you should change this variable:
NSString *dataFileName = #"mydailysoak.sqlite";
// ** Note: For basic usage you shouldn't need to change anything else
NSString *iCloudDataDirectoryName = #"Data.nosync";
NSString *iCloudLogsDirectoryName = #"Logs";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localStore = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:dataFileName];
NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil];
if (iCloud) {
NSLog(#"iCloud is working");
NSURL *iCloudLogsPath = [NSURL fileURLWithPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]];
NSLog(#"iCloudEnabledAppID = %#",iCloudEnabledAppID);
NSLog(#"dataFileName = %#", dataFileName);
NSLog(#"iCloudDataDirectoryName = %#", iCloudDataDirectoryName);
NSLog(#"iCloudLogsDirectoryName = %#", iCloudLogsDirectoryName);
NSLog(#"iCloud = %#", iCloud);
NSLog(#"iCloudLogsPath = %#", iCloudLogsPath);
if([fileManager fileExistsAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]] == NO) {
NSError *fileSystemError;
[fileManager createDirectoryAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]
withIntermediateDirectories:YES
attributes:nil
error:&fileSystemError];
if(fileSystemError != nil) {
NSLog(#"Error creating database directory %#", fileSystemError);
}
}
NSString *iCloudData = [[[iCloud path]
stringByAppendingPathComponent:iCloudDataDirectoryName]
stringByAppendingPathComponent:dataFileName];
NSLog(#"iCloudData = %#", iCloudData);
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[options setObject:iCloudEnabledAppID forKey:NSPersistentStoreUbiquitousContentNameKey];
[options setObject:iCloudLogsPath forKey:NSPersistentStoreUbiquitousContentURLKey];
[psc lock];
NSError *error;
[psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[NSURL fileURLWithPath:iCloudData]
options:options
error:&error];
if( error )
{
NSLog(#"Error adding persistent store %#, %#", error, [error userInfo]);
// comment in this line while debugging if get "Can't find model for source store" error in addPersistentStoreWithType.
// it means the sqlite database doesn't match the new model and needs to be created from scratch.
// this happens if you change the xcdatamodel instead of creating a new one under Xcode->Editor->Add Model Version...
// CoreData can only automatically migrate if there is a new model version (it can't migrate if the model simply changes, because it can't see the difference between the two models).
// be sure to back up the database if needed, because all data will be lost.
//[fileManager removeItemAtPath:iCloudData error:&error];
/*// this is another way to verify the hashes for the database's model to make sure they match one of the entries in the momd directory's VersionInfo.plist
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:[NSURL fileURLWithPath:iCloudData] error:&error];
if( !sourceMetadata )
NSLog(#"sourceMetadata is nil");
else
NSLog(#"sourceMetadata is %#", sourceMetadata);*/
}
[psc unlock];
}
else {
NSLog(#"iCloud is NOT working - using a local store");
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[psc lock];
NSError *error;
[psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:localStore
options:options
error:nil];
if( error )
NSLog(#"Error adding persistent store %#, %#", error, [error userInfo]);
[psc unlock];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingChanged" object:self userInfo:nil];
});
});
return __persistentStoreCoordinator;
}
The error message means it cannot find the .mom compiled model file for the existing store. Core Data is looking for the exact .mom version that configured the store. The .mom file tells tells Core Data how to map the serialized data in the file into objects. Without that model file, it does not know how to migrate the store to the new model because it doesn't know what data goes with each entity or entity property.
I've only seen this once and IIRC the cause was that the new .mom file had the exact same name and location as the old one. When the app was updated, the old .mom file was overwritten.
Try changing the name of the new model file and see if that helps. If not, we will probably need more detail about what you are doing.
I had the same problem too. I solved it by deleting my old database and building the project again. I am not sure that this is the right way to do. Apparently it is pointing to the wrong database file. In my case, I was creating a simple project using core data and was trying out migration. When I created a new model with an additional column and built my project, there were 2 files - coreData.sqlite and coreData ~.sqlite... So what I feel is that, the wrong database is getting pointed to and hence the error. When I deleted the database and built the project again, it worked perfectly for me.
If you do delete the database, it would be best to save a copy of the database in another location so that you do not lose it.
use core data migrations for updating old database with new schema with losing your data.
https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreDataVersioning/Articles/vmLightweightMigration.html
I am saving Images in NSCachesDirectory in a App. At the end of app execution i would like to clear all temporary cache. Is there a way to force clear all cache on application exit. I do realise the local folder keeps cache for 3 days..but my requirement is to force clear the Cache. Thx
This code should do the trick, just substitute the name of your cache directory...
NSString *cacheDirectoryName = [self getCacheDirectoryName];
NSArray *items = [fileManager directoryContentsAtPath:cacheDirectoryName];
for (NSString *item in items)
{
NSString *path = [cacheDirectoryName stringByAppendingPathComponent:item];
NSError *error = nil;
[fileManager removeItemAtPath:path error:&error];
[error release];
}
Then call the code in your AppDelegate applicationWillTerminate method.
In my app I sometimes need to rebuild and repopulate database file. SQLite databse is created and managed by CoreData stack.
What I'm trying to do is drop the file and then simply recreate persistentStoreCoordinator object.
It works under simulator but not on device, where I'm getting such an error:
NSFilePath = "/var/mobile/Applications/936C6CC7-423A-46F4-ADC0-7184EAB0CADD/Documents/MYDB.sqlite";
NSUnderlyingException = I/O error for database at /var/mobile/Applications/936C6CC7-423A-46F4-ADC0-7184EAB0CADD/Documents/MYDB.sqlite. SQLite error code:1, 'table ZXXXX already exists';
I cannot find the cause of this in any way. It indicates two different problems - Cocoa error 256 indicates that file does not exist or is not readable. But file IS created after creating persistenStoreCoordinator, although it's empty, but after executing some queries it disappears.
Second message indicating attempt to create alredy existing table is quite strange in that case.
I'm quite confused and cannot get the point what's going on here. My code looks like this:
NSString *path = [[WLLocalService dataStorePath] relativePath];
NSError *error = nil;
WLLOG(#"About to remove file %#", path);
[[NSFileManager defaultManager] removeItemAtPath: path error: &error];
if (error != nil) {
WLLOG(#"Error removing the DB: %#", error);
}
[self persistentStoreCoordinator];
WLLOG(#"Rebuild DB result %d", [[NSFileManager defaultManager] fileExistsAtPath: path]);
After this code is exectued, DB file exists but is empty. When then first query (and all following) is executed, it gives me the error above and file disappears.
Does anybody has an idea what's wrong with it?
Big thanks for pointing me the right way!
The Core Data stack does not like you removing the file under it. If you are wanting to delete the file you should tear down the stack, delete the file and then reconstruct the stack. That will eliminate the issue.
Part of the problem is that the stack keeps a cache of the data that is in the file. When you remove the file you don't have a way to clear that cache and you are then putting Core Data into an unknown and unstable state.
You can try telling the NSPersistentStoreCoordinator you are removing the file with a call to -removePersistentStore:error: and then adding the new store with a call to -addPersistentStoreWithType:configuration:URL:options:error:. I am doing that currently in ZSync and it works just fine.
I use the following method -resetApplicationModel in my app delegate and it works fine for me.
You may not need the kApplicationIsFirstTimeRunKey user default, but I use it to test whether to populate the Core Data store with default settings in a custom method called -setupModelDefaults, which I also call from -applicationDidFinishLaunching: if the first-time run flag is YES.
- (BOOL) resetApplicationModel {
// ----------------------
// This method removes all traces of the Core Data store and then resets the application defaults
// ----------------------
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:kApplicationIsFirstTimeRunKey];
NSLog(#"Turned ON the first-time run flag...");
NSError *_error = nil;
NSURL *_storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"MyAppSQLStore.sqlite"]];
NSPersistentStore *_store = [persistentStoreCoordinator persistentStoreForURL:_storeURL];
//
// Remove the SQL store and the file associated with it
//
if ([persistentStoreCoordinator removePersistentStore:_store error:&_error]) {
[[NSFileManager defaultManager] removeItemAtPath:_storeURL.path error:&_error];
}
if (_error) {
NSLog(#"Failed to remove persistent store: %#", [_error localizedDescription]);
NSArray *_detailedErrors = [[_error userInfo] objectForKey:NSDetailedErrorsKey];
if (_detailedErrors != nil && [_detailedErrors count] > 0) {
for (NSError *_detailedError in _detailedErrors) {
NSLog(#" DetailedError: %#", [_detailedError userInfo]);
}
}
else {
NSLog(#" %#", [_error userInfo]);
}
return NO;
}
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
[managedObjectContext release], managedObjectContext = nil;
//
// Rebuild the application's managed object context
//
[self managedObjectContext];
//
// Repopulate Core Data defaults
//
[self setupModelDefaults];
return YES;
}
You can keep a "clean" copy of your sqlite database as part of the application bundle, then just copy over the version in the documents directory whenever you'd like to refresh the database.
Here's some code from an App that does something similar (although this version will not copy over and existing db):
// Check for the existence of the seed database
// Get the path to the documents directory and append the databaseName
NSString* databasePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: kDatabaseName];
NSFileManager* fileManager = [NSFileManager defaultManager];
if ( ![fileManager fileExistsAtPath: databasePath] )
{
NSString* databasePathFromApp = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent: kDatabaseName];
[fileManager copyItemAtPath: databasePathFromApp
toPath: databasePath
error: nil];
}
[fileManager release];