Getting started with bookmarks for sandboxed app in OS X

I'm adapting my App (OSX, XCode 8.3) to sandbox.


I've created the entitlements for the app.


I'm locked at the first step of writing a file.


In the basic version, I do so :


IBAction is called by menu item


    @IBAction func newDossier(_ sender: NSMenuItem) {

        let savePanel = NSSavePanel()
        savePanel.title = "List file"
        savePanel.prompt = "Create"
        let fileManager = FileManager.default    

        savePanel.begin() { (result) -> Void in
            if (result == NSFileHandlingPanelOKButton) {
                let fileWithExtensionURL = savePanel.url!.appendingPathExtension("xxxx") // I call the file Theleme and select Desktop as location
                if fileManager.fileExists(atPath: fileWithExtensionURL.path) {
                   // warning that file did exist already
                } else {
                    // fileWithExtensionURL  is written to a global var
                    globalURL = fileWithExtensionURL
                    let _ = writeAFile
                }
   
            }
        }
    }

func writeAFile() -> Bool {
     let url = globalURL          // fileWithExtensionURL from the NSSavePanel, stored in a global      
        let data = NSMutableData()
        let archiver = NSKeyedArchiver(forWritingWith: data)
       // Call  archiver.encode() with data to save
        archiver.finishEncoding()
        data.write(to: globalURL, atomically: true)
    }
    return true
}


My issue is this : when I do this with sandboxing enabled (entitlements), the file is not written.

I understand I need to use bookmarks instead of URL, but where and how ?


I have tried just to create a bookmark in writeAFile, but even this doesn't work : I get a message of non existing file :

Error Domain=NSCocoaErrorDomain Code=260 "Scoped bookmarks can only be created for existing files or directories" UserInfo={NSURL=file:/ / / Users/me/Desktop/Theleme.xxxx NSDebugDescription=Scoped bookmarks can only be created for existing files or directories}


func writeAFile() -> Bool {
     let url = globalURL          // fileWithExtensionURL from the NSSavePanel, stored in a global  
        let data = NSMutableData()
        let archiver = NSKeyedArchiver(forWritingWith: data)
       // Call  archiver.encode() with data to save
        archiver.finishEncoding()
        data.write(to: globalURL, atomically: true)
//      27.7.2017 Creating bookmarks for sandboxing
               do {
                   let bookmark = try globalURL!bookmarkData(options: [.withSecurityScope], includingResourceValuesForKeys: nil, relativeTo: nil)
                  Swift.print(bookmark)
              } catch {
                  Swift.print(error)
              }
    }
    return true     
}
Answered by Claude31 in 249120022

I moved one inch forward ! Writing file to the container :

            let documentsUrl = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0] as URL  // or .documents
            let fileName = globalURL!.deletingPathExtension().lastPathComponent
            let fileUrl = documentsUrl.appendingPathComponent(fileName)
            let result = data.write(to: fileUrl, atomically: true)  /

I have now 2 questions :

- here I write to the desktopDirectory ; how to replace by a directory selected by user in NSSavePanel ?

- the documents are "hidden" in the Library/Containers/com.xxxx.App/Data/Desktop/ ;

is it possible to get the files visible directly in the directory selected by user ?

So that he can later copy and paste those files and reuse the copy in the App ? Or launch App by double clicking the file icon.

To be a bit more precise:


If I test data.write:

        let result = data.write(to: GlobalData.sharedGlobal.docURL! as URL, atomically: true)


result is false


So I'm in a ctach-22:

- I cannot write because I would need to use a bookmark (If I understand how sandboxing works)

- but cannot build the bookmark if the file does not exist

         let result = data.write(to: globalURL, atomically: true)
               do { 
                   let bookmark = try globalURL!bookmarkData(options: [.withSecurityScope], includingResourceValuesForKeys: nil, relativeTo: nil)
              } catch { 
                  Swift.print(error)
              }
    }

Creating URL give error:

Error Domain=NSCocoaErrorDomain Code=513 "Vous n’êtes pas autorisé à enregistrer le fichier « xxxxxxxx » dans le dossier « Bureau »." UserInfo={NSFilePath=/Users/Me/Desktop/xxxxxxxxx, NSUnderlyingError=0x61800024bb50 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}


I tried to use what I thought was an "authorized" directory :

       do {
            let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
            let fileName = globalURL!.deletingPathExtension().lastPathComponent
            let fileUrl = documentsUrl.appendingPathComponent(fileName)
         
            let bookmark = try fileUrl.bookmarkData(options: [.withSecurityScope], includingResourceValuesForKeys: nil, relativeTo: nil)
        } catch {
            Swift.print(error)
        }


Then get the following error:

Error Domain=NSCocoaErrorDomain Code=260 "Scoped bookmarks can only be created for existing files or directories" UserInfo={NSURL=file:///Users/me/Library/Containers/com.company.appName/Data/Documents/Theleme, NSDebugDescription=Scoped bookmarks can only be created for existing files or directories}


So, for sure, I did not catch how it works.


Do I need to add some authorization in the plist in addition to what is in entitlements?

Accepted Answer

I moved one inch forward ! Writing file to the container :

            let documentsUrl = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask)[0] as URL  // or .documents
            let fileName = globalURL!.deletingPathExtension().lastPathComponent
            let fileUrl = documentsUrl.appendingPathComponent(fileName)
            let result = data.write(to: fileUrl, atomically: true)  /

I have now 2 questions :

- here I write to the desktopDirectory ; how to replace by a directory selected by user in NSSavePanel ?

- the documents are "hidden" in the Library/Containers/com.xxxx.App/Data/Desktop/ ;

is it possible to get the files visible directly in the directory selected by user ?

So that he can later copy and paste those files and reuse the copy in the App ? Or launch App by double clicking the file icon.

Getting started with bookmarks for sandboxed app in OS X
 
 
Q