Re: Does "- replaceItemAtURL: withItemAtURL: backupItemName: options: resultingItemURL: error:" handle cross-volume moves?
Re: Does "- replaceItemAtURL: withItemAtURL: backupItemName: options: resultingItemURL: error:" handle cross-volume moves?
- Subject: Re: Does "- replaceItemAtURL: withItemAtURL: backupItemName: options: resultingItemURL: error:" handle cross-volume moves?
- From: Daryle Walker <email@hidden>
- Date: Tue, 31 Mar 2015 02:35:41 -0400
> On Mar 30, 2015, at 8:37 PM, Kevin Perry <email@hidden> wrote:
>
>
>> On Mar 30, 2015, at 5:17 PM, Daryle Walker <email@hidden> wrote:
>>
>>> On Mar 30, 2015, at 7:59 PM, Kevin Perry <email@hidden> wrote:
>>>
>>> -replaceItemAtURL:… relies on the atomicity of the POSIX rename() function in order to safely do its operation. Since rename() doesn’t work across volumes (returning EXDEV), the two URLs must be on the same volume.
>>>
>>> If you’re using a temporary directory with replaceItemAtURL:…, you want to use -[NSFileManager URLForDirectory:inDomain:appropriateForURL:create:error:] with NSItemReplacementDirectory, passing it the to-be-replaced item URL. This will get you an appropriate temporary directory on the target volume to ensure -replaceItemAtURL:… won’t fail due to rename's EXDEV.
>>>
>>>> On Mar 30, 2015, at 4:44 PM, Daryle Walker <email@hidden> wrote:
>>>>
>>>> NSFileManager’s “- moveItemAtURL: toURL: error:” method does a copy if the source and destination file-URLs are on different volumes. Does “- replaceItemAtURL: withItemAtURL: backupItemName: options: resultingItemURL: error:” do similar? The docs for the latter don’t mention it. I’m worried since those docs do mention temporary directories, that we have to do different-volume detection (possibly harder than in the pre-X days) and copying manually. I don’t want to do that, since Apple already includes that logic in the former method.
>>
>> So I have to: (1) call URLForDirectory… to make sure there’s an appropriate temporary directory, (2) call moveItemAtURL… to relocate the downloaded file from the volume NSURLSession used to the one made in the first step, and (3) call replaceItemAtURL… for the final move, right? Is there a way to easily determine if two file objects are in the same volume, to skip the second step if unneeded? (Or is there a way to tell NSURLSession which volume to put its temporary files in advance?…)
>
> I’m not personally familiar with NSURLSession, but don’t see any API to specify a volume/directory for the download destination.
>
> You can check if two URLs are on the same volume by comparing values for NSURLVolumeIdentifierKey. If they are on the same volume, you can probably skip both (1) and (2). The API doesn’t require a specific temporary directory to work.
Here’s my first pass:
> static
> BOOL CgMoveFileToDestinationTemporaryDirectory(NSURL *sourceTemporaryFile, NSURL *fileOnDestinationVolume, NSURL **newTemporaryFile, NSError **error) {
> NSCParameterAssert(sourceTemporaryFile);
> NSCParameterAssert(fileOnDestinationVolume);
> NSCParameterAssert(newTemporaryFile);
>
> // Don't move if the source and destination files use the same volume.
> id sourceVolume = nil, destinationVolume = nil;
>
> if ([sourceTemporaryFile getResourceValue:&sourceVolume forKey:NSURLVolumeIdentifierKey error:error] && [fileOnDestinationVolume getResourceValue:&destinationVolume forKey:NSURLVolumeIdentifierKey error:error]) {
> if ([sourceVolume isEqual:destinationVolume]) {
> *newTemporaryFile = sourceTemporaryFile;
> return YES;
> }
> // Else: move the source file across volumes, see below.
> } else {
> return NO;
> }
>
> // Move the source file to the destination file's volume's temporary directory.
> NSFileManager * const filer = [NSFileManager defaultManager];
> NSURL * const tempDir = [filer URLForDirectory:NSItemReplacementDirectory inDomain:NSUserDomainMask appropriateForURL:fileOnDestinationVolume create:YES error:error];
>
> if (!tempDir) {
> return NO;
> }
> *newTemporaryFile = [tempDir URLByAppendingPathComponent:sourceTemporaryFile.lastPathComponent];
> return [filer moveItemAtURL:sourceTemporaryFile toURL:*newTemporaryFile error:error];
> }
The last two arguments originally had “__autoreleasing” between the stars, but that messed up the in-line source checker and autocompletion. Removing the qualifiers fixed both problems. (The new completion went from not having any parameters, to showing all four, and inserting “__autoreleasing” itself.) The function is used a NSURLSessionDownloadTask completion handler like:
> NSURL * const plannedDestination = [NSURL fileURLWithPath:response.suggestedFilename isDirectory:NO];
> NSURL * stagedLocation = nil;
> NSURL * actualDestination = nil;
>
> NSCAssert(plannedDestination, @"Creating destination URL failed");
> if (CgMoveFileToDestinationTemporaryDirectory(location, plannedDestination, &stagedLocation, &error) && [[NSFileManager defaultManager] replaceItemAtURL:plannedDestination withItemAtURL:stagedLocation backupItemName:CgBackupFilename(plannedDestination.path.lastPathComponent) options:NSFileManagerItemReplacementUsingNewMetadataOnly | NSFileManagerItemReplacementWithoutDeletingBackupItem resultingItemURL:&actualDestination error:&error]) {
> gbprintln(@"%@", actualDestination.path);
> // To-do: Is there a way to find out if a backup file was needed and created?
> } else {
> gbfprint(stderr, @"Error, copying: %@", error.localizedDescription);
> gbfprintln(stderr, error.localizedFailureReason ? @" (%@)" : @"", error.localizedFailureReason);
> returnCode = EXIT_FAILURE;
> }
I was thinking of showing the path of the moved file (I already print the path of the new file on standard output.), if any, on standard-error, but I don’t know how to check if the backup file was needed and actually made (as per the to-do in the code). I know there’s a file-existence method, but I’ve read about warnings about using it due to hackers possibly causing stupid race condition tricks. But maybe I don’t need to worry about those tricks, since I’m not doing anything with the backup file afterwards. Or maybe I don’t need to worry about informing the user at all.
—
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden