Search code examples
applescript-objc

How to Overwrite all files in a folder?


I am using the following to copy a file:

on replace:sender

        set allFiles to choose file with prompt "Please select your file:" with multiple selections allowed
        repeat with theFile in allFiles

            tell application "Finder"
                set {name:fileName, name extension:nameExtension, file type:fileType} to theFile
                if fileType is "png" or nameExtension is "png" then
                    set myPng to "~/Documents" & fileName as POSIX file


                    else
                    delay 0.2
                    tell current application's NSAlert's alloc's init()
                        its setMessageText:"Replace Photo"
                        its setInformativeText:"The file \"" & fileName & "\" is not a PNG file!"
                        its setAlertStyle:2
                        its setShowsSuppressionButton:false
                        its beginSheetModalForWindow:theWindow completionHandler:(missing value)
                        return
                    end tell
                end if

                if exists myPng then
                    delay 0.2
                    tell current application
                        display alert"There is already an older item named \""& fileName &"\" at this location." message ¬
                        "Do you want to replace it with the newer \"" & fileName & "\" one you're moving?" buttons {"Cancel", "Replace"} default button "Replace" as critical

                        set response to button returned of the result
                        if response is "Replace" then
                            delay 0.2
                            do shell script "rm -rf " & quoted form of POSIX path of myPng & space & "~/Documents" & myPng with administrator privileges
                            do shell script "mv " & quoted form of POSIX path of theFile & space & "~/Documents" with administrator privileges
                        end if

                        if response is "Cancel" then
                            return
                        end if
                    end tell
                    else
                    do shell script "mv " & quoted form of POSIX path of theFile & space & "~/Documents" with administrator privileges
                end if
            end tell
        end repeat
    end replace:

If there is a file with the same name in the target folder the user receives an alert, but what is happening is that with each copied file this alert is displayed, I would like to show this alert only once, just as it is done by macOS and then if the user clicks the "Replace" button all files will be replaced at once.


Solution

  • Your script doesn't compile so it didn't allow me to test it to really get a feel for what you're doing, so I hope I'm in the ball park with what I've come up with below.

    But, aside from that, I'll be blunt: your script is a mess. You've got a Finder block that contains some AppleScriptObjC code, and then some shell function calls... I think you need to pick one, and then organise the code a bit more logically so people (especially you) can make sense of what's going on.

    I typically avoid Finder for file system operations, but in this situation, it is advantageous because it allows one to compare a potential list of items (generated by a whose filter) with a known list of items—something no other application allows (and instead thinks you wish to compare it to the number 64). It also means that a move operation can be undone if necessary:

    -- Bits of text for joining 
    -- Used for the alert dialogs
    property lf : linefeed
    property lft : linefeed & tab
    property lf2 : lf & lf
    property lf2t : lf2 & tab
    property bullet : "›"
    property li : bullet & space
    
    -- Icon files, also used
    -- for the alert dialogs
    property CoreTypes : "/System/Library/CoreServices/CoreTypes.bundle"
    property StopIcon : path to resource "AlertStopIcon.icns" in bundle CoreTypes
    property NoteIcon : path to resource "AlertNoteIcon.icns" in bundle CoreTypes
    
    
    on replace:sender
            set prompt to "Please select some files:"
            set fs to choose file with prompt prompt ¬
                    with multiple selections allowed
    
            -- Get the directory in which the chosen files lie
            set dir to (some item in fs & "::" as text)
            set the text item delimiters to dir's POSIX path
    
            -- Organise the files into two lists:
            -- PNG files and non-PNG files
            set PNGfs to {}
            repeat with f in fs
                    get text items 2 thru -1 of f's POSIX path as text
                    set f's contents to the result
                    tell f to if its contents ends with ".png" then
                            set end of PNGfs to its contents
                            set its contents to null
                    end if
            end repeat
            set fs to every string in fs
    
            set the text item delimiters to lft & li
            if fs ≠ {} then display dialog ["The following files ", ¬
                    "are not PNG files and will be ignored:", lf2t, ¬
                    li & fs] as text with title ["Oops…!"] buttons ¬
                    ["D'oh!"] with icon NoteIcon default button 1
    
            if PNGfs = {} then return -- Nothing to move
    
            tell application id "com.apple.Finder"
                    set here to dir as alias -- The source folder
                    set there to the (path to the documents folder) -- Destination folder
    
                    -- Enumerate files that might be replaced
                    set duplicates to the name of every file ¬
                            in there whose name is in PNGfs
    
                    if duplicates ≠ {} then tell (display dialog contents ¬
                            of ["The following files in ", here's POSIX path, ¬
                            " share names with files in ", there's POSIX path, ¬
                            ":", lf2t & li & duplicates & lf2, "Do you want ", ¬
                            "to:", lft, "• Move all files anyway, replacing ", ¬
                            "the ones in ", there's POSIX path, ";", lft, "•", ¬
                            " Move only the files that can be moved without ", ¬
                            "replacing anything; OR", lft, "• Don't move any", ¬
                            " of the files for now ?"] as text ¬
                            with title ["Replace Existing Files?"] ¬
                            buttons ["Replace", "Keep", "Abort"] ¬
                            default button 1 with icon StopIcon) ¬
                            to set do to its button returned
    
                    -- If the user aborts, the script terminates.
                    -- If the user elects to replace existing files, 
                    -- then we move those existing files to the trash.
                    -- If the user wishes to keep the existing files,
                    -- they remain in place.  Either way, the final
                    -- operation is the same: move the files without
                    -- replacing anything.
                    if do = "Abort" then return 0 -- No files moved
                    if do = "Replace" then delete ¬
                            (files in there whose ¬
                                    name is in ¬
                                    duplicates)
                    move the (files in here whose ¬
                            name is in PNGfs) to ¬
                            there without replacing
            end tell
    end replace:
    

    Doing it this way avoids the repeat loop and thus you only get a single dialog box per set of grouped events (one if the user selects files of the wrong type; and one if there's a risk of overwriting files).

    In fact, you can even get rid of the first repeat loop that is used to split the list into two by file types: the choose file command has a parameter called of type, where you can specify one or more file types that the user's selection will be restricted to:

    set fs to choose file with prompt prompt ¬
            with multiple selections allowed ¬
            of type ["png"] --OR:["public.png"]
    

    "public.png" is the uniform type identifier for PNG files.