NSFileWrapper fails when from iCloud and works from local directory - ios

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)

Related

Getting a package contents as NSData

My app creates some back-up files as Packages (in fact as directories with an extension, and an Exported UTI that conforms to com.apple.package).
I would like to be able to read them as NSData, that I can attach to an email in a MFMailComposeViewController. Actually, it doesn't work because dataWithContentsOfURL: returns nil when I try to read the package (I think because it's a directory, not a regular file).
I know my package files are fine because I can access them on my Mac when I download the "app container" from my iPhone.
I generate them using NSFileWrappers, and calling the writeToURL:options:originalContentsURL:error: method.
I don't want to use serializedRepresentation because it generates NSData that can be read only by NSFileWrapper (and I would like to be able to open them on my Mac, by clicking on "Show Packages Contents").
Here is the part of code that doesn't work:
NSURL *finalBackupURL = [outputDirectoryURL URLByAppendingPathExtension:#"ext"];
if (![packageWrapper writeToURL:finalBackupURL options:0 originalContentsURL:nil error:error])
#throw [NSException exceptionWithError:*error];
NSData *data = [NSData dataWithContentsOfURL:finalBackupURL];
(data = nil whereas the file has well been created)
Thank you for your help

Saving PFFile Eventually

A PFObject can be saveEventually to be sync on Parse when network is reachable, while keeping it locally meanwhile.
If your file contains a PFFile, the file must be savedInBackground before your PFObject can be save.
How to saveEventually a PFFile, for it to be send now, or later when network is reachable ?
As you might know, that feature isn't available within ParseSDK, so after seeing a few posts vaguely explaining how to bypass this, I wrote a sample working XCode project
That's only a working PoC with limitations such as only working for a single Parse class to associate saved PFFile on.
It requires Reachability pod 'Reachability', '~> 3.2'
How to use it ? Well, I guess the sample projects describes it well, but here is a piece of code to understand how it works :
(Remember to run pod install to resolve dependencies before running example)
/*
This example uses an UIImage, but this works with any file writable as NSData
We begin by writing this image in our tmp directory with an uuid as name.
*/
UIImage *nyancat = [UIImage imageNamed:#"nyancat.jpg"];
NSData *imageData = UIImageJPEGRepresentation(nyancat, 0.5);
NSString *filename = [[NSUUID UUID] UUIDString];
NSURL *fileUrl = [PFFileEventuallySaver fileURLInTmpWithName:filename];
[imageData writeToURL:fileUrl atomically:YES];
/*
We create a PFObject (you can pass an array to below function if you need your file to be saved on several objects). If upload works on first time, do what you want with your file, like linking it on your PFobject.
If saving fails, it'll be retried as soon as network is available, on this session or nexts launches of app.
In that case, the pointer at key kPFFILE_MANAGER_OBJECT_FILE_KEY of your PFFObject will be set with the PFFile, then saved eventually within PFFileEventuallySaver
*/
PFObject *object = [PFObject objectWithClassName:kPFFILE_CONTAINER_OBJECT_CLASSNAME];
[[PFFileEventuallySaver getInstance] trySaveobjectAtURL:fileUrl associatedObjects:#[object] withBlock:^(PFFile *file, NSError *error) {
if(!error)
{
NSLog(#"[First try, network is fine] File saved, saving PFObject");
object[kPFFILE_MANAGER_OBJECT_FILE_KEY] = file;
[object saveEventually];
NSLog(#"Try again disabling your network connection");
}
else
{
NSLog(#"No network, connect back your wifi, or relaunch app. Your file will be sent");
}
} progressBlock:^(int percentDone) {
NSLog(#"[First try, network is fine] Sending file %d/100%%", percentDone);
}];
This could be greatly improved, but I thought you guys might found that useful, as I would've wanted to find a similar working example.

Save eventually on PFObject with PFFile (Parse Local Datastore)?

Goal
I am trying to save a PFObject that has a PFFile as an attribute. I am using the new Local Datastore for iOS, so I would like to save this PFObject with the saveEventually() method.
The Problem
The problem I am encountering is that the saveEventually() method doesn't seem to like saving the PFFiles. I tried to saveEventually() my object without any PFFile attached, and that worked fine. As soon as my PFFile was reattached, Xcode threw a couple of breakpoint notices (errors?) but did not terminate the app, and it appears as though all went well - however a check on the Parse Data Browser confirms that the save did not go through. Prior to the Local Datastore feature I don't believe this save would have been possible - it would have thrown the "Unable to saveEventually a PFObject with a relation to a new, unsaved PFFile." error. It seems as though the Local Datastore feature has fixed this, as it states in the iOS Local Datastore docs:
"Pinning a PFObject is recursive, just like saving, so any objects
that are pointed to by the one you are pinning will also be pinned.
When an object is pinned, every time you update it by fetching or
saving new data, the copy in the local datastore will be updated
automatically. You don't need to worry about it at all."
I have updated the SDK to the latest version (v1.6.2). Any ideas?
PFFiles still don't support saveEventually see here
That page was last updated : 2015-01-23
You could pinInBackgroundWithBlock and if successful save the PFFile to a temporary folder in you app bundle and delete it when necessary or unpinned
I just released a class which allows to saveEventually a PFFile.
You can find it here :
/*
This example uses an UIImage, but this works with any file writable as NSData
We begin by writing this image in our tmp directory with an uuid as name.
*/
UIImage *nyancat = [UIImage imageNamed:#"nyancat.jpg"];
NSData *imageData = UIImageJPEGRepresentation(nyancat, 0.5);
NSString *filename = [[NSUUID UUID] UUIDString];
NSURL *fileUrl = [PFFileEventuallySaver fileURLInTmpWithName:filename];
[imageData writeToURL:fileUrl atomically:YES];
/*
We create a PFObject (you can pass an array to below function if you need your file to be saved on several objects). If upload works on first time, do what you want with your file, like linking it on your PFobject.
If saving fails, it'll be retried as soon as network is available, on this session or nexts launches of app.
In that case, the pointer at key kPFFILE_MANAGER_OBJECT_FILE_KEY of your PFFObject will be set with the PFFile, then saved eventually within PFFileEventuallySaver
*/
PFObject *object = [PFObject objectWithClassName:kPFFILE_CONTAINER_OBJECT_CLASSNAME];
[[PFFileEventuallySaver getInstance] trySaveobjectAtURL:fileUrl associatedObjects:#[object] withBlock:^(PFFile *file, NSError *error) {
if(!error)
{
NSLog(#"[First try, network is fine] File saved, saving PFObject");
object[kPFFILE_MANAGER_OBJECT_FILE_KEY] = file;
[object saveEventually];
NSLog(#"Try again disabling your network connection");
}
else
{
NSLog(#"No network, connect back your wifi, or relaunch app. Your file will be sent");
}
} progressBlock:^(int percentDone) {
NSLog(#"[First try, network is fine] Sending file %d/100%%", percentDone);
}];

Error while deleting the file

While deleting existing the file with this command:
[[NSFileManager defaultManager] removeItemAtPath:self.sourceFileName error:&error];
I got the following error
Error: ImageIO: CGImageReadCreateDataWithMappedFile 'open' failed '/Users/asdasd/Library/Application Support/iPhone Simulator/7.1/Applications/DD251D7D-F0AF-40E1-A033-F221623D589D/Library/ScanSession/Source/page3.jpeg
error = 2 (No such file or directory)'
This happens while I copied pic from album into app folder. The most interesting thing is that file exists, but not fully copied. Is there a way to check wether file is file operation completed?
check weather your file & Directory available
for (NSString *filename in files) {
NSString *path = [yourPath stringByAppendingPathComponent:yourFileName];
BOOL isDir;
if([[NSFileManager defaultManager] fileExistsAtPath:yourPath isDirectory:&isAvilDir] && isAvilDir){
NSLog(#"%# Check is a directory", your path file);
}
else {
NSLog (#"%# Check is a file",your path file);
}
}
I have a similar problem right now, and it seems as though when you delete a file that certain other methods you may have called immediately prior may actually not have completed yet. I'm considering delaying the actual deletion of files to allow for background processes to complete.
Solved it 2 yars ago. Forgot to post & close the question. It was caused by another thread, where the file was deleted first. I think its one of the standart mutlithreading issues while working with CoreData

How to slow down UIDocument's initial loadFromContents:ofType:error:?

When I drag a file package from Finder into the iTunes.app file sharing pane, my UIDocument class is trying to read in the associated file wrappers, and it appears my code to read it is executing faster than iTunes.app can copy the contents over. The initial file wrappers array contains only one of the two files inside the wrapper.
So how do I "slow" my code down?
I ran a test using performSelector:withObject:AfterDelay:1.0f, and that worked fine, but that feels really risky: What if a really large file is dragged in to iTunes (by really large I mean one that exceeds my delay)? What if multiple files are all dropped on at the same time?
I looked at somehow discerning that the file is ready to be read, but Apple's NSFileManager documentation says
"It's far better to attempt an operation (such as loading a file or
creating a directory), check for errors, and handle those errors
gracefully than it is to try to figure out ahead of time whether the
operation will succeed."
But where the timing problem comes up is during the decoding of the constituent file wrappers, so how to handle it "gracefully" is eluding me.
My package file has two data files within it (at this early stage, but the design is because there will be more): data.dat and info.dat. When I first got code working to the point where I could drag a file into iTunes.app and notice it in my view controller, data.dat was always decoding fine, but info.dat was not found. Once the file is inside my local documents folder, the view controller presents all the data as expected (i.e. info.dat is inside the file wrapper and correctly formed). Suspecting this to be a timing issue, I renamed the filename constants and on-disk files to zdata.dat and ainfo.dat -- sure enough: ainfo.dat loads and my UIDocument subclass complains that zdata.dat wasn't found.
I use lazy loading, but the view controller has an immediate interest in the info.dat contents, so lazy loading isn't lazy enough for iTunes to get through copying!
From my UIDocument subclass implementation:
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
self.fileWrapper = (NSFileWrapper *)contents;
// Lazy load everything!
_data = nil;
_metadata = nil;
return YES;
}
- (id)decodeObjectFromWrapperWithPreferredFilename:(NSString *)preferredFilename
{
NSFileWrapper *fw = [self.fileWrapper.fileWrappers objectForKey:preferredFilename];
if (!fw) {
NSLog(#"Unexpected error: Couldn't find %# in the file wrapper for %#", preferredFilename, self.fileURL);
return nil;
}
NSData *data = [fw regularFileContents];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
return [unarchiver decodeObjectForKey:#"data"];
}
- (GSBMetadata *)metadata
{
if (_metadata == nil) {
if (self.fileWrapper != nil) {
// NSLog(#"Loading metadata for %#...",self.fileURL);
_metadata = [self decodeObjectFromWrapperWithPreferredFilename:kGSBMetadataFileName];
} else {
_metadata = [[GSBMetadata alloc] init];
}
}
return _metadata;
}
The problem is discovered in decodeObjectFromWrapperWithPreferredFilename: when the check is made to ensure that the expected file wrapper is present. Normally this would be the case if the file was corrupt, or perhaps version 2 of the app used a different file format. But gracefully handling those circumstances fall under the heading of "paranoid programming expected that sort of thing" and not under the heading of "just wait a second and all your data will be available to you."

Resources