macOS 商店沙盒应用程序使用 NSOpenPanel select 下载文件夹,但无法再次访问该文件夹

macOS store sandbox app uses NSOpenPanel to select download file folder, but can not access the folder again

我的应用是从网站下载文件。

我为 macOS 商店启用了项目的沙箱。

该应用程序将触发 NSOpenPanel 询问用户 select 保存下载文件(所有文件列表存储在 sqlite 文件中)的文件夹。 例如:

/home/mymac/myfolder

一切正常。如果我关闭应用程序并重新打开它,我希望它可以继续下载文件(在sqlite文件中)。

但是报错: 设置安全信息:不允许操作

系统似乎不允许该应用访问该文件夹

/home/mymac/myfolder

再一次。

如果我使用NSOpenPanel select系统下载文件夹

/home/mymac/Downloads

关闭应用程序并重新打开应用程序,一切正常。 看起来系统只允许应用程序访问文件夹

/home/mymac/Downloads

再一次。

欢迎您的评论

您可以为此使用安全书签。我附上了我正在使用的 class:

import Foundation
import Cocoa

public class SecureFolders
{
    public static var window: NSWindow?

    private static var folders = [URL : Data]()
    private static var path: String?

    public static func initialize(_ path: String)
    {
        self.path = path
    }

    public static func load()
    {
        guard let path = self.path else { return }

        if !FileManager.default.fileExists(atPath: path)
        {
            return
        }

        if let rawData = NSData(contentsOfFile: path)
        {
            let data = Data(referencing: rawData)

            if let folders = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [URL : Data]
            {
                for folder in folders
                {
                    self.restore(folder)
                }
            }
        }
    }

    public static func remove(_ url: URL)
    {
        folders.removeValue(forKey: url)
    }

    public static func store(url: URL)
    {
        guard let path = self.path else { return }

        do
        {
            let data = try NSKeyedArchiver.archivedData(withRootObject: self.folders, requiringSecureCoding: false)
            self.folders[url] = data

            if let url = URL(string: path)
            {
                try? data.write(to: url)
            }
        }
        catch
        {
            Swift.print("Error storing bookmarks")
        }
    }

    public static func restore(_ folder: (key: URL, value: Data))
    {
        let restoredUrl: URL?
        var isStale = false

        do
        {
            restoredUrl = try URL.init(resolvingBookmarkData: folder.value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
        }
        catch
        {
            Swift.print("Error restoring bookmarks")
            restoredUrl = nil
        }

        if let url = restoredUrl
        {
            if isStale
            {
                Swift.print ("URL is stale")
            }
            else
            {
                if !url.startAccessingSecurityScopedResource()
                {
                    Swift.print ("Couldn't access: \(url.path)")
                }

                self.folders[url] = folder.value
            }
        }
    }

    public static func allow(folder: String, prompt: String, callback: @escaping (URL?) -> ())
    {
        let openPanel = NSOpenPanel()
        openPanel.directoryURL = URL(string: folder)
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = true
        openPanel.canCreateDirectories = false
        openPanel.canChooseFiles = false
        openPanel.prompt = prompt

        openPanel.beginSheetModal(for: self.window!)
        {
            result in

            if result == NSApplication.ModalResponse.OK
            {
                let url = openPanel.url
                self.store(url: url!)

                callback(url)
            }
            else
            {
                callback(nil)
            }
        }
    }

    public static func isStored(_ directory: Directory) -> Bool
    {
        return isStored(path: IO.getDirectory(directory))
    }

    public static func remove(_ directory: Directory)
    {
        let path = IO.getDirectory(directory)
        self.remove(path)
    }

    public static func remove(_ path: String)
    {
        let url = URL(fileURLWithPath: path)
        self.remove(url)
    }

    public static func isStored(path: String) -> Bool
    {
        let absolutePath = URL(fileURLWithPath: path).path

        for url in self.folders
        {
            if url.key.path == absolutePath
            {
                return true
            }
        }

        return false
    }

    public static func areStored(_ directories: [Directory]) -> Bool
    {
        for dir in directories
        {
            if isStored(dir) == false
            {
                return false
            }
        }

        return true
    }

    public static func areStored(_ paths: [String]) -> Bool
    {
        for path in paths
        {
            if isStored(path: path) == false
            {
                return false
            }
        }

        return true
    }
}

用法:

fileprivate func initialize() // Put a call to this in func applicationDidFinishLaunching(_ aNotification: Notification)
{
    let directories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let path = directories[0].appending("/SecureBookmarks.dict")

    SecureFolders.initialize(path)
    SecureFolders.load()
}

要将文件夹添加到安全书签:

fileprivate func allow(_ path: String)
{
    SecureFolders.allow(folder: path, prompt: "Open")
    {
        result in
        // Update controls or whatever
    }
}

不要忘记设置显示 NSOpenPanel 所需的 window 实例。您可以在其中一个 NSViewControllers 的 viewDidAppear 中设置实例:

override func viewDidAppear()
{
    super.viewDidAppear()
    SecureFolders.window = NSApplication.shared.mainWindow
}

您需要获取 URL 的书签并永久保存。当您的应用程序打开时,从存储的书签中检索 URL。

文档中描述了执行此操作的方法:Locating Files Using Bookmarks

你只需要2个方法:

- (NSData*)bookmarkForURL:(NSURL*)url
- (NSURL*)urlForBookmark:(NSData*)bookmark

您可以将书签存储在 .plist 文件中,如果您不希望有很多书签,甚至可以存储在 UserDefaults 中。