> On Mar 30, 2015, at 8:37 PM, Kevin Perry <kpe...@apple.com> wrote:
> 
> 
>> On Mar 30, 2015, at 5:17 PM, Daryle Walker <dary...@mac.com> wrote:
>> 
>>> On Mar 30, 2015, at 7:59 PM, Kevin Perry <kpe...@apple.com> 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 <dary...@mac.com> 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 (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to