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

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."

Related

Xcode - iOS - Simply upload a file to FTP Server

I'm trying to work with FTP Servers.
I have googled around for everything and everything is hard to understand for beginners like me. SimpleFTPSample is hard to understand because it is so much at a time. views, buttons, labels, textflelds, upload, download, request, list, get. Same with BlackRaccoon and everything else.
How to Simply and programily upload "test.txt" to FTP Server: "192.168.1.111" in Xcode (iPhone app) without views or button. Just code that can be in the ViewDidLoad for example.
Maybe something like this?:
NSURL* url = [NSURL URLWithString:#"ftp://username:pw#189.92.32.34"];
CFReadStreamRef stream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url);
stream.delegate= self;
[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[stream open];
but which file?
expand this, or write a new code. i don't know, this is new for me.
Thanks Jonathan
As the writer of Black Raccoon perhaps I'm biased (well, I KNOW I'm biased), but I've attempted to make it as simple and powerful as possible. Let's look at what you want to do, upload a file:
There are four things we need to upload a file - start up code, then four delegate methods: overwrite check, data, success and the fail. Let's assume that you read the entire file into memory (okay for small files less than 2 megs).
First, you need this in your header:
BRRequestUpload *uploadData; // Black Raccoon's upload object
NSData *uploadData; // data we plan to upload
Now for the code part:
- (IBAction) uploadFile :(id)sender
{
//----- get the file path for the item we want to upload
NSString *applicationDocumentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filepath = [NSString stringWithFormat: #"%#/%#", applicationDocumentsDir, #"file.text"];
//----- read the entire file into memory (small files only)
uploadData = [NSData dataWithContentsOfFile: filepath];
//----- create our upload object
uploadFile = [[BRRequestUpload alloc] initWithDelegate: self];
//----- for anonymous login just leave the username and password nil
uploadFile.path = #"/home/user/myfile.txt";
uploadFile.hostname = #"192.168.1.100";
uploadFile.username = #"yourusername";
uploadFile.password = #"yourpassword";
//----- we start the request
[uploadFile start];
}
The first will be asking your code if you want to overwrite an existing file.
-(BOOL) shouldOverwriteFileWithRequest: (BRRequest *) request
{
//----- set this as appropriate if you want the file to be overwritten
if (request == uploadFile)
{
//----- if uploading a file, we set it to YES (if set to NO, nothing happens)
return YES;
}
}
Next, Black Raccoon will ask you for chunks of data to send. If you have a very large file you NEVER want to try to send it all in one shot - Apple's API will choke and drop data. However, we only have one small chunk so we do this:
- (NSData *) requestDataToSend: (BRRequestUpload *) request
{
//----- returns data object or nil when complete
//----- basically, first time we return the pointer to the NSData.
//----- and BR will upload the data.
//----- Second time we return nil which means no more data to send
NSData *temp = uploadData; // this is a shallow copy of the pointer
uploadData = nil; // next time around, return nil...
return temp;
}
Remember we can ONLY do this for a small file.
Next we have our completion handler (if things worked according to plan):
-(void) requestCompleted: (BRRequest *) request
{
if (request == uploadFile)
{
NSLog(#"%# completed!", request);
uploadFile = nil;
}
}
Lastly we have our failure handler:
-(void) requestFailed:(BRRequest *) request
{
if (request == uploadFile)
{
NSLog(#"%#", request.error.message);
uploadFile = nil;
}
}
It would be WONDERFUL if it was as simple as saying [BRFtpUploadTo: dest srcfile: srcFile destfile: dstFile] but there are many reasons why you SHOULDN'T. Part of it has to do with how Apple has implemented their internal FTP. There are also the issues of blocking, errors, etc. In the end, FTP sounds like it should be trivial but ends up being a bit of a nightmare.
FTP is non-trivial which is why there are so many implementations. I'm not arguing that Black Raccoon is the best, but it is maintained with response to issues being between minutes to a couple of days.
It may look daunting at first, but Black Raccoon is, in my opinion, one of the better FTP libraries. I've spent a lot of time and effort to make it a quality product with excellent response to issues. How do I do this for free? Volume. ;)
Good luck with whatever FTP software you end up with!
Upload path is required when uploading. That is the way FTP works.
The port is the standard FTP port. I know of no way to change this without violating the API. If you figure it out, you stand a good chance of not passing Apple's check.
This code will upload/download any file.
I do not know how to make this work under secure conditions. This uses Apple's FTP protocol. There are other FTP packages that have built this from scratch and are far more intelligent. I would look into them.
BR was designed because I needed simple FTP communication. White Raccoon didn't do this for me because at the time (it has since been modernized).

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)

Invalidating QLPreviewController "cache"

QLPreviewController seems to cache file contents based on the local file's URL. In my application, the file contents can be updated remotely and would cause the new contents to be downloaded.
If I view a file in QLPreviewController, update it remotely, then re-preview it, the file does not show up as updated.
The file is definitely updated on disk, and other controls show the correct updated file.
The workaround I'm using right now is to basically move a file when it's previewed to a unique filename (using timestamp), which will of course not be in the QLPreviewController's cache. However, this has other repercussions, for example, if the app is killed or it crashes (god forbid), I won't know "where" to find the downloaded file.
I'm looking for less invasive hacks, or solutions to making QLPreviewController refresh its cache. The APIs don't seem to expose anything, so don't be afraid to submit a hack if it's less gross than the one I've presented above (not including copying/moving the file to a guaranteed unique URL, which I am already utilizing).
Just ran into this issue myself. I solved it by recreating the QLPreviewController each time I reload an item with the same name as the currently viewed item. Creating a new QLPreviewController clears the cache.
I know this is an old question but someone might have the same problem and find this answer helpful.
You should use refreshCurrentPreviewItem after downloading complete
I had the same problem. Opening a locally generated CSV file.
I have my _previewController* setup as a #property of my controller. Then what i did:
- (void)viewDidLoad
{
[super viewDidLoad];
self.previewController = [[QLPreviewController alloc] init];
_previewController.delegate=self;
_previewController.dataSource=self;
}
- (void)previewCSV
{
[_previewController reloadData]; // this triggers a reload
[self presentModalViewController:_previewController animated:YES];
}
IN other solution that comes to mind (not tested).
Depending on your URL, you could add something like http://url?time=123456 to your URL. Like this you change the URL but without side effect. The time (or any other parameter) you can change on each request.
It's the ugliest bug in iOS. Cache management in iOS 5 and beyond. I think is the same reason that makes iCloud buggy, Share-at-Home crashing and so on. Bad cache managements and so worst synchronization systems.
Well, my solution for this was to store the download file in a folder and use the current date to name the folder. It is equivalent to #Rogier's solution, but this works always. You get a name for the folder, for example, with [[NSDate date] description]. Instead of saving the file replacing the old one, you delete previous file, delete previous folder and save new file in a new folder. It's working fine for me.
Just remove all files from tmp directory like this:
- (void)clearCache
{
NSString *tempPath = NSTemporaryDirectory();
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tempPath error:nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (int i = 0; i < [dirContents count]; i++) {
NSLog(#"Directory Count: %i", [dirContents count]);
NSString *contentsOnly = [NSString stringWithFormat:#"%#%#", tempPath, [dirContents objectAtIndex:i]];
[fileManager removeItemAtPath:contentsOnly error:nil];
}
}

Does NSFileWrapper load everything into memory?

Lets say I have an NSFileWrapper directory. This directory is made up of several levels of directories and files. Some of the files are large. Are all these files loaded into memory, or are they lazily loaded?
If they are loaded into memory, are there any alternatives to NSFileWrapper with similar functionality that won't load files into memory? Something I can hook into UIDocument?
This is for a document based application, which uses UIDocument's that are synced with iCloud. A document can have images and videos embedded inside it. Each image/video has a preview image (thumbnail) that is shown in an HTML document. The full size images or videos shouldn't be loaded into memory, but rather loaded on demand.
I also need a way to add a resource without loading it into memory. Something like "initWithAsset:(ALAsset *)" would be ideal.
I've made an app a while ago that generates a video. This video was then saved to a specific file format using a UIDocument subclass.
The only way to make the app not run out of memory while executing contentsForType:error: was to output the video to a file in the tmp dir and init the filewrapper with the url to the video with NSFileWrapperReadingWithoutMapping-option to prevent it from loading the video to memory and just copy in the file.
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError {
if (self.fileWrapper == nil) {
self.fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
}
if (self.videoURL != nil) {
NSError *fileReadError;
NSFileWrapper *videoFileWrapper = [[NSFileWrapper alloc] initWithURL:self.videoURL options:NSFileWrapperReadingWithoutMapping error:&fileReadError];
if(fileReadError){
NSLog(#"File read error: %#", [fileReadError localizedDescription]);
}else {
[videoFileWrapper setPreferredFilename:#"video.mov"];
[self.fileWrapper addFileWrapper:videoFileWrapper];
}
}
//...
}
It's not totally clear from the documentation, but I'm 99% positive that NSFileWrapper will memory-map everything. The main use case for NSFileWrapper is to embed files inside documents, where you'd presumably need access to said file at all times.
What are you trying to use the file wrapper for? I assume if you're mapping an entire directory it's not necessarily to embed media inside a document, but perhaps I'm mistaken. If you maybe talk a bit more about your use case, as that will influence what alternatives you might go for.

stringWithContentsOfFile and initWithContentsOfFile return null after several runs

I am creating an iOS app which reads in a text file and displays the contents in a UIText field.
For the 1st three consecutive runs of thee app (Restarting a new session without exiting),
the data is read in fine. However on the fourth attempt, the data returned from the file is all nulls.
I've verified the file integrity. The issue exists when using stringWithContentsOfFile or initWithContentsOfFile.
After many hours of troubleshooting, I believe the issue is somehow related to a buffer being cleared within the above mentioned methods.
Any insight regarding this issue is greatly appreciated. I've tried many things with no luck.
Here's the code I use to read in the file:
TheString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:#"My_TextFile" ofType:#"txt"] encoding:NSUTF8StringEncoding error:NULL];
Here's the code I use to display certain contents of the file (The contents are placed in an array of type NSArray):
NSArray *My_Array;
My_Array= [TheString componentsSeparatedByString:#"\n"];
/* Obtain specific data to display */
DisplayedData = [My_Array objectAtIndex:M[l]-1];
:
:
/* Display the data in the view */
MyUITextView.text = DisplayedData;
/* Log the data */
NSLog(#"%#", MyUITextView.text);
On the 4th invocation of the code above, the data returned is blank and NSLOG is returning nulls
Thanks so much for any help!
Maybe I'm a little bit late with answer, but, anyway, maybe somebody will find it useful.
OK, I have also spent a day trying to figure out why my custom class for scrollable view is working 3 times and refuse at the 4-th time... I found that the problem has quite the same attributes as yours: nested NSString objects unexpectedly disappear. Though pointers point to the same address in memory, memory is already filled with quite arbitrary objects instead my NSStrings.
And I paid attention that I created these NSStrings using the following class method:
+ (id)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
So, I'm not the owner of these NSStrings.
And I assumed that to be the owner can be a solution, so I created my NSStrings through alloc and
- (id)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
instance method.
App was repaired!

Resources