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

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);
}];

Related

PFFile and JSON?

In my chatting application, I'm using Parse for a user table, getting ID's, images, etc. I recently added this functionality, and I have encountered a problem. When I send a message, I create an NSDictionary with information about the message such as time, message, sender, sender objectId, etc. But, when I try to add the PFFile (image file) associated with the user, I get an error saying that PFFile cannot be converted to JSON (PubNub message format). How can I add PFFile as part of the NSDictionary used in the message to be compatible with JSON, or there might be another way.
I'm not familiar with asynchronous tasks, but in my code, I have a method - (NSDictionary *)parseMessageToDisplay:(NSDictionary *)message {} where the input would be message received from PubNub, and it would return a format better united to be displayed in a UITableView. If I added the ID of the file or user to my dictionary, how could I get my image in UIImage or NSData, and return it from my method in an NSDictionary. Sorry if this post seems long, just trying to provide a lot of information.
In order to use parse.com, PFFile in particular, you'll probably want that NSDictionary to be a PFObject instead. A PFFile reference can be saved as an attribute of a PFObject -- in fact that's the only way it can be saved.
Thanks to #danh for this suggestion, but you really saved me. Apparently Parse creates a URL for all PFFiles and I can just send that URL (NSString *) with my NSDictionary to PubNub, and then in my - (NSDictionary *)parseMessageToDisplay:(NSDictionary *)message method just use [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURLString]]; and get data from that. YAY! No long running confusing asynchronous tasks to made my day terrible!

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.

Image within PFFile will be uploaded to parse instead of being saved into the local datastorage

I am using swift with the Parse Framework (1.6.2). I am storing data into the local datastorage. The data consists of a few strings and an image. This image will be stored within a PfFile and then will be added to the PfObject.
Now I wondered why the image doesn't get saved until I noticed I needed to call the save() method of the PFFile. Problem is that the Image then seems to be uploaded to Parse which I don't want. I want it to be stored on the local device..
Is there a solution for this?
Thanks
Edit:
The code works like this:
var spot = PFObject(className: "Spot")
let imageData = UIImageJPEGRepresentation(spotModel.getPhoto(), 0.05)
let imageFile = PFFile(name:"image.jpg", data:imageData)
spotObj.setObject(spotModel.getCategory(), forKey: "category")
spotObj.setObject(imageFile, forKey: "photo")
//if I simply would do this, the PFObject will be saved into the local datastorage, but the image won't be saved
spotObj.pin()
//if I do this, the image will be saved also BUT it won't be saved into the local data storage but will be uploaded to parse
imageFile.save()
spotObj.pin()
Okay, take a look here Save eventually on PFObject with PFFile (Parse Local Datastore)?.
One this is for sure, if you call save on PFFile it will get save to online datastore. So you should use PFFile.save(). I think best option for you is to save the file in some folder. locally and save that path in PFObject. Parse documentation just say this
"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."
It recursively calls .pin() on the other objects in your main PFObject. But if you take a look at PFFile API doc it doest have a .pin() which means it doesn't support saving PFFile to local datastore. So I would say you should save them in some directory and save path in your PFObject.
Update
save:
Saves the file synchronously and sets an error if it occurs.
- (BOOL)save:(NSError **)error
Parameters
error
Pointer to an NSError that will be set if necessary.
Return Value
Returns whether the save succeeded.

Parse backend new Error for uploading files - Error: File name must be a string - PFFile

I'm building an IOS app and using parse.com as a backend. All of a sudden, today I am getting this error - Error: File name must be a string without any other explanation. I'm certain the file name that I am using is a string. I haven't changed any of the Native IOS code so it must be a new Parse issue. The only difference - today I updated my account to the new pricing model on Parse, wondering if that's related?
// Code snippet
NSString *fileName = #"test.mov"; // Also tried just #"test"
PFFile *file = [PFFile fileWithName:fileName data:fileData]; // NSData - 286553 bytes
[file saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// Break point right here - this is where the error occurs.
NEWS:
ALL should go to: https://developers.facebook.com/bugs/622479264497355/?comment_id=1426426400945705
And post your account email to Hector Ramos.
Update2:
This has been fixed and is preparing for a deploy.
The problem is: Basically, if you are currently using LESS than 1GB of data storage (for example, I'm using 679MB), instead of correctly calculating that I'm using (679 / (1024MB * 20)) = 3.3% of the quota, its calculating it as 679/20, which gets the result of 3395%. Parse, if you see this, PLEASE... its really just a few lines of code.. please resolve this ASAP!
This was an internal error and has been resolved. Parse posted an incident report here.
EDIT: As several people have pointed out, the error is still present even though Parse claims it has been resolved. Hrmmmmmm...

NSFileWrapper fails when from iCloud and works from local directory

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)

Resources