Read the files in that folder

Hi everyone,


My problem is that I want to read the files in that document folder. I use the code from this stackoverflow

https://stackoverflow.com/questions/31278869/file-access-in-a-sandboxed-osx-app-with-swift

+ the solution of the "Accessing applicationDocumentsDirectory"

https://forums.developer.apple.com/thread/84448


However, when I start my MacOS app (swift), I get only the URL.

print ("Restoring \(bookmark.key)")


How can I read all the files in this folder with the subfolders. Is there a working code example?


Thanks,

Replies

It's not clear what you are asking.


Are you asking what APIs to use to enumerate the contents of a folder? (Answer: use the methods of the FileManager class.)


Are you saying that you have code to enumerate the contents, but it does not work as expected? (In that case, isolate the problem to a particular part of your code, and show us a relevant fragment of what you're doing.)

Hi there,


I want to read the files from the selected folder I choose (example, my documents folder /Users/stefan/Documents/ ).

I use the code from stackoverflow. And that works well to get the folder prompt (to add permission for that folder). But I see only the URL, when I print it.

I need to get all the files in that Document folder.


I tried that code, but it do not show the content in my previous selected ("documents" folder):


let fileNames = try! FileManager.default.contentsOfDirectory(atPath: "\(bookmark.key)")

for fileName in fileNames {

print(fileName)

}

The parameter passed to "contentsOfDirectory(atPath:)" is meant to be a String representing the path.


— What is "bookmark.key"? Presumably it isn't the path.


— Using string interpolation, as in "\(…)", is the wrong thing to do. Interpolation inserts a description of the value, not the value itself. If bookmark.key is already a path string, you should simply pass that. If not, then passing a description of it won't work.


In any case, you probably should not be using "contentsOfDirectory(atPath:)" at all. Although it's not deprecated (yet), an API that takes a URL parameter is always to be preferred. In this case, that's "contentsOfDirectory(at:includingPropertiesForKeys:options:)".


Also, both of these API are shallow enumerations, that won't give you the contents of subfolders within your folder. If you want that, you have to use "enumerator(at:includingPropertiesForKeys:options:errorHandler:)".

I tried this code in my "func restorebookmark", but still see nothing.

print("Restoring \(bookmark.key)")


            restoredUrl = try URL.init(resolvingBookmarkData: bookmark.value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
     
            do {
                let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
                let enumerator = FileManager.default.enumerator(at: restoredUrl!,
                                                                includingPropertiesForKeys: resourceKeys,
                                                                options: [.skipsHiddenFiles], errorHandler: { (url, error) -> Bool in
                                                                    print("Print: directoryEnumerator error at \(url): ", error)
                                                                    return true
                })!
             
                for case let fileURL as URL in enumerator {
                    let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
                    print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
                }
            } catch {
                print(error)
            }

In the debug log I get this message:
Restoring file:///Users/stefan/Documents/

Print: directoryEnumerator error at file:///Users/stefan/Documents: Error Domain=NSCocoaErrorDomain Code=257 "The file “Documents” couldn’t be opened because you don’t have permission to view it." UserInfo={NSURL=file:///Users/stefan/Documents, NSFilePath=/Users/stefan/Documents, NSUnderlyingError=0x618000048f10 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

a. Did you create the bookmark with security scope?


b. Did you create the bookmark from the original URL returned by NSOpenPanel or NSSavePanel? (Creating a security scoped bookmark from a URL from a security scoped booked doesn't work.)

a. + b. Is that not the code in stackoverflow?


I just wait to get a mac app (swift), when the user open it for the first time it show that NSOpenpanel to choose the your documents folder.

And then it shows all the files in that folder. Even when I close and restart the application. I can see those files in my documents (in a tableview). (with an option to choose later, another folder with the NSButton).


It is just a simple question, where there is no sample code available. Also, here not on Apple doc. (and Microsofts got for each API a working sample on github)

If you can provide me a working sample in swift. Than I will be very happy 🙂 (It is so difficult as beginner developer)

An issue with the example in SO is that they do not define app.applicationDocumentsDirectory


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


OOPer has proposed a solution for this in a previous thread :

    var applicationDocumentsDirectory: URL { 
        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! 
    }


I agree with your other comment: that would be a great progress from Apple doc to get a working example for such API, not just partial explanation.

Hi there,


applicationDocumentsDirectory that is already fixed (since the begin of my post). But I still see no files show up.

None of us control what sample code Apple provides, so we can't help you there.


>> applicationDocumentsDirectory that is already fixed


Not really. The problem is not in getting the URL to the Documents folder. The problem is that apps are not allowed to randomly read from folders without explict permission from the user of the app.


You already have the correct logic to enumerate the files in a directory. Now you have to solve your security (permission) problem, and you have 2 basic choices:


1. Write a non-sandboxed app. In this case, you don't need explicit permission to read any file you want. However, you can't distribute a non-sandboxed app to users in a form that will automatically be allowed to run. That's because an app that that can read or write any file is a potential malicious export. (Even if your code is safe, your app can be hijacked to damage a user's system.)


2. Ask the user for permission to read the files, using NSOpenPanel. That gives you a URL that will work immediately, and can be saved as a security-scoped bookmark so that the URL can be re-used later. AFAICT you haven't done the first part of this yet.

About 2.

Yes, that is what I want. Is that not in this line? (code from stackoverflow)

func allowFolder() -> URL?


This is what I got now (+ in the design 1 openbutton). What should be added?

https://github.com/stefanvd/macos/blob/master/readfiles.swift

I don't know. You've only posted fragments of code in this thread, so it's hard to tell. The linked file looks like it does approximately the right thing, but I'm not in a position to trace through it and double-check.


Does it work the first time (right after getting the directory URL from NSOpenPanel)? Are you sure your entitlements are correct (Project -> App Sandbox -> File Access -> User Selected File -> Read Only or Read/Write)?

If I click on my open button, I get my NSopenpanel. And click then on OK. But still no files names/path in my 'Print'.


Yes the entitlements is inside my project:

com.apple.security.files.user-selected.read-only TRUE

com.apple.security.files.bookmarks.app-scope TRUE

com.apple.security.files.bookmarks.document-scope TRUE

It looks like the problem you're having is nothing to do with the bookmark resolution, or the URL that you got from NSOpenPanel. When you save the bookmark, you are saving to the user's Documents folder (via applicationDocumentsDirectory() ). You don't have access to that folder, because you would need permission from the user (via NSOpenPanel) to access it.


If, in running your existing NSOpenPanel invocation, you (as user) choose the Documents folder, the bookmark save will succeed, because you're writing it in the folder that you got permission for. (If you chose a different folder, the save would silently fail, since you don't have any error handling.)


But now you've got a catch-22. You can't read the bookmark file back in a later launch, because to do so your app needs permission to read from the Documents folder, but it doesn't have that until after it's read the bookmark from there.


Instead, you should save the bookmark file in a location that's inside your app container, so that you don't need user permission to read or write it. You can use the Application Support directory (which you get hold of in a similar way to getting the Documents directory) for the bookmark file, or you can save the bookmark data in UserDefaults if you prefer.


For documentation on containers (and what's inside or outside), see here:


https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html


under the heading "Container Directories and File System Access".

Hi there,


It save normal already well this file "Bookmarks.dict", because I see it in my folder:

/Users/stefan/Library/Containers/com.stefanvd.app/Data/Documents

But still same issue.

Any updates?

A working sample?