I have the same issue as this question where the iCloud ubiquity container is not being cleaned up when I delete the app.
But when I try to delete the ubiquity container I get an error message (The operation couldn’t be completed. (Cocoa error 513.)). How can I delete it?
This is what I'm using:
NSString *path = #"/private/var/mobile/Library/Mobile Documents/XXXXXX";
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
EDIT: Even if I delete the App on the device and all iCloud data (Settings App->iCloud->Storage & Backup->Manage Storage->App Name) there's still some data left over on the iCloud ubiquity container. This is the data I want to delete the first time the app is launched (in case the user re-installs the app).
You should be able to remove files INSIDE the ubiquity container by going to Settings App->iCloud->Storage & Backup->Manage Storage->App Name and then delete any files. I think you may only see files in the iCloud/Documents directory though so you may need code to clear anything else.
Alternately use a Mac and go to ~/Library/Mobile Documents and remove files there.
To get the iCloud container use this:
NSURL *iCloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:ubiquityID];
where ubiquityID is your apps iCloud container ID.
To list all files in the iCloud container use something like this passing in the iCloudURL
/*! Recursively lists all files
#param dir The directory to list
#param padding A string padding to indent the output depending on the level of recursion
*/
- (void)listAllFilesInDirectory:(NSURL*)dir padding:(NSString*)padding {
NSArray *docs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:dir includingPropertiesForKeys:nil options:0 error:nil];
for (NSURL* document in docs) {
FLOG(#" %# %#", padding, [document lastPathComponent]);
BOOL isDir;
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:document.path isDirectory:&isDir];
if (fileExists && isDir) {
[self listAllFilesInDirectory:document padding:[NSString stringWithFormat:#" %#", padding]];
}
}
}
And to delete stuff from the ubiquity container you need to user a fileCoordinator something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForDeleting
error:nil byAccessor:^(NSURL* writingURL) {
NSFileManager* fileManager = [[NSFileManager alloc] init];
NSError *er;
//FLOG(#" deleting %#", writingURL);
bool res = [fileManager removeItemAtURL:writingURL error:&er];
if (res) {
LOG(#" iCloud files removed");
}
else {
LOG(#" document NOT removed");
FLOG(#" error %#, %#", er, er.userInfo);
}
}];
}
Related
I want to copy a file from the iOS 11 Files app to my local app sandbox. For testing purposes it is assumed that the file is locally available in the Files app (downloaded from iCloud to the local storage). The file extension is registered with my app and when a file is pressed in the Files app then my app receives the file URL from the Files app:
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSURL *nsUrl; // comes from Files app. For instance "file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/test.rar"
NSURL *targetUrl; // file in my app's document directory
NSError *coordinatorError = nil;
[fileCoordinator coordinateReadingItemAtURL:nsUrl options:NSFileCoordinatorReadingWithoutChanges error:&coordinatorError byAccessor:^(NSURL *newURL)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
//if ([fileManager fileExistsAtPath: [nsUrl path]])
{
NSLog(#"Copy from %# to %#", newURL, targetUrl);
NSError *copyError = nil;
[fileManager copyItemAtURL:newURL toURL:targetUrl error:©Error];
if (!copyError)
{
// OK
}
else
{
NSLog(#"Files app error: %#", copyError);
}
}
}];
But the operation fails with this output:
2017-11-22 09:30:28.685127+0100 test[434:40101] Copy from file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/test.rar
to file:///var/mobile/Containers/Data/Application/01BB33E6-2790-0FD0-8270-000/Documents/test.rar
2017-11-22 09:30:28.687174+0100 test[434:40101] Files app error: Error Domain=NSCocoaErrorDomain Code=257 "The file “test.rar” couldn’t be
opened because you don’t have permission to view it."
UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/test.rar,
NSUnderlyingError=0x1c084abf0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
Is there something special required to get read access to the external file?
Regards,
Here is how you can access the files and stop when are done with it.
//To gain access
[nsUrl startAccessingSecurityScopedResource]
and
//To stop access
[nsUrl stopAccessingSecurityScopedResource]
i have the same issue to Copy file from iOS 11 Files app to sandbox . finally i solved my issue this link
check here
and the sample code .
[fileURL startAccessingSecurityScopedResource];//fileURL ---> Which FileURL you want to copy
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSFileAccessIntent *readingIntent = [NSFileAccessIntent readingIntentWithURL:fileURL options:NSFileCoordinatorReadingWithoutChanges];
[fileCoordinator coordinateAccessWithIntents:#[readingIntent] queue:[NSOperationQueue mainQueue] byAccessor:^(NSError *error) {
NSData *filePathData;
if (!error)
{
// Always get URL from access intent. It might have changed.
NSURL *safeURL = readingIntent.URL;
// here your code to do what you want with this
}
[fileURL stopAccessingSecurityScopedResource];
}];
I want to have my app save the documents it creates to iCloud Drive, but I am having a hard time following along with what Apple has written. Here is what I have so far, but I'm not for sure where to go from here.
UPDATE2
I have the following in my code to manually save a document to iCloud Drive:
- (void)initializeiCloudAccessWithCompletion:(void (^)(BOOL available)) completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.ubiquityURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (self.ubiquityURL != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"iCloud available at: %#", self.ubiquityURL);
completion(TRUE);
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"iCloud not available");
completion(FALSE);
});
}
});
}
if (buttonIndex == 4) {
[self initializeiCloudAccessWithCompletion:^(BOOL available) {
_iCloudAvailable = available;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:selectedCountry];
NSURL* url = [NSURL fileURLWithPath: pdfPath];
[self.manager setUbiquitous:YES itemAtURL:url destinationURL:self.ubiquityURL error:nil];
}];
}
I have the entitlements set up for the App ID and in Xcode itself. I click the button to save to iCloud Drive, and no errors pop up, the app doesn't crash, but nothing shows up on my Mac in iCloud Drive. The app is running on my iPhone 6 Plus via Test Flight while using iOS 8.1.1.
If I run it on Simulator (I know that it won't work due to iCloud Drive not working with simulator), I get the crash error: 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[3]'
Well, you've got me interested in this matter myself and as a result I've spent way to much time on this question, but now that I've got it working I hope it helps you as well!
To see what actually happens in the background, you can have a look at ~/Library/Mobile Documents/, as this is the folder where the files eventually will show up. Another very cool utility is brctl, to monitor what happens on your mac after storing a file in the iCloud. Run brctl log --wait --shorten from a Terminal window to start the log.
First thing to do, after enabling the iCloud ability (with iCloud documents selected), is provide information for iCloud Drive Support (Enabling iCloud Drive Support). I also had to bump my bundle version before running the app again; took me some time to figure this out. Add the following to your info.plist:
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.YOUR_BUNDLE_IDENTIFIER</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
<key>NSUbiquitousContainerName</key>
<string>iCloudDriveDemo</string>
</dict>
</dict>
Next up, the code:
- (IBAction)btnStoreTapped:(id)sender {
// Let's get the root directory for storing the file on iCloud Drive
[self rootDirectoryForICloud:^(NSURL *ubiquityURL) {
NSLog(#"1. ubiquityURL = %#", ubiquityURL);
if (ubiquityURL) {
// We also need the 'local' URL to the file we want to store
NSURL *localURL = [self localPathForResource:#"demo" ofType:#"pdf"];
NSLog(#"2. localURL = %#", localURL);
// Now, append the local filename to the ubiquityURL
ubiquityURL = [ubiquityURL URLByAppendingPathComponent:localURL.lastPathComponent];
NSLog(#"3. ubiquityURL = %#", ubiquityURL);
// And finish up the 'store' action
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) {
NSLog(#"Error occurred: %#", error);
}
}
else {
NSLog(#"Could not retrieve a ubiquityURL");
}
}];
}
- (void)rootDirectoryForICloud:(void (^)(NSURL *))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
if (rootDirectory) {
if (![[NSFileManager defaultManager] fileExistsAtPath:rootDirectory.path isDirectory:nil]) {
NSLog(#"Create directory");
[[NSFileManager defaultManager] createDirectoryAtURL:rootDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(rootDirectory);
});
});
}
- (NSURL *)localPathForResource:(NSString *)resource ofType:(NSString *)type {
NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *resourcePath = [[documentsDirectory stringByAppendingPathComponent:resource] stringByAppendingPathExtension:type];
return [NSURL fileURLWithPath:resourcePath];
}
I have a file called demo.pdf stored in the Documents folder, which I'll be 'uploading'.
I'll highlight some parts:
URLForUbiquityContainerIdentifier: provides the root directory for storing files, if you want to them to show up in de iCloud Drive on your Mac, then you need to store them in the Documents folder, so here we add that folder to the root:
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
You also need to add the file name to the URL, here I copy the filename from the localURL (which is demo.pdf):
// Now, append the local filename to the ubiquityURL
ubiquityURL = [ubiquityURL URLByAppendingPathComponent:localURL.lastPathComponent];
And that's basically it...
As a bonus, check out how you can provide an NSError pointer to get potential error information:
// And finish up the 'store' action
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) {
NSLog(#"Error occurred: %#", error);
}
If you are intending to work with UIDocument and iCloud, this guide from Apple is pretty good:
https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/Introduction/Introduction.html
EDITED:
Don't know of any better guide of hand, so this may help:
You will need to fetch the ubiquityURL using the URLForUbuiquityContainerIdentifier function on NSFileManager (which should be done asynchronously).
Once that is done, you can use code like the following to create your document.
NSString* fileName = #"sampledoc";
NSURL* fileURL = [[self.ubiquityURL URLByAppendingPathComponent:#"Documents" isDirectory:YES] URLByAppendingPathComponent:fileName isDirectory:NO];
UIManagedDocument* document = [[UIManagedDocument alloc] initWithFileURL:fileURL];
document.persistentStoreOptions = #{
NSMigratePersistentStoresAutomaticallyOption : #(YES),
NSInferMappingModelAutomaticallyOption: #(YES),
NSPersistentStoreUbiquitousContentNameKey: fileName,
NSPersistentStoreUbiquitousContentURLKey: [self.ubiquityURL URLByAppendingPathComponent:#"TransactionLogs" isDirectory:YES]
};
[document saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
}];
You'll also want to look into using NSMetadataQuery to detect documents uploaded from other devices and potentially queue them for download, and observing the NSPersistentStoreDidImportUbiquitousContentChangesNotification to find about changes made via iCloud, among other things.
** Edit 2 **
Looks like you are trying to save a PDF file, which is not quite what Apple considers a "document" in terms of iCloud syncing. No need to use UIManagedDocument. Remove the last 3 lines of your completion handler and instead just use NSFileManager's
setUbiquitous:itemAtURL:destinationURL:error: function. The first URL should be a local path to the PDF. The second URL should be the path within the ubiquiuty container to save as.
You may also need to look into NSFileCoordinator perhaps.
I think this guide from Apple may be the most relevant:
https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/iCloud/iCloud.html
I have a newsstand app which has magazines and uses the newsstand framework. I realized there was something wrong when deleting the magazines and/or when downloading them because when I accessed settings/usage my app keeps growing in memory usage when downloading and deleting the same magazine.
Found the issue... when downloading the issue in the delegate method:
-(void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
I just needed to add something like this at the end:
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[destinationURL path] error:&error];
if (error){
NSLog(#"ERROR:%#", error);
}
Even the directory is called "caches" you need to manually delete. Ok problem solved but what about the customers who already download my app and have tons of MBs dead in the cache directory.
I wanted to know how to get this directory and delete everything on it at launch and only once...
I can do it only once using a NSUserdefault but how do I get this directory and delete any zip files in it... an example of this directory and a file within is:
/private/var/mobile/Applications/1291CC20-C55F-48F6-86B6-B0909F887C58/Library/Caches/bgdl-280-6e4e063c922d1f58.zip
but this path varies with the device. I want to do this at launch so I'm sure there are no downloads in progress but any other solutions are welcome, thanks in advance.
Everything that you need is enumerate all files from Caches directory and remove ones that have zip extension:
- (void)removeZipFilesFromCachesDirectory {
static NSString *const kZIPExtension = #"zip";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *cachesDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSError *error = nil;
NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:cachesDirectoryPath error:&error];
if (error == nil) {
for (NSString *fileName in fileNames) {
NSString *filePath = [cachesDirectoryPath stringByAppendingPathComponent:fileName];
if ([filePath.pathExtension.lowercaseString isEqualToString:kZIPExtension]) {
NSError *anError = nil;
[fileManager removeItemAtPath:filePath error:&anError];
if (anError != nil) {
NSLog(#"%#", anError);
}
}
}
} else {
NSLog(#"%#", error);
}
}
I'm trying to copy a folder and it's contents to a sub-directory in the documentation directory and it's failing with the error:
"The operation couldn’t be completed. No such file or directory"
First I try to create a folder in the documentation directory like this:
NSString *diagramsDirectory = [docDirectory stringByAppendingPathComponent:#"Diagrams"];
if (![fileManager fileExistsAtPath:docDirectory isDirectory:&isDirectory] || !isDirectory)
{
NSError *error = nil;
NSDictionary *attr = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSFileProtectionKey];
[fileManager createDirectoryAtPath:diagramsDirectory
withIntermediateDirectories:NO
attributes:attr
error:&error];
if (error) {
NSLog(#"error creating dir. path: %#", [error localizedDescription]);
}
}
NSLog(#"diagrams directory = %#", diagramsDirectory);
The console log seems to indicate this works:
diagrams directory = /Users/../iPhone Simulator/../Library/Documentation/Diagrams
However, when I then try to copy a folder called "Diagrams" from a directory on the Mac:
NSString *pathToDirectories = #"/User/Desktop/Project Resource Files/Files/";
NSError *error = nil;
NSArray *folders = [fileManager contentsOfDirectoryAtPath:pathToDirectories error:&error];
for (NSString *folder in folders) {
if ([folder isEqualToString:#"Diagrams"]) {
[self copyFolderAtPath:folder toDestinationFolderAtPath:docDirectory];
}
which calls the "copyFolderAtPath" method:
- (BOOL)copyFolderAtPath:(NSString *)sourceFolder toDestinationFolderAtPath:(NSString *)destinationFolder
{
destinationFolder = [destinationFolder stringByAppendingPathComponent:[sourceFolder lastPathComponent]];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
// check for destination folder
if ([fileManager fileExistsAtPath:destinationFolder])
{
if (![fileManager removeItemAtPath:destinationFolder error:&error])
{
NSLog(#"Could not remove old files. Error: %#", error);
return NO;
}
}
error = nil;
// copy destination
if (!([fileManager copyItemAtPath:sourceFolder toPath:destinationFolder error:&error])) {
NSLog(#"failed copying file at path %# to path %#. Error %#", sourceFolder, destinationFolder, error);
return NO;
}
return YES;
}
it returns "no" and I get the error.
Anyone got an idea what I'm doing wrong?
The device (and therefore the simulator) is isolated from the operating system so you cannot directly do file system copies. Imagine even if it let you do it from the simulator, how would a disconnected device running your app access the OS filesystem?
You will have to look into other options like having an application on the mac that opens sockets or having an http end point on the mac that the device copies from. Other options include syncing documents via iCloud or another cloud service. You can also transfer files via iTunes. I'm sure there's many other options ... Also checkout this
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* url = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSString* directory = [url path];
NSString* filePath = [directory stringByAppendingPathComponent:FILE_NAME];
if ([fileManager fileExistsAtPath:filePath])
{
[fileManager removeItemAtPath:filePath error:nil];
}
Here's my code. When it is executed, the file is deleted, but the space remains occupied. Here's the code for storing something into the file.
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* url = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSString* directory = [url path];
NSString* filePath = [directory stringByAppendingPathComponent:FILE_NAME];
NSArray* oldArray = nil;
if ([fileManager fileExistsAtPath:filePath])
{
oldArray = [[NSArray alloc] initWithContentsOfFile:filePath];
[fileManager removeItemAtPath:filePath error:nil];
}
NSMutableArray* mergeArray = [[NSMutableArray alloc] initWithArray:arrayOfPersons];
[mergeArray addObjectsFromArray:oldArray];
if ( [mergeArray writeToFile:filePath atomically:YES]) NSLog(#"Written");
By the way, it cost 1 MB to store an array with only 1 object(an NSDictionary with 2 keys). Is there a cheaper way to store it?
You need to be much more careful with your experiments. The unix file system does lots of stuff with files. In fact, when you "delete" a file, all you do is unlink it from the file system. If that file is open with another file descriptor, anywhere in the OS, it will remain open.
Furthermore, there are lots of optimizations to reuse file nodes. Just because you delete a file, does not mean that space goes back automatically. It could be "reserved" in your app for several reasons, for other files to use. No sense giving it back to the file system until the file system needs it.
settings->general->usage is a very rough measurement of file system utilization. A better measurement would be accessing the attributes of the file and file system directly.
Using your code as a base, consider this:
- (NSString*)workingDirectory {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* url = [[fileManager URLsForDirectory:NSCachesDirectory
inDomains:NSUserDomainMask] lastObject];
return [url path];
}
- (NSString*)filePath {
return [[self workingDirectory] stringByAppendingPathComponent:FILE_NAME];
}
Now, you can see all the attributes of the entire file system with this:
NSDictionary *attributes = [[NSFileManager defaultManager]
attributesOfFileSystemForPath:[self workingDirectory] error:0];
NSLog(#"file system attributes: %#", attributes);
and those for the specific file with this:
NSDictionary *attributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:[self filePath] error:0];
NSLog(#"file attributes: %#", attributes);
Pay attention to NSFileSystemFreeSize and NSFileSize.
Run your app, and dump both of these values. Create your file, and dump them again. Delete the file, and dump them again.
After all that, you may actually see the NSFileSystemFreeSize go UP, even after the delete. Remember, the system itself is creating temporary files, and is probably caching those file system nodes for future use.
You can get more consistent results if you quit all other apps. Then, quit yours (double-click power button, X all running apps). Delete the file before doing this.
Now, start your app, without the file existing.
Dump file system data.
Create the file.
Dump file system data.
Dump file data.
You should see the file taking up about 200-250 bytes, and the file system free size should drop 8192.
Delete the file.
Dump file system data. Is probably at least as big as it was before deleting file.
Quit app (not in XCode -- double-click power, X the app).
Run the app.
Dump file system data. You should see the data back to about what it was when you started earlier.
In conclusion, while it may look like the file system has not released the data, it really has, but maybe the tool you are using to query just does not know the details of the file system.
Note, also, that when an app is running, it will use lots of file system resources for stuff that you are not explicitly doing.
I hope that made sense...
Your code to delete the file looks correct, but you are switching between URLs and Paths when you don't need to. You should also be checking for an error when you try to delete the file so that you can see why it doesn't work. Try this:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *directoryURLs = [fileManager URLsForDirectory:NSCachesDirectory
inDomains:NSUserDomainMask];
NSURL *directoryURL = [directoryURLs objectAtIndex:0];
NSURL *fileURL = [directoryURL URLByAppendingPathComponent:FILE_NAME];
if (!fileURL)
{
NSLog(#"Could not create URL for file.");
return;
}
NSError *err = nil;
if (![fileURL checkResourceIsReachableAndReturnError:&err])
{
NSLog(#"File is not reachable.\n"
"Error: %# %d %#", [err domain], [err code], [[err userInfo] description]);
return;
}
err = nil;
[fileManager removeItemAtURL:fileURL error:&err];
if (err)
{
NSLog(#"Unable to delete existing file.\n"
"Error: %# %d %#", [err domain], [err code], [[err userInfo] description]);
return;
}
May it be, that the length of the ˚FILE_NAME` is greater or equal to 300 chars? This brought me to similar issues with NSFileManager some time ago...