File access in a sandboxed Mac app with swift

ぐ巨炮叔叔 提交于 2019-12-03 03:10:47

Here is my answer that I've just got working in Swift 3 with a little help from http://swiftrien.blogspot.com/2015/07/persisting-file-access-rights-between.html

import Foundation
import Cocoa

var bookmarks = [URL: Data]()

func bookmarkPath() -> String
{
    var url = app.applicationDocumentsDirectory
    url = url.appendingPathComponent("Bookmarks.dict")
    return url.path
}

func loadBookmarks()
{
    let path = bookmarkPath()
    bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: path) as! [URL: Data]
    for bookmark in bookmarks
    {
        restoreBookmark(bookmark)
    }
}

func saveBookmarks()
{
    let path = bookmarkPath()
    NSKeyedArchiver.archiveRootObject(bookmarks, toFile: path)
}

func storeBookmark(url: URL)
{
    do
    {
        let data = try url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
        bookmarks[url] = data
    }
    catch
    {
        Swift.print ("Error storing bookmarks")
    }

}

func restoreBookmark(_ bookmark: (key: URL, value: Data))
{
    let restoredUrl: URL?
    var isStale = false

    Swift.print ("Restoring \(bookmark.key)")
    do
    {
        restoredUrl = try URL.init(resolvingBookmarkData: bookmark.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)")
            }
        }
    }

}

func allowFolder() -> URL?
{
    let openPanel = NSOpenPanel()
    openPanel.allowsMultipleSelection = false
    openPanel.canChooseDirectories = true
    openPanel.canCreateDirectories = true
    openPanel.canChooseFiles = false
    openPanel.begin
        { (result) -> Void in
            if result == NSFileHandlingPanelOKButton
            {
                let url = openPanel.url
                storeBookmark(url: url!)
            }
    }
    return openPanel.url
}

Swift 4 (update):

import Foundation
import Cocoa

var bookmarks = [URL: Data]()

func fileExists(_ url: URL) -> Bool
{
    var isDir = ObjCBool(false)
    let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir)

    return exists
}

func bookmarkURL() -> URL
{
    let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
    let appSupportURL = urls[urls.count - 1]
    let url = appSupportURL.appendingPathComponent("Bookmarks.dict")
    return url
}

func loadBookmarks()
{

    let url = bookmarkURL()
    if fileExists(url)
    {
        do
        {
            let fileData = try Data(contentsOf: url)
            if let fileBookmarks = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(fileData) as! [URL: Data]?
            {
                bookmarks = fileBookmarks
                for bookmark in bookmarks
                {
                    restoreBookmark(bookmark)
                }
            }
        }
        catch
        {
            print ("Couldn't load bookmarks")
        }

    }
}

func saveBookmarks()
{
    let url = bookmarkURL()
    do
    {
        let data = try NSKeyedArchiver.archivedData(withRootObject: bookmarks, requiringSecureCoding: false)
        try data.write(to: url)
    }
    catch
    {
        print("Couldn't save bookmarks")
    }
}    

func storeBookmark(url: URL)
{
    do
    {
        let data = try url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
        bookmarks[url] = data
    }
    catch
    {
        Swift.print ("Error storing bookmarks")
    }

}

func restoreBookmark(_ bookmark: (key: URL, value: Data))
{
    let restoredUrl: URL?
    var isStale = false

    Swift.print ("Restoring \(bookmark.key)")
    do
    {
        restoredUrl = try URL.init(resolvingBookmarkData: bookmark.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)")
            }
        }
    }

}

func allowFolder() -> URL?
{
    let openPanel = NSOpenPanel()
    openPanel.allowsMultipleSelection = false
    openPanel.canChooseDirectories = true
    openPanel.canCreateDirectories = true
    openPanel.canChooseFiles = false
    openPanel.begin
        { (result) -> Void in
            if result == NSFileHandlingPanelOKButton
            {
                let url = openPanel.url
                storeBookmark(url: url!)
            }
    }
    return openPanel.url
}

To use this code you must first call NSOpenPanel so the user can select which folders to give you access to. The NSOpenPanel must be stored as a bookmark and saved to disk.

let url = allowFolder()
saveBookmarks()

When you restart the application you must call

loadBookmarks()

then your app will have the same level of access as it did when the user selected the folder. Hope this helps someone.

security scoped bookmarks is exactly the way to go. good starting point is apple documentation on AppStore sandbox (which includes sample code) and class reference of NSFileManager.

you then will not store the path in user defaults but the binary data of the bookmark.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!