I have a basic Cocoa app that let's the user manage a list of files. Files are added via drag & drop and I persist the access permissions in a secure bookmark across application restarts.
So far so good. The app is allowed to read and write to the user's files, but renaming fails with a permission error claiming that my app is not allowed to access the parent folder.
Code:
[[NSFileManager defaultManager] moveItemAtPath:currentPath
toPath:newPath error:&error]
Error:
Error Domain=NSCocoaErrorDomain Code=513 "“Some image.jpg” couldn’t
be moved because you don’t have permission to access “some folder”
I swear that this used to work just yesterday, nothing changed... (see update below)
Anyway. I would assume that if a user allows access to a file via an Open dialog or drag & drop that a sandboxed application should be allowed to rename the file.
UPDATE:
I finally managed to track this down to a change in my code:
I can reproduce the error when I call NSFileCoordinator.item(at: newSrcUrl, willMoveTo: newDstUrl)
before performing the actual move. If I skip this step, everything works again without any errors in the console.
The full code:
let coordinator = NSFileCoordinator()
coordinator.coordinate(writingItemAt: srcURL, options: .forMerging,
writingItemAt: dstURL, options: .forMerging) { newSrcUrl, newDstUrl in
// Calling this first results in "could not get a sandbox extension" error in console:
//coordinator.item(at: newSrcUrl, willMoveTo: newDstUrl)
try moveItem(at: newSrcUrl, to: newDstUrl)
coordinator.item(at: newSrcUrl, didMoveTo: newDstUrl)
}
item(at oldURL: URL, willMoveTo newURL: URL)
seems to be intended when changing the document's filename extension (e.g. .rtf to .rtfd), which is not the case for my app. Only the file name changes, not the file extension. I figured it would be best practice to call the will/didMove
methods, but looks like there are side effects.
To recap, all my app does is to ask the user to open a folder (via NSOpenPanel
so my app gets permission to write to that folder), then my app creates a file in that folder and later on renames it.
The problem you are facing is not about persisting anything, it's about how sandbox works :
The rule is…
To rename a file (in fact perform a move operation),
you need to have writing access to the parent directory of said file.
Our problem is…
If you drag files, sandbox will only extend access to these files, not their parent dir, hence this error saying it needs permission
Now, try dragging the folder containing these files, and you'll find that everything just works :)
So what do we do ?
A simple solution would be to ask the user to pick via NSOpenPanel a "working" directory, so that sandbox has access without alienating the user every time he wishes to rename
But we're now bothering the user for crap he shouldn't even know about in the first place !
So to me that's bad design/ux
Now i've read the docs about sandbox and i noticed NSFileCoordinator had a method called itemAtURL:willMoveToURL:
Wich led me to this little snippet (that i rewrote here because it was missing the willMove function)
what we want to do here is ask for a sandbox extension for the sake of renaming:
NSURL *sourceURL = document.fileURL;
NSURL *destinationURL = [[sourceURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:fileName isDirectory:NO];
NSError *writeError = nil;
__block NSError *moveError = nil;
__block BOOL success = NO;
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[coordinator coordinateWritingItemAtURL:sourceURL
options:NSFileCoordinatorWritingForMoving
writingItemAtURL:destinationURL
options:NSFileCoordinatorWritingForReplacing
error:&writeError
byAccessor:^(NSURL *newURL1, NSURL *newURL2)
{
NSFileManager *fileManager = [NSFileManager new];
[coordinator itemAtURL:sourceURL willMoveToURL:destinationURL];
success = [fileManager moveItemAtURL:newURL1 toURL:newURL2 error:&moveError];
if (success)
{
[coordinator itemAtURL:newURL1 didMoveToURL:newURL2];
}
}];
Sadly, it seems this method is only targeted at changing the file extension rather than renaming, hence this error in the log :
NSFileSandboxingRequestRelatedItemExtension: an error was received from pboxd instead of a token. Domain: NSPOSIXErrorDomain, code: 1
Yay, apple, yay
The following works for me as long as I add myext
as a related item document type in my Info.plist
. This is noted in Apple's Documentation.
Here's an excerpt:
In both scenarios, you must make a small change to the application’s Info.plist file. Your app should already declare a Document Types (CFBundleDocumentTypes) array that declares the file types your app can open.
For each file type dictionary in that array, if that file type should be treated as a potentially related type for open and save purposes, add the key NSIsRelatedItemType with a boolean value of YES.
Code modified from BenRhayader's answer:
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSURL *sourceURL = chosenFile;
NSURL *destinationURL = [chosenFile URLByAppendingPathExtension: @"myext"];
[coordinator coordinateWritingItemAtURL:sourceURL
options:NSFileCoordinatorWritingForMoving
writingItemAtURL:destinationURL
options:NSFileCoordinatorWritingForReplacing
error:NULL
byAccessor:^(NSURL *newURL1, NSURL *newURL2)
{
NSFileManager *fileManager = [NSFileManager new];
[coordinator itemAtURL:sourceURL willMoveToURL:destinationURL];
NSError *moveError;
BOOL success = [fileManager moveItemAtURL:newURL1 toURL:newURL2 error:&moveError];
if (success)
{
[coordinator itemAtURL:newURL1 didMoveToURL:newURL2];
}
}];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With