LetsMoveIt

There is already a similar Project but I wanted a solution that can be implemented with pure on-board resources and does not require the installation of an extra framework. The result is “LetsMoveIt”. You only need to create a Swift file (in my case “LetsMoveIt.swift”) with the following content in your project. Of course you can place the “ToApps ()” function in any other document, but I find a separate class somehow more elegant. But as is so often the case, it's a matter of taste and of course it's up to everyone to do it differently. (-:

//
//  LetsMoveIt.swift
//
//  Created by Sascha Lamprecht 04.06.2021
//

import Cocoa

class LetsMoveIt: NSViewController {
    
    func ToApps() {

        let url = URL(fileURLWithPath: Bundle.main.resourcePath!)
        var LaunchPath = url.deletingLastPathComponent().deletingLastPathComponent().absoluteString.replacingOccurrences(of: "file://", with: "").replacingOccurrences(of: "%20", with: " ")
        LaunchPath.removeLast()
        let RealAppName = URL(fileURLWithPath: LaunchPath).lastPathComponent
        
        if LaunchPath.contains("/Applications/") {
            return
        }
        if UserDefaults.standard.bool(forKey: "Supress") {
            return
        }
        
        let alert = NSAlert()
        alert.messageText = NSLocalizedString("Move to Applications folder?", comment: "")
        alert.informativeText = NSLocalizedString("I can move myself to the Applications folder if you'd like. This will keep your Downloads folder uncluttered.", comment: "")
        alert.alertStyle = .informational
        alert.showsSuppressionButton = true
        let Button = NSLocalizedString("Do Not Move", comment: "")
        alert.addButton(withTitle: Button)
        let CancelButtonText = NSLocalizedString("Move to Applications Folder", comment: "")
        alert.addButton(withTitle: CancelButtonText)

        if alert.runModal() == .alertFirstButtonReturn {
            if let supress = alert.suppressionButton {
                let state = supress.state
                switch state {
                case NSControl.StateValue.on:
                UserDefaults.standard.set(true, forKey: "Supress")
                default: break
                }
            }
            return
        }

        let admin_check = "user=$( id -un ); admin_check=$( groups \"$user\" | grep -w -q admin ); echo \"$admin_check\""
        let process            = Process()
        process.launchPath     = "/bin/bash"
        process.arguments      = ["-c", admin_check]
        process.launch()
        process.waitUntilExit()
        
        let fileManager = FileManager.default
        let path = "/Applications/" + RealAppName
            if admin_check.contains(" admin ") {
                do {
                    if fileManager.fileExists(atPath: path) {
                        try fileManager.removeItem(atPath: path)
                    }
                    try fileManager.copyItem(atPath: LaunchPath, toPath: path)
                    try fileManager.removeItem(atPath: LaunchPath)
                } catch {
                    return
                }
            } else {
                let move_to_apps = "osascript -e 'do shell script \"rm -rf /Applications/" + RealAppName + "; cp -r \\\"" + LaunchPath + "\\\" /Applications/; chown -R " + NSUserName() + ":staff \\\"/Applications/" + RealAppName + "\\\"; rm -r \\\"" + LaunchPath + "\\\"\" with administrator privileges'"
                let process            = Process()
                process.launchPath     = "/bin/bash"
                process.arguments      = ["-c", move_to_apps]
                process.launch()
                process.waitUntilExit()
            }
        let task = Process()
        task.launchPath = "/usr/bin/open"
        task.arguments = [path]
        task.launch()
        exit(0)
    }
}

The function can now be called from anywhere in the project using the following syntax. For example from AppDelegate.swift:

func applicationDidFinishLaunching(_ aNotification: Notification) {
    LetsMoveIt().ToApps()
}

The following dialog then opens if the application is not yet in “/ Applications”:

You now have the option to move the application there.
If LetsMoveIt should also contain German, here are the corresponding strings:

/* No comment provided by engineer. */
"Do Not Move" = "Nicht verschieben";

/* No comment provided by engineer. */
"I can move myself to the Applications folder if you'd like. This will keep your Downloads folder uncluttered." = "Ich kann mich selbst in den Programme-Ordner verschieben wenn Du möchtest. Somit bleibt Dein Download-Ordner aufgeräumt.";

/* No comment provided by engineer. */
"Move to Applications Folder" = "In den Programme-Ordner verschieben";

/* No comment provided by engineer. */
"Move to Applications folder?" = "In den Programme-Ordner verschieben?";