NSFileManager’s copyItemAtPath method does not overwrite an existing file and instead returns an error. So, to overwrite an existing file, it seems reasonable to first employ removeItemAtPath, and that’s the approach employed in the script below.
This all seems reasonable until the copy fails for some reason. Then, the script has deleted the existing file and can’t copy the new file. I can only think of two alternatives. The first is to rename the existing file and then to rename it back if the copy is not successful. The second is to move the existing file to NSTemporaryDirectory and then to move it back if the copy fails. Is there a better way to handle this? Thanks.
use framework "Foundation"
use scripting additions
set theFile to POSIX path of (choose file)
set theFolder to POSIX path of (choose folder)
copyFile(theFile, theFolder)
on copyFile(POSIXPath, folderPath) -- from Shane's book with modification
set POSIXPath to current application's NSString's stringWithString:POSIXPath
set folderPOSIXPath to current application's NSString's stringWithString:folderPath
set theName to POSIXPath's lastPathComponent()
set newPath to folderPOSIXPath's stringByAppendingPathComponent:theName
set fileManager to current application's NSFileManager's defaultManager()
-- remove existing file - not in Shane's script
if (fileManager's fileExistsAtPath:newPath) then (fileManager's removeItemAtPath:newPath |error|:(missing value))
set {theResult, theError} to fileManager's copyItemAtPath:POSIXPath toPath:newPath |error|:(reference)
if (theResult as boolean) is false then
error (theError's localizedDescription() as text)
end if
end copyFile
It looks like the “replaceItemAtURL:withItemURL:backupItemName:resultingItemURL:error:” NSFileManager function might be the better way to do it, although I’ve not tried it myself, but a previous forum posting here shows an example of how to use it.
Thanks Mark for the response. I tested that method and it works just as I want but it does a move rather than a copy. I guess I could copy the file back from the destination to the source but that doesn’t seem very good. I’ll have to give this some more thought.
My test script:
use framework "Foundation"
use scripting additions
set sourceFile to POSIX path of (path to desktop) & "Test File.txt"
set destinationFile to POSIX path of (path to documents folder) & "Test File.txt"
copyAndReplace(sourceFile, destinationFile)
on copyAndReplace(sourceFile, destinationFile)
set sourceURL to current application's |NSURL|'s fileURLWithPath:sourceFile
set destinationURL to current application's |NSURL|'s fileURLWithPath:destinationFile
set filemanager to current application's NSFileManager's defaultManager
set {theResult, theError} to filemanager's replaceItemAtURL:destinationURL withItemAtURL:sourceURL backupItemName:(missing value) options:0 resultingItemURL:(missing value) |error|:(reference)
end copyAndReplace
The suggested safe approach is to create a temporary directory, copy to that, use replaceItemAtURL::::: with the copied version, then delete the temp directory. You use URLForDirectory:inDomain:appropriateForURL:create:error: to create the (NSItemReplacementDirectory) directory, which will usually be in the same directory as the target.
This is the handler from my FileManagerLib library:
-- This handler is called by other handlers
on atomicReplaceFromURL:sourceURL toURL:destinationURL
set theFileManager to NSFileManager's |defaultManager|()
set {tempURLDir, theError} to theFileManager's URLForDirectory:99 inDomain:1 appropriateForURL:destinationURL create:true |error|:(reference) -- 1 = NSUserDomainMask, 99 = NSItemReplacementDirectory
if tempURLDir is missing value then error (theError's |localizedDescription|() as text) number (theError's code() as integer)
set tempDestURL to tempURLDir's URLByAppendingPathComponent:(destinationURL's lastPathComponent())
set {theResult, theError} to (theFileManager's copyItemAtURL:sourceURL toURL:tempDestURL |error|:(reference))
if not theResult as boolean then
theFileManager's removeItemAtURL:tempURLDir |error|:(missing value)
error (theError's |localizedDescription|() as text) number (theError's code() as integer)
end if
set {theResult, theError} to theFileManager's replaceItemAtURL:destinationURL withItemAtURL:tempDestURL backupItemName:(missing value) options:1 resultingItemURL:(missing value) |error|:(reference) -- 1 = NSFileManagerItemReplacementUsingNewMetadataOnly
theFileManager's removeItemAtURL:tempURLDir |error|:(missing value)
-- if replacement failed, return error
if not theResult as boolean then error (theError's |localizedDescription|() as text) number (theError's code() as integer)
end atomicReplaceFromURL:toURL: