Can't see DataBase in device after copied in bundle - xcode

I finished my App for a distance calculation. I'll update my database before to copy it.
I did some tests and when I'm moving my database (DataBase.sqlite) into "Copy Bundle Ressources" and running App on my device, I can't see my data.
Looks like the device is using his own database with same name (DataBase.sqlite).
(DataBase will update a TableView during launch)
I created a AddButton to see if I can update my DataBase from an iPhone and it works.
I mean is I closed the App an re launched it, I can see the created data from the AddButton.
Have you any idea?

When you include the database into your bundle via Xcode's "Copy Bundle Resources", it's part of the bundle, i.e. you get the path via:
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"DataBase" ofType:#"sqlite"];
If you want to copy that to your Documents folder so that you can use it, you get the Documents path via:
NSString *documentsFolder = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *documentsPath = [documentsFolder stringByAppendingPathComponent:#"DataBase.sqlite"];
So, you might check for existence in Documents, and if not there, copy from bundle to Documents:
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:documentsPath]) {
NSError *error = nil;
BOOL success = [fileManager copyItemAtPath:bundlePath toPath:documentsPath error:&error];
NSAssert(success, #"%s: copyItemAtPath failed: %#", __FUNCTION__, error);
}
You can now open the database in the Documents folder (e.g. using documentsPath).
Now, if you ever attempted to open a database in Documents without first copying from the bundle, the standard sqlite3_open will create a blank database. So, you probably want to delete the app from the device/simulator and then reinstall, to get rid of that blank database in Documents (otherwise the above logic, testing for the existence of the database in Documents, will result in false positive).

Related

migratePersistentStore: causes duplicate data when destination URL exists

Just encountered a scenario that I thought I'd share. When using NSPersistentStoreCoordinator migratePersistentStore: with a destination URL that already exists, the resulting data is merged. My scenario is that I'm creating dated backups whenever my app closes and it's possible that multiple launches in the same day will backup so the same file.
I solved the issue by using NSFileManager fileExistsAtPath: and removeItemAtPath: to remove the existing file before calling migratePersistentStore:. This seems to have solved the duplication issue.
I couldn't find documentation that this is a feature but maybe it is.
UPDATED
I've added some example code for Jim. The flag for disabling journal_mode was very important for my use case. See here for more info
if ([[NSFileManager defaultManager] fileExistsAtPath: backupPath])
[[NSFileManager defaultManager] removeItemAtPath: backupPath
error: nil];
return [BRManagedObject.persistentStoreCoordinator
migratePersistentStore: store
toURL: [NSURL fileURLWithPath: backupPath]
options: #{
NSSQLiteManualVacuumOption : #(YES)
#ifndef SQLITE_USES_WRITE_AHEAD_LOG
, NSSQLitePragmasOption : #{ #"journal_mode" : #"DELETE" }
#endif
}
withType: persistentStoreType
error: nil];

Is Core Data Lightweight Migration, on Lion, not saving a backup file?

