I am having our app move it's readonly db to the App Support directory so that content updates can update it as well(over the wire updates, not app updates). The code below is in the app delegate to copy over the db, however on the first run NSFileManager in following attempts(during that run) to see if it is there or to load it does not see the copied file. However, it is copying it because if I close the app and restart it, everything works fine. I'm at a lose.
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSError *err = nil;
NSURL *ASD = [fm URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&err];
if (!err) {
NSURL* path = [NSURL URLWithString:DATABASE_NAME relativeToURL:ASD];
NSString *bundle = [[ NSBundle mainBundle] pathForResource:#"datafiles/data_main" ofType:#"sqlite"];
if ([fm fileExistsAtPath:[path path]]) {
if([DatabaseManager isBundledDBNewerThenInUse]){
NSLog(#"bundled db is newer");
[DatabaseManager close];
[fm removeItemAtURL:path error:&err];
if (err) {
NSLog(#"Error deleting old DB:%#",err);
}
else {
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
else
{
NSLog(#"db should have been copied over correctly");
}
}
}
}
else{
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
}
}
else
{
NSLog(#"Error in opening AS for DB copy:%#",err);
}
"[DatabaseManager isBundledDBNewerThenInUse]" returns YES if the db in the App Support directory either isn't there or has a version that is older then the one in the bundle. It opens the db in the App Support directory thus the [DatabaseManager close] before trying to remove it. I'm using FMDB in my DatabaseManager if that helps at all. But as I said after that initial load if you kill the app and go back into it it work perfectly. (On a database update from bundle you the db isn't updated on that first load either.) Any help would be great and if you need more info please just ask! Thanks!
From the bahaviour you describe (i.e. you restart the app, then it finds the copied database) it sounds a bit like you're maybe missing a crucial database open/close call somewhere.
I'd double-check where you make any calls to database open and close, and make sure it makes sense. Are you missing a database open call? Should there be a database open call in the above code, in the cases where the database gets copied from app bundle to the app support directory, after the copy has happened?
The answer was dispatch_async on the main thread and then double checking the cached data and reloading it if needed. Corrected code:
dispatch_async(dispatch_get_main_queue(),^(){
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSError *err = nil;
NSURL *ASD = [fm URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&err];
if (!err) {
NSURL* path = [NSURL URLWithString:DATABASE_NAME relativeToURL:ASD];
NSString *bundle = [[ NSBundle mainBundle] pathForResource:#"datafiles/data_main" ofType:#"sqlite"];
if ([fm fileExistsAtPath:[path path]]) {
if([DatabaseManager isBundledDBNewerThenInUse]){
NSLog(#"bundled db is newer");
[DatabaseManager close];
[fm removeItemAtURL:path error:&err];
if (err) {
NSLog(#"Error deleting old DB:%#",err);
}
else {
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
else
{
NSLog(#"db should have been copied over correctly");
}
}
}
}
else{
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
else
NSLog(#"DB Copied");
}
}
else
{
NSLog(#"Error in opening AS for DB copy:%#",err);
}
});
Related
I want to copy files located at /Library to the folder /User/Library/AddressBook/Sample/,
I used:
[[NSFileManager defaultManager] copyItemAtPath: #"/Library/MyFile.mp3"
toPath: #"/User/Library/AddressBook/Sample/MyFile.mp3"
error: &error];
But I encountered an error that says `Operation could not be completed. No such file or
directory`
I am working on a jailbroken iPhone.
The directory:
/User/Library/AddressBook/Sample/
does not exist on a phone normally. Have you added the Sample subdirectory, before trying to copy the mp3 file into it?
With the NSFileManager methods, I would also recommend using the error object to help you debug:
NSError* error;
[[NSFileManager defaultManager] copyItemAtPath:#"/Library//MyFile.mp3" toPath: #"/User/Library/AddressBook/Sample/MyFile.mp3" error:&error];
if (error != nil) {
NSLog(#"Error message is %#", [error localizedDescription]);
}
Also, it looks like there is a mistake in your spelling of copyItemAtPath, but probably it's only in your question, and not in your code? Anyway, please double-check.
And, you have a double-slash (//) in your path, too, but I don't think that's hurting you. Just take it out, and be careful when typing :)
Update
If you are just running this app normally, but on a jailbroken phone, your app won't have access to those directories. Apps installed normally, on a jailbroken phone, still are sandboxed. The jailbreak doesn't remove all the rules on the phone. If you install the app in /Applications, like true jailbreak apps are, then that code should work for you.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDirectory = [paths objectAtIndex:0];
NSLog(#"%#",libraryDirectory); // Library path
NSString *AddressBookPath = [libraryDirectory stringByAppendingPathComponent:#"AddressBook"];
if (![[NSFileManager defaultManager] fileExistsAtPath:AddressBookPath])
{
NSError* error;
// Create "AddressBook Dir"
if([[NSFileManager defaultManager] createDirectoryAtPath:AddressBookPath withIntermediateDirectories:NO attributes:nil error:&error])
{
// Create "Sample Dir"
NSString *samplePath = [AddressBookPath stringByAppendingPathComponent:#"Sample"];
if (![[NSFileManager defaultManager] fileExistsAtPath:AddressBookPath])
{
NSError* error;
if([[NSFileManager defaultManager] createDirectoryAtPath:AddressBookPath withIntermediateDirectories:NO attributes:nil error:&error])
{
// Copy Files Now
NSError* error;
NSString *fromPath = [libraryDirectory stringByAppendingPathComponent:#"MyFile.mp3"];
NSString *toPath = [samplePath stringByAppendingPathComponent:#"MyFile.mp3"];
[[NSFileManager defaultManager] copyItemAtPath:fromPath toPath:toPath error:&error];
if (error != nil)
{
NSLog(#"Error message is %#", [error localizedDescription]);
}
}
}
}
else
{
NSLog(#"[%#] ERROR: attempting to write create MyFolder directory", [self class]);
NSAssert( FALSE, #"Failed to create directory maybe out of disk space?");
}
}
I have the following code in my persistentStoreCoordinator. Without the iCloud part, it works fine. With iCloud, it stops on the addPersistentStoreWithType method. No error, it just stops on it and doesn't continue.
Any thoughts?
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [NSURL fileURLWithPath:STORE_PATH];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil];
NSLog(#"icloud: %#", iCloud);
if (iCloud) {
NSString *iCloudLogsDirectoryName = #"Logs";
NSURL *iCloudLogsPath = [NSURL fileURLWithPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]];
//Create logs directory, in case it doesn't exist
if([fileManager fileExistsAtPath:[iCloudLogsPath path]] == NO) {
NSLog(#"logs directory doesn't exist");
NSError *fileSystemError;
[fileManager createDirectoryAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]
withIntermediateDirectories:YES
attributes:nil
error:&fileSystemError];
if(fileSystemError != nil) {
NSLog(#"Error creating logs directory %#", fileSystemError);
}
}
NSString *iCloudEnabledAppID = #"app id removed from stackoverflow";
[options setObject:iCloudEnabledAppID forKey:NSPersistentStoreUbiquitousContentNameKey];
[options setObject:iCloudLogsPath forKey:NSPersistentStoreUbiquitousContentURLKey];
NSLog(#"logs path: %#", iCloudLogsPath);
}
[_persistentStoreCoordinator lock];
NSError *error;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
else {
NSLog(#"done adding persistent store");
}
[_persistentStoreCoordinator unlock];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingChanged" object:self userInfo:nil];
[self.delegate contextWasSetupInManager:self];
});
});
return _persistentStoreCoordinator;
}
That's common with iCloud. Apple advises you to put it on a background queue since that call may block. It blocks because that's where Core Data actually makes its connection to the iCloud server and starts downloading any new data that's available. The call doesn't continue until it's finished. If there's any significant amount of data to download, you may wait a while.
Sometimes it takes a while for no readily apparent reason. It's flaky like that.
Ultimately what you're seeing is pretty much the way things are with iCloud right now. File a bug or two and hope that things improve.
I just experienced this on one of my test devices (iPhone 5 running iOS 6.1). Doing a factory reset and then restoring from a current backup of the phone fixed it.
I'm not sure that this is the explanation, but there seems to be an issue with deleting an app's iCloud ubiquity container data through device Settings > iCloud > Storage & Backup. This might put the device into a state where some remnants of the ubiquity container remain and cannot be removed. Doing a full wipe and restore on the device seems to fix it.
I found an old post that looked like it was related this issue (http://stackmonthly.com/2011/11/core-data), and this entry on OpenRadar (http://openradar.appspot.com/10440734). I messed around with it for several hours until I wiped and restored the device, which worked perfectly: the addPersistentStoreWithType completed almost instantly.
Moving forward I'm going to try to clear the store using the nukeAndPave method in the WWDC 2012 sample application SharedCoreData (session 227) rather than manually deleting the store from Settings. Hopefully that will prevent this situation from recurring.
I'm not totally sure why these issues come up but I have had a lot of success in getting around problems of this nature by doing a reset of the phone's settings:
Settings > General > Reset > Reset All Settings
I have this code, which should be working perfectly, but I can't udnerstand why it isn't:
+(NSString *)writeImageToFile:(UIImage *)image {
NSData *fullImageData = UIImageJPEGRepresentation(image, 1.0f);
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents/Images/"];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDirectory = NO;
BOOL directoryExists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory];
if (directoryExists) {
NSLog(#"isDirectory: %d", isDirectory);
} else {
NSError *error = nil;
BOOL success = [fileManager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:&error];
if (!success) {
NSLog(#"Failed to create directory with error: %#", [error description]);
}
}
NSString *name = [NSString stringWithFormat:#"%#.jpg", [JEntry generateUuidString]];
NSString *filePath = [path stringByAppendingPathComponent:name];
NSError *error = nil;
BOOL success = [fullImageData writeToFile:filePath options:NSDataWritingAtomic error:&error];
if (!success) {
NSLog(#"Failed to write to file with error: %#", [error description]);
}
return filePath;
}
It passed the directoryExists without an error, but when it gets to writeToFile, it gives me this error:
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be completed. (Cocoa error 512.)" UserInfo=0x5634ee0 {NSFilePath=/var/mobile/Applications/5E25F369-9E05-4345-A0A2-381EDB3321B8/Documents/Images/18DAE0BD-6CB4-4244-8ED1-9031393F6DAC.jpg, NSUnderlyingError=0x5625010 "The operation couldn’t be completed. Not a directory"}
Any ideas why this might be?
I was able to reproduce your error when writing a file first in the path #"Documents/Images/", then trying to write the image using your code.
I think there are two possible scenarios for this:
1) You created that file by mistake at a previous execution of your app. This will be solved if you reset the simulator using the menu: iOS Simulator > Reset Contents and Settings, and uninstalling the app from your device: Long press > click on the x symbol.
2) There is some code somewhere else in your app that creates this file. If this is the case, you should find this code and remove it.
From FoundationErrors.h:
NSFileWriteUnknownError = 512
Try using withIntermediateDirectories:YES.
In my case a period '.' in the directory name (e.g. ~/Documents/someDir.dir/somefile) was the cause of the problem. I removed the offending character and the error disappeared.
Searched on here and got some vague answers, so I thought i'd rephrase the problem to get some clearer answers-
Right now I have an SQL Lite db that reads/parses information from a pre-formatted .txt file. When I open the app, there is a slight 'lag' as the iDevice parses the info, then gets fetched for the iDevice. I'm just wondering if there's any way to just 'save' all the information directly in the xCode so there's no lag/fetch time?
Thank you.
What you can do is pre-build your sqlite database and then include it as a resource in your application. As this database is inside your application bundle it will be read only on your device so you will need to make a copy of it in your application document area.
I have successfully used something like the following code in a production iPhone application
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolderPath = [searchPaths objectAtIndex: 0];
NSString* dbFilePath = [documentFolderPath stringByAppendingPathComponent: DATABASE_FILE_NAME];
if(![[NSFileManager defaultManager] fileExistsAtPath: dbFilePath]) {
// copy the template database into the right place
NSError* error = nil;
[[NSFileManager defaultManager] copyItemAtPath: [[NSBundle mainBundle] pathForResource: #"template" ofType: #"db"] toPath: dbFilePath error: &error];
if(error) {
#throw [NSException exceptionWithName: #"DB Error" reason: [error localizedDescription] userInfo: nil];
}
}
int dbrc; // database return code
dbrc = sqlite3_open ([dbFilePath UTF8String], &db);
if(IS_SQL_ERROR(dbrc)) {
#throw [NSException exceptionWithName: #"DB Error" reason: NSLocalizedString(#"Could not open database", #"Database open failed") userInfo: nil];
}
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];