I'm experiencing problems (on Mojave and Catalina) with "reusing" security scope URL bookmark for a folder between app launches in my app.
It's simple decompressing application using libarchive
framework. User selects file to decompress, I want to store URL bookmark for it's parent folder (e.g. ~/Desktop), and reuse it next time user tries to decompress file in the same folder.
First, I added following to my app's entitlements file:
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
When accessing file (parent folder respectively) for the first time:
NSOpenPanel
to obtain access to the file folder:let directoryURL = fileURL.deletingLastPathComponent()
let openPanel = NSOpenPanel()
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = false
openPanel.prompt = "Grant Access"
openPanel.directoryURL = directoryURL
openPanel.begin { [weak self] result in
guard let self = self else { return }
// WARNING: It's absolutely necessary to access NSOpenPanel.url property to get access
guard result == .OK, let url = openPanel.url else {
// HANDLE ERROR HERE ...
return
}
// We got URL and need to store bookmark's data
// ...
}
let data = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
bookmarks[url] = data
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
libarchive
to decompress .zip file to it's parent folder:fileURL.startAccessingSecurityScopedResource()
// Decompressing file with libarchive...
fileURL.stopAccessingSecurityScopedResource()
When relaunching app, decompressing file in the same folder, reusing saved bookmark data:
let bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data]
let directoryURL = fileURL.deletingLastPathComponent()
let data = bookmarks[directoryURL]!
var isStale = false
let newURL = try URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
libarchive
to decompress .zip file to it's parent folder:fileURL.startAccessingSecurityScopedResource()
// Decompressing file with libarchive...
fileURL.stopAccessingSecurityScopedResource()
But this time libarchive
returns error saying Failed to open \'/Users/martin/Desktop/Archive.zip\'
I know I might be doing something terribly wrong or not understanding concept of security scoped URL bookmarks but can't find where's the problem. Any hints?
FINAL SOLUTION
Both Rckstr's answer and answer in this Apple developer forum thread pointed me into the right direction. It's absolutely necessary to call startAccessingSecurityScopedResource()
on THE SAME INSTANCE of URL returned by try URL(resolvingBookmarkData: data, options: .withSecurityScope ...
You're resolving the security-scoped bookmark (for the directory) to let newUrl
, but you call startAccessingSecurityScopedResource()
on the file's URL fileURL
. You need to call it for newURL
.
newURL.startAccessingSecurityScopedResource()
// Decompressing fileURL with libarchive...
newURL.stopAccessingSecurityScopedResource()
Two more remarks:
startAccessingSecurityScopedResource()
and
stopAccessingSecurityScopedResource()
, because the user explicitly
granted you access for this session.var isStale: ObjCBool = ObjCBool(false)
instead. I'm no Swift expert, so not sure if var isStale = false
is ok to use.