macOS 沙盒应用程序,无法将桌面文件夹中的文件复制到临时目录

macOS sandboxed app, cannot copy file inside desktop folder to temporary directory

我有一个本地 macOS 应用程序 Messer,它允许 convert/resize/pad 图像文件。我内置的一项功能是自动转换特定文件夹中的文件。

此功能适用于大多数文件夹,即使它可能需要权限才能访问它们。例如,select将 Downloads 文件夹自动转换会触发一个权限对话框,供用户授予访问权限。

Desktop 文件夹也是如此,但是,即使已对 Desktop 文件夹授予权限,我在尝试复制子文件夹中的文件时仍会出错-目录。

错误输出:

2022-04-27 13:35:04.255243+0200 Messer[1254:11983] open on /Users/osp/Desktop/posts/a-plus-content-4.png: Operation not permitted
Messer, could not copy file Error Domain=NSCocoaErrorDomain Code=513 "“a-plus-content-4.png” couldn’t be copied because you don’t have permission to access “com.ospfranco.messer”." UserInfo={NSSourceFilePathErrorKey=/Users/osp/Desktop/posts/a-plus-content-4.png, NSUserStringVariant=(
    Copy
), NSDestinationFilePath=/var/folders/qn/vyvn49j90jv9_77vq77wzvw00000gn/T/com.ospfranco.messer/a-plus-content-4.png, NSFilePath=/Users/osp/Desktop/posts/a-plus-content-4.png, NSUnderlyingError=0x60000206dda0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

当我尝试将文件复制到临时目录时抛出:

func secureCopyItem(at srcURL: URL, to dstURL: URL) {
    do {
        if FileManager.default.fileExists(atPath: dstURL.path) {
            try FileManager.default.removeItem(at: dstURL)
        }
            
        try FileManager.default.copyItem(at: srcURL, to: dstURL)
    } catch let error {
        print("Messer, could not copy file \(error)")
        SentrySDK.capture(error: error)
    }
}

在这里您可以看到该应用程序具有完整的磁盘访问权限:

但应用程序仍然崩溃!如果我选择任何其他文件夹,复制操作将正常进行。所有的权利和权限似乎都在那里。知道哪里出了问题吗?

编辑 1:

好像不太清楚我真的从桌面URL复制到一个临时目录,所以这里是一些周围的代码:

if url.isImage && url.isLocalFile {
    let newFileUrl = FileConstants.tempUrl.appendingPathComponent(url.lastPathComponent)
                
    FileManager.default.secureCopyItem(at: url, to: newFileUrl)
                
    self.initPipeline(urls: [newFileUrl], autoSave: true)
}

另一个常量文件中的温度URL:

static let tempUrl: URL = {
    return FileManager.default.temporaryDirectory
}()

我可以向您保证,与另一个目录(例如下载)中的另一个文件相同的代码 运行 是正确的,并且可以正常工作。

编辑 2:

阅读了一些其他答案后,我访问资源的方式可能有问题,我允许用户通过 UI select 一个文件夹,然后保存此路径在我的申请中。但是,由于权限的原因,资源可能只能在用户 select 编辑文件夹后的一段时间内访问,而不是无限期地访问。因此,一段时间后,当我尝试访问文件夹中的资源时,macOS 会阻止我读取文件的任何尝试。

我试过使用一些资源锁定方法,但它 returns 在传递的 URL:

上是错误的
let scopedResourceLocked = url.startAccessingSecurityScopedResource()
                print("SCOPED RESOURCE LOCKEd \(scopedResourceLocked)") // prints false
                let newFileUrl = FileConstants.tempUrl.appendingPathComponent(url.lastPathComponent)
                
                FileManager.default.secureCopyItem(at: url, to: newFileUrl)
                
                url.stopAccessingSecurityScopedResource()

编辑 3:

我刚刚尝试重新select 文件夹(通过 NSOpenPanel)并转换文件并且成功了。所以我想如果没有用户交互,就无法以一致的方式实现这一点。稍后 macOS 将再次 lock/protect 文件夹中的文件。

非常感谢@vadian 为我指明了正确的方向。事实证明,拥有完整的磁盘访问权限是不够的,即使用户已授予对特定文件夹的权限。

从 NSOpenPanel 获得 URL 后,您需要创建书签并保存二进制数据。在进一步打开应用程序时,您可以对二进制数据进行水合。从这个书签对象,您可以再次创建一个 URL,您需要调用 startAccessingSecurityScopedResourcestopAccessingSecurityScopedResource 来访问用户已授予访问权限的任何先前文件夹(即使您正在访问该文件夹内的文件或子文件夹。

基本遵循了这个教程:

https://medium.com/fantageek/how-to-access-bookmark-url-in-macos-b38bc82f03e9

这是一个视觉版本: