UImanagedDocument's persistentStore iCloud backup - core-data

Im trying to create backups for core data UIManagedDocument, and store them in iCloud. Its been > 2 months since im trying to do this and i dont understand how to do that. there is no info in the internet at all...
im trying to create local backups at least and it doesnt work either
this is the code :
-(void)testCopyStoreToDocuments
{
AppDelegate* appDelegate=(AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate.userDataDocument closeWithCompletionHandler:^(BOOL closed)
{
if(closed)
{
#autoreleasepool {
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSString *sourceFile = [[[[LoadingManager localDocumentURL]URLByAppendingPathComponent:#"StoreContent"]path] stringByAppendingPathComponent:#"persistentStore"];
NSURL *sourceURL = [NSURL fileURLWithPath:sourceFile];
[fc coordinateReadingItemAtURL:sourceURL options:NSFileCoordinatorReadingWithoutChanges error:nil byAccessor:^(NSURL *sourceURLtoUse) {
NSError *error = nil;
NSFileManager *fm = [[NSFileManager alloc] init];
NSString *destinationFile = [[[LoadingManager localDocumentsDirectoryURL]path] stringByAppendingPathComponent:#"persistentStore"];
//simply copy the file over
BOOL copySuccess = [fm copyItemAtPath:[sourceURLtoUse path]
toPath:destinationFile
error:&error];
if (copySuccess) {
NSLog(#" copied file successfully");
} else {
NSLog(#"Error copying item at path: %#\nTo path: %#\nError: %#", sourceFile, destinationFile, error);
}
}];
fc = nil;
}
}
}];
}
-(void)testReplaceStore
{
AppDelegate* appDelegate=(AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate.userDataDocument closeWithCompletionHandler:^(BOOL closed)
{
if(closed)
{
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fc coordinateWritingItemAtURL:[[[LoadingManager localDocumentURL]URLByAppendingPathComponent:#"StoreContent"]URLByAppendingPathComponent:#"persistentStore"] options:NSFileCoordinatorWritingForDeleting error:nil byAccessor:^(NSURL *sourceURLtoUse){
NSLog(#"%#",sourceURLtoUse);
NSError * error = nil;
NSLog(#"replacment: %hhd",[[NSFileManager defaultManager]replaceItemAtURL:sourceURLtoUse withItemAtURL:[[LoadingManager localDocumentsDirectoryURL]URLByAppendingPathComponent:#"persistentStore"] backupItemName:#"backUp" options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:nil error:&error]);
NSLog(#"%#",error);
}];
NSLog(#"stores: %#",appDelegate.userDataDocument.managedObjectContext.persistentStoreCoordinator.persistentStores);
[appDelegate.userDataDocument saveToURL:appDelegate.userDataDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL saved)
{
if(saved)
{
[appDelegate.userDataDocument openWithCompletionHandler:^(BOOL opened)
{
if(opened)
{
NSLog(#"opened");
}
}];
}
else
{
NSLog(#"failed to save");
NSLog(#"stores: %#",appDelegate.userDataDocument.managedObjectContext.persistentStoreCoordinator.persistentStores);
}
}];
}
}];
}
It Nslogs an error when replacing is called :
replacment: 0
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be completed. (Cocoa error 512.)" UserInfo=0x15d91540 {NSFileNewItemLocationKey=file:///var/mobile/Applications/85974C93-75FD-406A-B1BF-EDE7DFC25FE2/Documents/persistentStore, NSFileOriginalItemLocationKey=file:///var/mobile/Applications/85974C93-75FD-406A-B1BF-EDE7DFC25FE2/Documents/Data%20Document/StoreContent/persistentStore, NSUnderlyingError=0x15db0080 "The operation couldn’t be completed. (Cocoa error 260.)",

I just tested this code and it works fine. Here is a sample app http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/
You will get that error if the target file already exists.
/** Copies file to iCloud container removing the target file if it already exists
#param docURL URL of UIManagedDocument whose store file is to be copied (its Core Data Store must not be shared in iCloud!
*/
- (void)copyFileToICloud:(NSURL*)docURL {
#autoreleasepool {
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSURL *sourceURL = [[docURL URLByAppendingPathComponent:#"StoreContent"] URLByAppendingPathComponent:#"persistentStore"];
// Local directory - test
//NSString *destinationFile = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:#"persistentStore_backup"];
NSURL *destinationURL = [[[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:#"Documents"] URLByAppendingPathComponent:#"persistentStore_backup"];
FLOG(#" source file is %#", sourceURL);
FLOG(#" target file is %#", destinationURL);
NSError *cError;
[fc coordinateWritingItemAtURL:destinationURL options:NSFileCoordinatorWritingForReplacing error:&cError byAccessor:^(NSURL *newURL) {
NSError *error = nil;
NSFileManager *fm = [[NSFileManager alloc] init];
// Delete it if it already exists
if ([fm fileExistsAtPath:[newURL path]]) {
FLOG(#" target file exists");
if (![fm removeItemAtURL:newURL error:&error]) {
FLOG(#" error unable to remove target file");
NSLog(#"Error removing item Error: %#, %#", error, error.userInfo);
return;
} else {
FLOG(#" target file removed");
}
}
//simply copy the file over
BOOL copySuccess = [fm copyItemAtPath:[sourceURL path]
toPath:[newURL path]
error:&error];
if (copySuccess) {
NSLog(#" copied file successfully");
} else {
NSLog(#"Error copying items Error: %#, %#", error, error.userInfo);
}
}];
if (cError) {
FLOG(#" error is %#", cError);
}
fc = nil;
}
}
EDIT: A few additional things to remember:
If you are using WAL mode (or the default Core Data mode for iOS7, OS X 10.9) then you have to copy the ~wal and ~shm files as well - better copy the entire StoreContent directory.
To copy the files from iCloud you will have to do a metadata query to find the files and then check the download status, I think you will have to trigger the download and check the files have downloaded before you can copy them.

Related

RestKit and Collapsible Menu, synchronize with Core Data

I'm using RestKit and RRNCollapsableTable. The problem is, when I load view for the first time, RestKit is downloading data from JSON. That delay causes menu to not load. What I'm trying to do is to make CollapsableTable wait for data.
[self requestData:^(BOOL success) {
if (success) {
_menu = [self buildMenu];
[self model];
}
}];
- (void)requestData:(void (^)(BOOL success))completionBlock {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Messages"];
NSSortDescriptor *byId = [NSSortDescriptor sortDescriptorWithKey:#"customNewsId" ascending:YES];
fetchRequest.sortDescriptors = #[byId];
NSError *error = nil;
// Setup fetched results
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext
sectionNameKeyPath:#"customNewsId"
cacheName:nil];
//[self.fetchedResultsController setDelegate:self];
BOOL fetchSuccessful = [self.fetchedResultsController performFetch:&error];
if (!fetchSuccessful) {
abort();
}
[[RKObjectManager sharedManager] getObjectsAtPath:#"api/json/get/bZmroLaBCG" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load complete: Table should refresh...");
//[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:#"LastUpdatedAt"];
//[[NSUserDefaults standardUserDefaults] synchronize];
NSError *error = nil;
if ([[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext hasChanges] && ![[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext saveToPersistentStore:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
completionBlock(YES);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Load failed with error: %#", error);
completionBlock(NO);
}];
completionBlock(YES);
}
Then BuildMenu is just iterating over fetched objects and put them in section.
-(NSArray *)buildMenu {
__block NSMutableArray *collector = [NSMutableArray new];
NSInteger sections = [self.fetchedResultsController.sections count];
for (NSInteger i = 0; i < sections; i++) {
Messages *message = [_fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:i]];
MenuSection *section = [MenuSection new];
section.items = #[message.message,message.pushNotificationMessage];
section.title = message.title;
NSLog(#"section.title - %#",section.title);
[collector addObject:section];
}
return [collector copy];
}
Method model is responsible for DataSource for CollapsableTable.
-(NSArray *)model {
return _menu;
}
Thanks in advance for any help.
Greetings.

Automatic lightweight migration works for local storage but iCloud storage "loses" all legacy data

I'm tearing my hair out with this one.
I've got an App on iTunes which I added iCloud support to end of last year (Oct '13) on iOS7.0
This week I decided to write a new functional for the App which requires a new entity in the xcdatamodel. A very simple change/addition. Should have no impact on the current data set.
I create a new v2 xcdatamodel and set it to Current Model version, compile and run and it works fine if I've got iCloud switch off on my iPad. I see previous saved data.
Run it again with iCloud switch on and I get a blank table with no data.
No error messages, nothing.
Hoping someone can throw some light on what I've done wrong here:
- (NSManagedObjectModel *)managedObjectModel {
if (__managedObjectModel != nil) {
return __managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"UserData" withExtension:#"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
NSError *error = nil;
BOOL success = NO;
if((__persistentStoreCoordinator != nil)) {
return __persistentStoreCoordinator;
}
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator;
NSString *iCloudEnabledAppID = #"C3FUPX46ZG~com~software~App";
NSString *dataFileName = #"UserData.sqlite";
NSString *iCloudDataDirectoryName = #"CoreData.nosync";
NSString *iCloudLogsDirectoryName = #"CoreDataLogs";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localStore = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:dataFileName];
NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil];
if (iCloud && ([UserDefaults getIsiCloudOn])) {
// This iCloud storage fails to migrate.
NSURL *iCloudLogsPath = [NSURL fileURLWithPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]];
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];
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES,
NSPersistentStoreUbiquitousContentNameKey : iCloudEnabledAppID,
NSPersistentStoreUbiquitousContentURLKey : iCloudLogsPath
};
success = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[NSURL fileURLWithPath:iCloudData]
options:options
error:&error];
if (!success) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
} else {
// This local storage migrates automatically just fine.
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES
};
success = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:localStore
options:options
error:&error];
if (!success) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kCoreDataChangeNotification object:self userInfo:nil];
});
return __persistentStoreCoordinator;
}
UPDATE: Switched on core data debugging & iCloud debugging/logging.
Migration works for both local & iCloud. Logs are the same, ending with:
CoreData: annotation: (migration) inferring a mapping model between data models with...
CoreData: annotation: (migration) in-place migration completed succeessfully in 0.03 seconds
-PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:: CoreData: Ubiquity: mobile~F9AC6EB1
Using local storage: 1
With iCloud storage & debugging on it seems to cause a delay and I briefly see my saved data for about 10seconds when it then disappears.
Just before it disappears the debugs spit out:
CoreData: annotation: (migration) inferring a mapping model between data models with...
Using local storage: 0
The iCloud logs are enormous which is why I'm not posting them here. From what I can see I have over 400 log files and iCloud seems to be doing some sort of syncing. If I leave the App and iPad open and on for a few hours I still see an empty data set. So it's not a case of waiting for a sync catch up. I'm still at a loss even with the debugs on....

downloaded video - AFHTTPRequestOperation vs. NSURLSessionDownloadTask

I try to update my existing download-model, so I have replaced my old code:
AFHTTPRequestOperation *downloadRequest = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[downloadRequest setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
[data writeToFile:video2Save.localFilePath atomically:YES];
video2Save.downloadComplete = YES;
[YEPersistentModelHelper saveData:_downloadVideos ToDiskWithIdentifier:persistentIdDownloadedVideos];
NSLog(#"file downloading complete : %#", video2Save.localFilePath);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"file downloading error : %#", [error localizedDescription]);
}];
[downloadRequest start];*/
with the following:
NSURLSessionDownloadTask *downloadTask = [_sessionManager downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[NSString stringWithFormat:#"%#.mp4",video2Save.videoVersionId]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
video2Save.localFilePath = [[filePath filePathURL] absoluteString];
video2Save.downloadComplete = YES;
[YEPersistentModelHelper saveData:_downloadVideos ToDiskWithIdentifier:persistentIdDownloadedVideos];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *err = nil;
NSDictionary *att = [fileManager attributesOfItemAtPath:video2Save.localFilePath error:&err];
NSLog(#"NSDictionary: %#", att);
}];
[downloadTask resume];
And it seems to work fine. The complete-block is executed & the file exists at the traced target.
The problem is, that I am no longer available to play the video! I use the MPMoviePlayerController which throws this useful error:
_itemFailedToPlayToEnd: { kind = 1; new = 2; old = 0; }
The only difference seems to be the file-permissions. The first one adds a "staff"-group & everyone is allowed to read while the second only grants access for "me". But even if I change it in the finder I am not able to play it...
Does anyone has an idea!?
to save location file use path no absoluteString
video2Save.localFilePath = [[filePath filePathURL] absoluteString];
don't call absoluteString even to play.. just use the path
like this for example to call the video
NSURL *FilePathURL = [NSURL fileURLWithPath:[docDir stringByAppendingPathComponent:fileToCheck]];
[[myvideoCalss :[FilePathURL path]]

What is the best way to remove logs file Core Data creates, when removing a UIManagedDocument from iCloud?

I would have thought NSFileManagers method of removeItemAtURL:error: would remove the Core Data log files created when using UIManagedDocuments with iCloud.
What is the best way to make sure all of these log files are removed?
I have used...
- (void)deleteRemnantsOfOldDatabaseDocumentAndItsTransactionLogsWithCompletionHandler:(completion_success_t)completionBlock
{
__weak CloudController *weakSelf = self;
NSURL *databaseStoreFolder = self.iCloudDatabaseStoreFolderURL;
NSURL *transactionLogFolder = self.transactionLogFilesFolderURL;
[self deleteFileAtURL:databaseStoreFolder withCompletionBlock:^(BOOL docSuccess) {
[weakSelf deleteFileAtURL:transactionLogFolder withCompletionBlock:^(BOOL logSuccess) {
completionBlock(docSuccess && logSuccess);
}];
}];
}
In conjunction with...
- (void)deleteFileAtURL:(NSURL *)fileURL withCompletionBlock:(completion_success_t)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSError *coordinatorError = nil;
__block BOOL success = NO;
[fileCoordinator coordinateWritingItemAtURL:fileURL
options:NSFileCoordinatorWritingForDeleting
error:&coordinatorError
byAccessor:^(NSURL *writingURL) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *removalError = nil;
if ([fileManager fileExistsAtPath:[writingURL path]]) {
if (![fileManager removeItemAtURL:writingURL error:&removalError]) {
NSLog(#"deleteFileAtURL: removal error: %#", removalError);
} else {
success = YES;
}
}
}];
if (coordinatorError) {
NSLog(#"deleteFileAtURL: coordinator error: %#", coordinatorError);
}
completionBlock(success);
});
}
Note: this was used for a single document toolbox style app, and was intended more for clearing out the iCloud container before creating a brand new document, in an 'apparently' empty iCloud store for the first time. But I'm sure it can be adapted without too much work.
Oops, the above won't make sense/work without:
typedef void (^completion_success_t)(BOOL success);
You can debug the contents of your iCloud container and verify things have been removed by using a method like (which to be honest I've probably lifted from somewhere else and modified):
- (void)logDirectoryHierarchyContentsForURL:(NSURL *)url
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtURL:url
includingPropertiesForKeys:#[NSURLNameKey, NSURLContentModificationDateKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:nil];
NSMutableArray *results = [NSMutableArray array];
for (NSURL *itemURL in directoryEnumerator) {
NSString *fileName;
[itemURL getResourceValue:&fileName forKey:NSURLNameKey error:NULL];
NSDate *modificationDate;
[itemURL getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:NULL];
[results addObject:[NSString stringWithFormat:#"%# (%#)", itemURL, modificationDate]];
}
NSLog(#"Directory contents: %#", results);
}
And it's also worth logging onto developer.icloud.com and examining what is actually in the iCloud store. There is sometimes a difference between what is retained in the device ubiquity container, and what is actually in the iCloud server folder structure. Between all of these you can get quite a good idea of what's going on.

rapid persistent store for database from UIManagedDocument

I created a database through a UIManagedDocument. If I add records slowly, everything works. If I rapidly (3-4 seconds between records), the records are out of order when fetched and may not survive across the app stopping/starting.
The UIManagedDocument is created in viewDidAppear and modified in "useDocument" (code provided below) which checks for non-existant database and creates it if necessary or just opens it.
Again, I think my issue is that the core data is not immediately stored to the SQLite.
-(void)setExampleDatabase:(UIManagedDocument *)exampleDatabase {
if ( _exampleDatabase != exampleDatabase ) {
_exampleDatabase = exampleDatabase;
NSMutableDictionary *pragmaOptions = [NSMutableDictionary dictionary];
[pragmaOptions setObject:#"FULL" forKey:#"synchronous"];
[pragmaOptions setObject:#"1" forKey:#"fullfsync"];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
pragmaOptions, NSSQLitePragmasOption,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES, NSInferMappingModelAutomaticallyOption, nil];
exampleDatabase.persistentStoreOptions = options;
//
// Does the file exist?? Is not, create it.
if ( ! [[NSFileManager defaultManager] fileExistsAtPath: [self.exampleDatabase.fileURL path]] ) {
[self.exampleDatabase saveToURL:self.exampleDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
//
// OK, the creation has been done, enable the add button so records can be created
self.addButton.enabled = YES;
}];
//
// The file exists, but it's close, so open it.
} else if ( self.exampleDatabase.documentState == UIDocumentStateClosed ) {
[self.exampleDatabase openWithCompletionHandler:^(BOOL success) {
if ( success ) {
//
// It's now successfully opened, enable the add button, load the data from
// the core data database and cause the objects to be displayed in the table view
// "getCounts" is immediately below this method
self.addButton.enabled = YES;
[self getCounts:self.exampleDatabase.managedObjectContext];
[self.tableView reloadData];
} else {
NSLog(#"Error opening a closed database");
}
} ];
} else if ( self.exampleDatabase.documentState == UIDocumentStateNormal ) {
//
// OK, the database is opened. Enable the add button, load the data from
// the core data database and cause the objects to be displayed in the table view
// "getCounts" is immediately below this method
self.addButton.enabled = YES;
[self getCounts:self.exampleDatabase.managedObjectContext];
[self.tableView reloadData];
} else {
NSLog(#"Something is wrong in useDocument - it exists in an unknown state");
exit(1);
}
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if ( ! self.exampleDatabase ) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"DefaultDb"];
self.exampleDatabase = [[UIManagedDocument alloc] initWithFileURL:url];
}
}

Resources