如何正确地将文件从沙盒应用程序复制到应用程序脚本文件夹?

How to properly copy files to Application Scripts folder from Sandboxed App?

我真的很困惑如何正确复制文件并授予执行权限,例如来自沙盒应用程序的 AppleScript 文件。我已经阅读了几篇文章和话题,但我读得越多,就越让我感到困惑。

任务

我的应用程序需要 运行 来自 .scpt 文件的非常简单的 AppleScript。为此(如果我做对了),我需要将此文件复制到 Users/thisUser/Library/Application \Scripts/.com.developerName.appName/。在我可以与该文件夹交互之前,用户需要授予对该文件夹的访问权限。这可以通过向用户显示 NSOpenPanel 他可以 select 路径来完成。确认应用程序可以访问该路径后,我可以复制文件,然后 运行 脚本(应用程序沙盒 用户选择的文件 必须是 read/write)。到目前为止一切顺利。

问题

我发现向 select 展示一个带有空文件夹的 Finder window 对用户非常不友好,所以我想知道是否还有其他我可以做。关于这个问题,我发现的最接近的是 拖放 文件夹 "into the app" - 可以找到详细信息 here.

我想我不是唯一一个创建需要 运行 特定脚本的(沙盒)应用程序的人,我不敢相信上述方法是唯一可能的解决方案!?因此,

can I not just have a single window with an OK button and some information above that the app needs permission to write into that folder without showing an entire Finder window?

当我四处寻找解决方案时,我也遇到了应用程序本身的几个设置。不幸的是,这里的文档非常有限,我无法真正了解具体设置的实际作用以及我如何测试它们(诚然,这是因为这是我的第一个 OSX 应用程序,我基本上没有任何线索我在做什么)。其中之一是应用程序 Build Phase 设置中的 Copy Files 选项:

这对我来说确实很有希望,因为我认为如果我安装该应用程序,它会自动将文件复制到脚本目标(可能会出现某种用户提示)并且我可以使用它。但它什么都不做。任何时候都不会发生复制,即使我 deselect Copy only when installing 设置也是如此。我还尝试了下拉菜单中可用的不同目标文件夹

不幸的是我也在这里

我知道这里的人不太喜欢详细回答这样的问题,因为我可能 缺乏知识 但我真的很感激如果有人至少可以帮助我进入正确的方向并指导我找到一些可以解决我的问题的资源!

谢谢!

好吧,我似乎找到了一个解决方案(至少对我而言)似乎或多或少对用户友好并且符合 Apple 的沙盒指南。

同样,我对使用 Xcode 和 SwiftUI 进行应用程序开发非常陌生,所以我不确定此解决方案是否 100% "the right way of doing it"。但由于我花了很长时间才找到它,也许其他人可以使用它并加快开发速度!

解决方案

就像我在上面的问题中提到的那样,我试图摆脱(在我看来)非常烦人的 NSOpenPanel Finder 提示,用户应该在其中 select 文件夹.我进一步询问了应用程序 Build Phase 选项卡中的 Copy Files 设置 - 结果证明这就是解决方案!不幸的是,我仍然对下拉列表中显示的目的地列表没有任何线索,而是选择 Absolute Path 并插入

Users/$USER/Library/Application Scripts/$PRODUCT_BUNDLE_IDENTIFIER

完成任务!该文件在每次构建时都被复制到应用程序的 Application Scripts 目录中,我可以从中 运行 沙箱外的脚本。

下一步是创建一个 class,它使用 NSUserScriptTask

执行脚本
import Foundation

class ExecuteAppleScript {

    var status = ""

    private let scriptfileUrl : URL?


    init() {
        do {
            let destinationURL = try FileManager().url(
                for: FileManager.SearchPathDirectory.applicationScriptsDirectory,
                in: FileManager.SearchPathDomainMask.userDomainMask,
                appropriateFor:nil,
                create: true)

            self.scriptfileUrl = destinationURL.appendingPathComponent("CreateMailSignature.scpt")
            self.status = "Linking of scriptfile successful!"

        } catch {
            self.status = error.localizedDescription
            self.scriptfileUrl = nil
        }
    }

    func execute() -> String {
        do {
            let _: Void = try NSUserScriptTask(url: self.scriptfileUrl!).execute()
            self.status = "Execution of AppleScript successful!"
        } catch {
            self.status = error.localizedDescription
        }

        return self.status
    }
}

然后我创建了一个 Button View 来处理请求

import SwiftUI

struct GenerateSignatureButtonView: View {

    @State var status = ""

    var body: some View {
        VStack(alignment: .center) {
            Button(action: {
                self.status = ExecuteAppleScript().execute()
            })
            {
                Text("Generate Signature")
            }
            Text("\(self.status)")
        }
    }
}

struct GenerateSignatureButtonView_Previews: PreviewProvider {
    static var previews: some View {
        GenerateSignatureButtonView()
    }
}

单击按钮时,会弹出一个 window 应用程序想要访问以控制(在我的情况下)邮件应用程序。

每次用户关闭应用程序、重新打开应用程序并再次单击按钮时,都会重复此用户提示。我想这可以通过 Security-Scoped-Bookmarks 以某种方式进行管理,但我还不确定如何进行。此外,错误处理在这个例子中并没有真正起作用,因为弹出窗口出现在 之后 successful 消息出现在状态字段中。这可能是另一个需要弄清楚的大问题......异步?

好的,希望这对您有所帮助!