Previously, when you did a lightweight migration, core data created a backup file in the same folder like ~yourFile. But using Lion, I can't find this backup file. Is the file hided somewhere? I did look the documentation, but without success.
Any help would be appreciated.
Many thanks
Mario
It seems the ˜file is no more automatically created. So the solution I found is to create the file programatically.
So, I'm posting my solution
// Determine if a migration is needed
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:url error:&error];
NSManagedObjectModel *destinationModel = [persistentStoreCoordinator managedObjectModel];
BOOL pscCompatibile = [destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
// if migration is needed, copy the file appending OLD to the original name
if (pscCompatibile == 0) {
NSLog(#"doing migration");
NSURL *urlOld = [NSURL fileURLWithPath: [applicationSupportDirectory stringByAppendingPathComponent: #"AudioStManDataOLD"]];
[[NSFileManager defaultManager] copyItemAtURL:url toURL:urlOld error:&error];
}
tx
Mario

Updating/replacing CoreData sqlite file in app

My app currently have this sqlite file (lets name it v1)
What i want to do is that when i activate a IBAction , it will automatically delete the current file (v1) and retrieve values from webservice and store inside a new file (v2) and the application will then use the new sqlite file (v2)
any idea on how to implement?
my current code is here:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"test.sqlite"]];
NSLog(#"%#",storeUrl);
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;
}
You can:
download the new database from the server
reset the whole Core Data context (store coordinator, managed object contexts, managed objects)
replace the old database with the new one
ask the UI to refresh to display the newest content: the Core Data context should reload lazily
But this approach is quite dangerous, as you'd have to make sure every Core Data object in your app can be reset. Also, replacing the database should happen just after resetting the context, to avoid a race condition. If you have threads / blocks accessing Core Data this is even more difficult.
A better approach may be to update the database records by downloading a JSON file that contains the new database's contents, delete the old records, and insert the new ones. This also makes sure that you can update your Core Data schema in a future version of the app without breaking your content updating process. And by using the NSFetchResultsController class, your table views or other UI elements may even update automatically with a nice animation as your database gets updated.
If you care about bandwidth, I use protocol buffers which are way more compact than any JSON or even sqlite file would be, and quite easy to use.

Cocoa: Correct way to get list of possible PlugIns directories for an app?

In a Cocoa app generally we can install a plugin bundle in one of a number of places. If for example the app is called "MyApp" you'd be able to install the plugin at:
/Applications/MyApp.app/Contents/PlugIns
~/Library/Application Support/MyApp/PlugIns
/Library/Application Support/MyApp/PlugIns
/Network/Library/Application Support/MyApp/PlugIns
I'm building an NSArray of paths to search in the correct order but I'm pretty sure I'm doing this wrong since it feels like I'm doing too much work for something Apple seem to provide a lot of functions for.
NSArray *systemSearchPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSAllDomainsMask, YES);
NSMutableArray *searchPaths = [NSMutableArray array];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSString *systemPath in systemSearchPaths) {
NSString *systemPluginsPath = [systemPath stringByAppendingPathComponent:#"PlugIns"];
// FIXME: Remove debug code
NSLog(#"Considering plugin path %#", systemPluginsPath);
if ([fileManager fileExistsAtPath:systemPluginsPath]) {
[searchPaths addObject:systemPluginsPath];
}
}
[searchPaths addObject:[[NSBundle mainBundle] builtInPlugInsPath]];
This results in the Array returned by NSSearchPathForDirectoriesInDomains, with the builtInPlugInsPath value appended to the end.
However, it actually searches directories like "~/Library/Application Support/PlugIns" (missing the "MyApp") folder. Before I start hacking the code to inject the name of my application (which is subject to change at any time), am I doing this wrong?
Is there a way to just tell Cocoa "give me all search paths for 'PlugIns'" directories for this application"?
Nope. You're doing it right.
You can get the name of your application at run time by asking the main bundle for its info dictionary and looking for kCFBundleNameKey therein. When you rename your application, change the bundle name in your Info.plist.
(Definitely do not use your application's filename, as that's much more fragile.)
Be aware that users might not like it if the plug-ins they installed stop working because you renamed your application.
Note that your code above will not catch the PlugIns folder inside the application bundle. For that, ask your main bundle for its built-in plug-ins path or URL.

Mounting a folder as a device in Finder using Cocoa

Is there a way to mount a folder on the hard disk as a device in Finder. The intend here is to provide the user with an easy way to get to a folder that my application uses to store data. I don't want my user to go searching for data in Application Data. I would rather allow them to make this data available as a mounted volume or device in Finder. I would also like this volume or device to be read/write, so that if the user makes any changes to the data files, the changes will get reflected in the original folder.
Is there a way to do this in cocoa, carbon or applescript.
Try looking into FUSE. You can have all sorts of psuedo filesystems with that.
But I'd caution a little against what you are trying to do. It may make more sense to just have a button that opens the folder in your application, rather than create a new device. I personally would find it hard to continue to use an application that does such a thing. It doesn't really fit with the rest of the available applications.
You could also use an alias to point to your Application Data directory.
You can use sparse disk image to create "fake" drive.
But why not make data directory configurable in your application? Or use subdirectory in ~/Documents/?
Alias/symlink on desktop will be the easiest solution:
ln -s '~/Application Data/Yourapp' '~/Desktop/Yourapp Data'
Can I suggest rethinking this entirely? A symlink or alias would work, but, if possible, a better idea would be to register for the filetypes people will be moving into that folder, and then respond to opening them by moving or copying them to the correct folder. I'm thinking of something like the Dashboard interface, where if you double-click a downloaded .wdgt file, it asks if you want to 'install' the widget and then, if you do, copies it into ~/Library/Widgets. Obviously, if you're dealing with common types like images, folders, or generic text files, this might be impractical.
For implementation, you'd just add the document types to your Info.plist, and handle them in you App Delegate's -application:openFile: method.
I would also urge caution with this, seems potentially somewhat confusing to most users. That said, have you considered simply creating a softlink to the directory in question?
I Do it using NSWorkspace. In my case I make an initial check with the function -(BOOL)isMountedPath;
The code for mounting is:
NSURL *path=[NSURL URLWithString:#"smb://server.resource/KEYS_DB"];
if(NO==[self isMountedPath:[path absoluteString]])
{
NSWorkspace *ws=[NSWorkspace sharedWorkspace];
[ws openURL:path];
}
The code to check if a path is mounted is:
-(BOOL)isMountedPath:(NSString *)share
{
NSArray * keys = #[NSURLVolumeURLForRemountingKey];
NSArray * mountPaths = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:keys options:0];
NSError * error;
NSURL * remount;
for (NSURL * mountPath in mountPaths) {
[mountPath getResourceValue:&remount forKey:NSURLVolumeURLForRemountingKey error:&error];
if(remount){
if ([[[NSURL URLWithString:share] host] isEqualToString:[remount host]] && [[[NSURL URLWithString:share] path] isEqualToString:[remount path]])
{
printf("Already mounted at %s\n", [[mountPath path] UTF8String]);
return YES;
}
}
}
return NO;
}
Other possible useful method is:
-(NSString *)mountedPath:(NSString *)share
{
NSArray * keys = #[NSURLVolumeURLForRemountingKey];
NSArray * mountPaths = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:keys options:0];
NSError * error;
NSURL * remount;
for (NSURL * mountPath in mountPaths) {
[mountPath getResourceValue:&remount forKey:NSURLVolumeURLForRemountingKey error:&error];
if(remount){
if ([[[NSURL URLWithString:share] host] isEqualToString:[remount host]] && [[[NSURL URLWithString:share] path] isEqualToString:[remount path]])
{
printf("Already mounted at %s\n", [[mountPath path] UTF8String]);
return [mountPath path];
}
}
}
return nil;
}

Resources