Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-07-16

Persisting file access rights between sessions

While the Apple sandbox is a good thing, it does tend to complicate matters for us software developers.
For example when a user opens a file or folder from within a NSOpenPanel the application automatically receives access rights to that file/folder.
But when the path is saved to disk, and the application wants to use this path in the next session, the sandbox will interfere and deny access rights.
Luckily Apple has also created a solution for this: the path can be stored as a "Security Scoped Bookmark". The application can -in later sessions- read this data, create a new URL from it and then request the OS (sandbox) to reinstate the same rights as in the previous session. A bit of work, but not intrinsically difficult.

Here is how to create a Security Scoped Bookmark:

// Retrieve the bookmark data from the URL and add it to the bookmarks
                
var error: NSError?

if let data: NSData? = url.bookmarkDataWithOptions(
   NSURLBookmarkCreationOptions.WithSecurityScope,
   includingResourceValuesForKeys: nil,
   relativeToURL: nil,
   error: &error) {
                    
   bookmarks[path] = data      
                    
} else { // 'data' is nil, there should be an error message
                    
   let detail = error?.localizedDescription ?? "missing error description"
}

That example is of course assuming that the path is available as a NSURL, which in the case of a NSOpenPanel will indeed be the case. For drag & drop it will be necessary to convert the string's with file paths to a NSURL first. Also, the above example (and the one below) is for application wide access. Document relative paths must be created slightly different.

The NSData must be saved to file when the session ends:

// Create a string that contains the security scoped bookmark data
                

let bytes = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)
... save the bytes to file using your favourite method

and in the following session it must be reloaded. After reloading, we recreate the NSURL with:

// Recreate the security scoped bookmark url
                    
var isStale: ObjCBool = false
var error: NSError?
let url = NSURL(
   byResolvingBookmarkData: data,
   options: NSURLBookmarkResolutionOptions.WithSecurityScope,
   relativeToURL: nil,
   bookmarkDataIsStale: &isStale,
   error: &error)
                                       
   if error != nil {
                        
      ... error handling

   } else if isStale { // Exclude stale resources
                        
      ... error handling
                     
   } else if url == nil { // Should never happen when error == nil !!
                        
      ... error handling
                    
   } else {

                        
      // Regain the access rights
                        
      if !url!.startAccessingSecurityScopedResource() {
                            
          errorMessage += "Could not gain access rights for: \(url!.path!)\r\n"

      } 
   }

}

Once the application no longer needs the access rights, they should be released with:

url.stopAccessingSecurityScopedResource()

Failure to release security scoped bookmarks will result in memory leaks within the kernel. This may or may not be a problem for your application, depending on how many resources it needs.

If you want to speed up your development process even more, get our fully functional SecurityScopedBookmarks example. Depending on your experience, this could easily save you a days work for the price of a (cheap) cup of coffee ($2). And you will be helping to keep this blog alive.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2 comments:

  1. Thanks for the concise description. Note that with newer Swift versions you need to enclose it in a do/try/catch since both bookmark methods are Throws.

    ReplyDelete
  2. Thanks Thomas,

    After Swift 3 ships I am planning to do a complete overhaul of all my posts.

    ReplyDelete