> 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