Приложение песочницы магазина MacOS использует NSOpenPanel для выбора папки для загрузки файла, но не может снова получить к ней доступ - PullRequest
0 голосов
/ 08 ноября 2019

Мое приложение для загрузки файла с веб-сайта.

Я включил песочницу проекта для магазина MacOS.

enter image description here

Приложение вызовет NSOpenPanel, чтобы попросить пользователя выбрать папку, в которую сохраняются загружаемые файлы (все хранилища списка файлов в файле sqlite). например:

/home/mymac/myfolder

Все в порядке. Если я закрою приложение и снова открою его, я надеюсь, что оно может продолжить загрузку файлов (в файле sqlite).

, но оно сообщает об ошибке: настройка информации о безопасности: операция не разрешена

похоже, система не позволяет приложению снова получить доступ к папке

/home/mymac/myfolder

.

Если я использую NSOpenPanel для выбора папки загрузки системы

/home/mymac/Downloads

, закройтеприложение и снова откройте приложение, все работает отлично. Похоже, система только разрешает приложению доступ к папке

/home/mymac/Downloads

снова.

Ваш комментарий приветствуется

Ответы [ 2 ]

1 голос
/ 08 ноября 2019

Вам необходимо получить закладку на URL и хранить ее постоянно. Когда ваше приложение откроется, получите URL-адрес из сохраненной закладки.

Способ сделать это описан в документации: Поиск файлов с помощью закладок

Вам нужно только 2 метода:

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

Вы можете сохранить закладку в файле .plist или даже в UserDefaults, если вы не ожидаете, что у вас будет много закладок.

1 голос
/ 08 ноября 2019

вы можете использовать безопасные закладки для этого. Я прикрепил используемый класс:

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. Вы можете установить экземпляр в viewDidAppear одного из ваших NSViewControllers:

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