I want to back up and retrieve sqlite3 database in core data. This is to provide a quick save and restore of users data saved in an app. The plan is to email the database, then open it on the receiving device and all the previously data will magically appear and all is good.
I have read many posts and documentation on the subject but putting it all together is where I could do with some advice.
What I have done so far:
I have managed to email the sqlite3database using the following
NSString *filePath = [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingPathComponent:#"db.sqlite"];
NSError *error = nil;
NSData *fileData = [[NSData alloc] initWithContentsOfFile:filePath options:0UL error:&error];
if (error != nil) {
DebugLog(#"Failed to read the file: %#", [error localizedDescription]);
} else {
if (fileData == nil) {
DebugLog(#"File data is nil");
}
}
MFMailComposeViewController *mailView = [[MFMailComposeViewController alloc] init];
if (mailView != nil) {
mailView.mailComposeDelegate = self;
[mailView setSubject:#"back up database"];
[mailView addAttachmentData:fileData
mimeType:#"application/octet-stream"
fileName:#"db.sqlite3"];
[mailView setMessageBody:#"Database attached"
isHTML:NO];
[self presentViewController:mailView animated:YES completion: NULL];
}
Further research leads me to believe I have to attach other journal files to this to this? iOS: How can I create a backup copy of my core data base? And how to export/import that copy?
Ive logged these documents as db.sqlite db.sqlite-shm db.sqlite-wal
My question is
1. Do I send all these db.sqlite db.sqlite-shm db.sqlite-wal as attachments in the email?
2. What is the procedure to receive these files and copy them into the app.
Simply opening the attachment does not work as it can only be read by certain apps. I then made a Document type in my info.plist and my app appears in the action sheet as an app to receive the document but crashes the app with error incomprehensible archive
So in summary, how can I save and restore my core data
Archive all 3 sqlite files into one zip file and name it Backup.myappbackup or something similar (it’s good to add date of backup to filename). Than you can register extension to be recognized by your application which will allow Mail.app to recognize this extension and open this backup within your app. Next you will unarchive core data files, replace them at your locations and reconfigure core data stack. You can also allow users to put your backups through iTunes Sharing and restore it from your Documents folder.
Related
We have an iOS application that manages documents via Core Data. The actual files reside in the app's shared container so that the app's file provider extension can also access them for Files.app support. We want to give the user the option to open these files in third-party apps so that they can edit them in-place instead of sending a copy to the other app.
We provide a UIActivityViewController for sharing files with other apps. We also provide a UIActivity that shows a UIDocumentInteractionController which seems to work better in some cases. We give the UIActivityViewController the document's file URL, the raw text content, and printable data.
This works but all third-party editors are shown as Copy to … instead of Open in …
We've also set the UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace properties to YES in the app's info.plist but they seem to be only relevant for open-in-place when sharing files residing in the app's Documents folder.
Now we've stumbled upon the NSItemProviderFileOptionOpenInPlace option for NSItemProvider. As we're already supporting a file provider extension and from Apple's documentation this seemed like a great place to accomplish just what we want.
Adding a "pure" NSItemProvider works, in a way, but shows fewer options than when also sharing the file URL and text in addition (which is expected). However, when we use -[NSItemProvider registerFileRepresentationForTypeIdentifier:fileOptions:visibility:loadHandler:] with the said option (or just zero, same result) and return the file URL in the loadHandler's completionHandler() nothing is shared anymore. E.g., Mail no longer attaches the file, Messages doesn't show the document for sending.
These are the relevant bits of the code:
NSMutableArray *items = [NSMutableArray array];
NSMutableArray <UIActivity *> *activities = [NSMutableArray array];
NSURL *fileURL = self.record.metadata.fileURL;
NSString *fileUTI = self.record.metadata.uti;
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem: fileURL typeIdentifier: fileUTI];
[itemProvider registerFileRepresentationForTypeIdentifier:fileUTI fileOptions:NSItemProviderFileOptionOpenInPlace visibility:YES loadHandler:^NSProgress * _Nullable(void (^ _Nonnull completionHandler)(NSURL * _Nullable, BOOL, NSError * _Nullable))
{
if (fileURL)
completionHandler(fileURL, YES, nil);
else
completionHandler(nil, YES, [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]);
return nil;
}];
[items addObject:itemProvider];
self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:activities];
[UIAppDelegate.splitViewController presentViewController:self.activityViewController animated:YES completion:nil];
The using the Share menu the item provider's load handler is correctly called and the file's actual URL returned.
Is that not how NSItemProviderFileOptionOpenInPlace is intended to be used? Or are we using it simply wrong? Apple's description is extremely sparse and we couldn't find any information elsewhere on the internet except for the official documentation.
I've found out what my problem was: Not deep enough understanding of the relationship between the activity view controller and file providers.
As all my files reside in the shared container and are published also through the file provider extension, what I need to share through the activity view controller is the exact same URL that is shared through the file provider extension. Technically then the app that opens the file accesses it through there file provider mechanism.
I am attempting to create a process to backup my app data stored in a SQLite file. To this point I have been able to successfully hook into the Dropbox SDK, upload a backup file then download the backup file. I am stuck attempting to replace the current .sqlite with the downloaded backup .sqlite. Replacing the file itself did not yield desired results so I attempted to drop the tables of the current DB and re-load them from the backup. However when attempting to query the backup I am getting an error that the file is encrypted (it is not) or not a database. Error message = DB Open Error: file is encrypted or is not a database.
Code to simply count records from a table in backup below. If anyone has any ideas or even better a simpler way for me to swap out the existing .sqlite for the backup.sqlite that would be much appreciated.
self.dbManagerBackup = [[DBManager alloc] initWithDatabaseFilename:#"Backup.sqlite"];
NSString *query = [NSString stringWithFormat:#"select * from Table1"];
if (self.Backup != nil) {
self.Backup = nil;
}
self.Backup = [[NSArray alloc] initWithArray:[self.dbManagerBackup loadDataFromDB:query]];
NSLog(#"*** Backup Backup Count = %lu",(unsigned long)self.Backup.count);
I am unable to achieve a simple lightweight migration by simply adding 1 Entity to the datamodel.
I have read and followed all the guides/documentation/posts/answers, I can't seem to find my mistake/error.
I do have created a new datamodel from the already existing one.
I do have set the new datamodel as current datamodel.
I do have only added 1 Entity to the new datamodel (+ link to the parent Entity).
I do have passed the dictionary options NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption in the method addPersistentStoreWithType.
I even tried to log everything, thank to the method given from this post: core data migration
/*! The method checks the Core Data file version is compatible with the App's model version
and then pushes the main menu view onto the navigation stack. If not compatible it displays a
message to the user.
#param file The file URL for the Core Data Store. With UIManagedDocument you have to get the
actual store file URL, you can't just use the UIManagedDocument file URL.
*/
-(void) checkCoreDataFileVersion:(NSURL*)file
{
if ([self checkVersion:file]) {
// file version is compatible so continue (add code to push the menu view)
} else {
// file version is NOT compatible
_fileOpenErrorAlert = [[UIAlertView alloc] initWithTitle:#"Unable to open Document" message:#"Please check that you have the correct application version installed" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[_fileOpenErrorAlert show];
}
return;
}
/*! Checks the Core Data files models version against the apps model version to see if they
are compatible. This will return YES if a lightweight migration can be performed and NO if NOT.
#param fileURL The file URL for the Core Data Store. With UIManagedDocument you have to get the
actual store file URL, you can't just use the UIManagedDocument file URL.
#return Returns YES if they are compatible and NO if not.
*/
- (bool)checkVersion:(NSURL*)fileURL {
NSManagedObjectModel *model = [self managedObjectModel];
NSLog(#" app model entity version hashes are %#", [model entityVersionHashesByName]);
NSError *error;
NSDictionary *metaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:fileURL error:&error];
if (!metaData) {
NSLog(#"problem getting metaData");
NSLog(#" - error is %#, %#", error, error.userInfo);
return NO;
}
bool result = [model isConfiguration:nil compatibleWithStoreMetadata:metaData];
if (!result) {
NSLog(#" file is not compatible!");
NSLog(#" metadata is %#", metaData);
}
return result;
}
When I make a diff of the metadata from all the Entities, I only match difference for 1 Entity (the newly created). So why it can't make a migration ? I just added 1 Entity.
EDIT :
I don't have Crashes, the App is working fine.
There is something I don't understand. When I download our lastest App from the AppStore, launch it and when I build from xCode my lastest developement App (with the new datamodel) over the one from the AppStore, the migration doesn't occur.
BUT when I use GIT, when I put the HEAD to the lastest release TAG, build, launch the App. Then put back the HEAD to my lastest development feature (with the new datamodel etc..), build and run, the migration is done and everything is working.
So which scenario should I trust ?
Yes, You should trust the 2nd senario to test coredata migration by applying it to the last released code.
The first senario is no more valid since Apple for some security reasons nomore give the ability to update an itune-downloaded app using xcode directly.
There was a way to test the upgrade on itune-version but not directly from xcode.
Technical Note TN2285
Testing iOS App Updates
Install an ad hoc distribution of an archived build of the update
using iTunes on a device that already has the old version of the app
installed.
Installing Your App on Test Devices Using iTunes
I have an app that currently saves a NSMutableArray to a file in the documents directory on the iPad. This is saved by
[residentData writeToFile:filePath atomically:NO];
and is read back in by;
residentData = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
I need to write this file to iCloud, as the data needs to be available across several devices. I have tried the following to create the file in iCloud;
NSData *myFileData = [NSKeyedArchiver archivedDataWithRootObject:residentData];
[fileManager createFileAtPath:[icloudURL path] contents:myFileData attributes:nil];
Which does indeed create a file in the correct place. I am struggling though to understand how to read this file back into my NSMutableArray (residentData). I have tried;
residentData = [NSKeyedUnarchiver unarchiveObjectWithData:myFileData];
But this doesn't decode into the array correctly. Please can someone show me the error of my ways - I suspect a fundamental misunderstanding on my part of NSData is to blame, but any pointers would be welcome.
It finally clicked, my approach was wrong. I don't need to use any of the above. Once I have done the;
[fileManager setUbiquitous:YES itemAtURL:currentPlace destinationURL:icloudURL error:&error];
The system takes care of everything else - i.e. just accessing the file normally with
residentData = [[NSMutableArray alloc] initWithContentsOfURL:icloudURL];
Has worked.
I have a problem syncing NSFileWrapper documents with iCloud. I am able to create my wrapper and save it to my ubiquitous container.
When I try to read it from the device that created it, it works. When I try to read form another device that got it from iCloud, it crashes.
Some code:
This function to add a wrapper container with a NSString
- (void) addNSString:(NSString*)_string toFileWrapper:(NSFileWrapper*)_wrapper forKey:(NSString*)_key {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:_string];
if(data) {
[_wrapper addRegularFileWithContents:data preferredFilename:_key];
}
}
And then here is how I decode it:
- (id) unarchiveObjectFromWrappers:(NSDictionary*)_wrappers withKey:(NSString*)_key {
id value = nil;
NSFileWrapper *wrapper = [_wrappers valueForKey:_key];
if(wrapper) {
NSData *data = [wrapper regularFileContents];
if(data) {
value = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
}
return value;
}
The decoding part works on one device and not on the others (EXC_BAD_ACCESS when the NSKeyedUnarchiver tries to unarchive from the NSData. The NSData seems good, it has the proper length and everything but when I try to log its datas for example it crashes).
My guess is that the NSFileWrapper doesn't download its full content, only its structure and that I have to do something to make it available. But I don't know what.
Any ideas?
========
Edit:
NSURLUbiquitousItemIsDownloadedKey says that the file is downloaded BUT if I try to copy it to the sandbox it fails with this error: "The operation couldn’t be completed. Bad file descriptor"
So the file is either not uploaded properly to iCloud or not downloaded properly...
It drove me crazy too. The solution is rather simple, yet totally undocumented by Apple. You must download the file specifically. Only the file wrapper is downloaded automatically, but not its contents. That's why the check says the file exists.
Before copying the file over, call something like this:
[[NSFileManager defaultManager]startDownloadingUbiquitousItemAtURL:cloudURL error:nil];
Related: Cannot sync simple text file with iCloud (bad file descriptor)