I'm looking for a better way to download a bunch of files from my iCloud container to my sandbox. This is what I currently use:
for (FileAttachments *fileLink in items) {
NSURL *cloudFileURL = [fileLink urlForCloudAttachmentWithName];
NSURL *fileURL = [backupCloudLocalDirectoryURL URLByAppendingPathComponent: fileLink.fileName];
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateReadingItemAtURL:fileURL options:NSFileCoordinatorReadingWithoutChanges error:&error
byAccessor:^(NSURL *newURL) {
NSError *blockError = nil;
[fileManager copyItemAtURL:cloudFileURL toURL:fileURL error:&blockError];
}];
}
}
Is there any problem with making a copy of the iCloud item this way? In production, I have some users complaining that all their files weren't downloaded. Is it better to use NSFileManager's startDownloadingUbiquitousItemAtURL:error instead? If so, why?
Thanks.
It's still not completely clear to me from comments how you're discovering the URLs, however some important details that affect your situation are:
Using coordinateReadingItemAtURL:block: has nothing to do with downloading files from iCloud. The purpose of NSFileCoordinator is to coordinate among readers and writers of a file so that, for example, you don't get two threads trying to write to the same file at the same time. You use file coordinators with iCloud because the iCloud system needs to read and write files and so does your app. Using a coordinator avoids corrupting the file, but again, has nothing to do with downloading the file.
To download a file from iCloud you need to use startDownloadingUbiquitousItemAtURL:error: and then wait until the file downloads. The normal flow here is:
a. Use NSMetadataQuery to find files that exist in the iCloud account
b. Use startDownloadingUbiquitousItemAtURL:error: to make sure they're available on the local device.
The reason you need to use this call is simply because that's how iCloud works on iOS. Files don't download until you ask them to download, and this is how you ask. [On OS X it's different, everything automatically downloads.]
You cannot simply copy from an iCloud URL to another location unless you already know that the file has been downloaded. The file-copy operation does not download the file, so if the file isn't available locally, the copy will fail.
You must use a metadata query to identify the files and their download status then, if they have not been downloaded initiate the download, and using the metadata query determine when the download is complete and then copy the file from the ubiquity container to the apps sandbox directory using a file coordinator. If you try copying the file while it's partially downloaded you may get some strange results.
I had the same problem with you.
My case is that: When network disconnects, iCloud service copy file from iCloud container to sandbox. When this line executes, it can not go into the block to copy file. This is the reason why this file can not be copied.
fileCoordinator coordinateReadingItemAtURL:fileURL options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:^(NSURL *newURL)
My solution is: Before copying file from iCLoud container to sandbox, you must check network. If it is not available, don't execute this code (return method). If network connects, execute fileCoordinator and copy.
More info: When copy file from ICloud container to sandbox, method fileManager copyItemAtURL:toURL:error: is OK because I implement this method and it's good.
Hope this works.
Related
I'm developing an IOS app which download some video and save it, but after update app or reinstall it the directory of app change, and app can not access to previous downloaded files.
Is there any way to save downloaded file to location out of application domain directory?
the code for generating path to save file is.
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
I cannot verify the update part of your question.
I use the application's Documents all the time and after an update that is untouched, so I can still use any files added to it in a previous version.
For the reinstalling (i.e. first delete the app, then re-download it from the AppStore and run it) there is obviously no such way, unless you consider what ravi.p suggested in his comment (adding it to PhotosAlbum). That would beat the entire purpose of a reinstall. If a user deletes the app they want to delete all its data. That's why the warning dialog specifically makes that clear. If you could circumvent this would beat the intention, wouldn't it?
The previous Documents directory isn't changed in this case, it is deleted and on the reinstall a new one is created.
I didn't check it recently, but I believe even the full path of the Documents directory doesn't change after an update, including the hashed part that is created by the sandboxing mechanism. My guess is that you're either confusing something with the update process or simply get a different file name in the newer app version.
Edit:
Okay, so apparently the application's folders can change on an update, at least according to what you further explained in your comment. :)
Then I am wondering how you can find your sqlite file again, but in ay way there is an easy solution to your problem:
Do not save the full path in your database. That is bad practice anyways, since the method you already used to get it in the first place (after downloading the file) is meant to be used for accessing the folder. So you only save
[response suggestedFilename]
in your database (going from your posted code).
In places where you need to access the file you then simply rebuild your path in just the same way you do in the first line of code you posted.
You can even write a convenience method for this in some place (a model class or the class taking care about DB access).
And by the way: Again, updating and reinstalling are two different things, especially the process in which Xcode copies your development build onto your development device is a different one from how users ultimately update your app from the store.
If I save a file to the documents directory in a folder, how long does it stay on the disk? Forever (in that case I would have to delete it manually if I wish to do so) or it is deleted every time the app closes?
Files in the documents directory will persist until either the user deletes the app, resets their device or you remove the files in code. Also note however that the contents of the documents directory are backed up by iTunes so could persist longer than you would otherwise expect.
On the other hand, the tmp directory is not so persistent and its contents won't necessarily survive an app relaunch (they're also not backed up by iTunes) so you could use this if you don't want persistant storage. /Library/Caches/ is similar in that it isn't backed up but it is persistent. From the docs regarding tmp:
The system will periodically purge these files [in the tmp folder] when your app is not running; therefore, you cannot rely on these files persisting after your app terminates.
Basically, if you want to store something for a short period and it doesn't matter if it's deleted by the OS, use tmp. If you want something that will persist app launches and whatnot but still won't be backed up via iTunes, use the cache folder in Library. It you want something that persists and is backed up, use documents.
Extra info based on comment
If the images should disappear when the app is relaunched then you should use tmp. But take note that the tmp directory is not guaranteed to be cleared on an app relaunch. It's certainly possible that the images will still be there the next time you load the app. It's a bit of a gamble really. If it is vital to kill the images then wipe them manually somewhere suitable:
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
for (NSString *file in tmpDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#%#", NSTemporaryDirectory(), file] error:NULL];
}
I have been trying to retrofit a large app to have it store its files in iCloud. The app is not really "Document-based" but the files may be thought of as configuration or preference files.
To be honest I haven't followed all the Apple iCloud guidelines. Some really don't fit in with the flow of the app. When the app starts I read the files from the ubiquity directory (using normal file reads) and when I write them I use a normal file write to the ubiquity directory. When the app start, I also call:
[fileManager startDownloadingUbiquitousItemAtURL:url error:&error];
I do have an NSFilePresenter watching for changes in the ubiquity directory. It notifies me of file changes but there are never any conflict notifications.
My problem is that often when I upload a file to iCloud, it will create a separate file with a number appended. E.g.
MyFile.skyset
MyFile 2 .skyset
These seem to show up when more than one app has been writing MyFile.skyset to the ubiquity directory.
They don't seem to be conflicted file versions. If I use NSFileVersion to look for conflicts and other versions, I only see the one version of MyFile.skyset and it is not in conflict.
I can't find any documentation that explains what this "versioned" file is and what to do about it. Any thoughts as to what is going on here?
Well, I don't fully understand the "versions" but the problem was solved by changing how I updated the files in the ubiquity directory.
I changed from:
[fileMgr removeItemAtPath:dstPath error:nil];
[fileMgr copyItemAtPath:srcPath toPath:dstPath error:&error];
To:
NSData *data = [NSData dataWithContentsOfFile:srcPath];
[data writeToFile:dstPath atomically:NO];
I looks like the operation of deleting the file before copying the updated information to the ubiquity directory was confusing the system. After switching to the second form I'm no longer seeing this problem.
Hope this answer helps someone else.
I have an app that downloads a whole bunch of data from over 100+ APIs upon successful login. I successfully download the data, and then use iExplorer to extract the data container folders (Documents, Library and Tmp) from the fully loaded application.
I would like to take a blank version of the original app, in .ipa format, and insert those data container folders into that fully loaded .ipa file. Then I will be able to take this new fully loaded .ipa, and use a deployment software to deploy it to a bunch of local user's devices. So everyone will have this fully loaded app.
Please, has anyone done this? Please provide some feedback, and don't argue with my methodology, because this has be done this way due to requirements. Maybe there is a step I'm missing? I'm not sure.
With the source code in hand, you can run the app in the simulator (no need for iExplorer), wait for it to download all the files and browse to the folder on your computer where the app was installed.
From there you can put aside any files you want along with their respective folders. If you're using Coredata there should be a SQLITE database file there somewhere (typically in your Application Support folder) and this might be all you need but it is hard to tell without looking at your implementation details.
Once you have the files you need set aside, add them to the app bundle via Xcode and create code to check whether files already exist (in which case you don't want to replace them), and if not copy all files needed from the bundle into their respective folders.
Here's some semi pseudo-code for you:
NSDictionary *userPrefs = [[NSUserDefaults standardUserDefaults] objectForKey:self.email];
if (![userPrefs[kInitialSetupCompleted] boolValue])
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *destinationFilePath = ...
NSURL *seedFilePath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:kCoreDataStoreName ofType:nil]];
NSError* err = nil;
if (![fileManager copyItemAtURL:seedPath toURL:destinationFilePath error:&err]) {
NSLog(#"Could not copy seed data. error: %#", err);
// Deal with error
} else {
// Set user defaults kInitialSetupCompleted to YES
}
}
Does the Dropbox Sync API allow for copying directories or files from Dropbox into the app sandbox directories?
I was excited to use the Dropbox Sync API and use Dropbox to manage media assets between my OS X machine and an iPad running my app.
I integrated the Sync API pretty quickly and am able to open files and save them in to Dropbox again.
Because my app is already set up with many many calls to dynamically load media assets, it would add unnecessary complexity to try to open all those assets from the Dropbox filesystem. My intention was to use the Sync API to determine if there are updated assets and then copy them in to one of my application directories.
But it seems that nearly all the objects returned using the Sync API are DBObjects, and I can't perform normal NSFilesystem file operations such as copy. Also the DBObjects seem to have no copy functions themselves.
Is there any way to do this without resorting to the CorecAPI?
Example, for an arbitrary file type called story
//get a list of stories and put them in an array
DBPath *storyPathDB = [[DBPath root] childPath:#"/storyBundlesDB"];
NSArray *listOfStoriesDB = [[DBFilesystem sharedFilesystem] listFolder:storyPathDB error:nil];
for (DBFileInfo *story in listOfStoriesDB)
{
//now I can list out the path of my stories, but that's about it...
DBPath *storyPath = story.path;
NSLog(#"%#",storyPath.stringValue);
//How can I treat these stories whether they are directories or .mov, .png, and copy them to my app directory?
}
I saw this question here Data syncing with DropBox API and iOS where people suggested using the SyncAPI for exactly this purpose, but it seems as if noone on that thread had actually tried it.
You could use readData to read the contents of the file and then write it to another location, if you can't directly use the DBFile objects.
Note that if you want to get notifications when a file (in Dropbox) changes, you'll need to hold that file open. Essentially, files are only updated when you hold the file open, get a notification that there's newer content available, and then call update.