Why does a FileManger.DirectoryEnumerator not work on an *.photoslibrary?

I wanted to enumerate into a photoslibrary to see which image files I have im/exported and used in other places. When I create a directory enumerator, the directory attributes show that I've gotten ahold of the photoslibrary directory, but the enumerator.nextObject returns nil. I see that there are options like DirectoryEnumerator.Options.skipsPackageDescendents, but setting or unsetting the bit has no effect that I can see.


The same code works OK with ordinary directories, like ~/Pictures. Unix commands like "find" and "ls" have no problems with iterating into a photoslibrary, so am I condemned to run "find" and read its stdout, rather than using FileManger goodness?

Replies

Is your app sandboxed?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

In other contexts, I've run afoul of sandboxing, so I'm not totally surprised by your suggestion. Unfortunately I can't figure out what the sandbox state is for my app. When I try to access the sandboxing details for my target, my Xcode UI doesn’t resemble the help documentation that pops up. i.e. there is no “Capabilities” menu item between “General” and “Resource Tags”. I’m using Xcode 10.2 (10E125) and MacOS 10.14.4 on a MacBook Pro (Retina, 13-inch, Late 2013). If you have any suggestions, I’d greatly appreciate them.


I've glanced at the documentation for programmatic use of Sandbox, but since my app iterates into ~/Pictures directory and its ordinary subdirectories, it doesn't seem likely that there is a general lockout...


Is there any possibility that a Command Line Tool is lacking some setup that occurs for a Cocoa app?

Xcode 10 has a Capabilities editor (the equivalent to Signing and Capabilities in Xcode 11). One difference from Xcode 11 is that the tab is present for fewer target types. Are you working on an app? Or a command-line tool? Or something else?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I'm working on a command line tool.

To check whether sandboxing had any effect, I made a Cocoa app that ran this stub viewDidLoad.

Whether sandboxing was on or off, the app prints

directory exists

nil


override func viewDidLoad() {

super.viewDidLoad()

let fileManager = FileManager.default

let path = "/Users/rbnerf/Pictures/TestLibraryA.photoslibrary/"

var isDirectory = ObjCBool(false)

if fileManager.fileExists(atPath: path, isDirectory: &isDirectory) {

if isDirectory.boolValue {

print("directory exists")

let contents = try? FileManager.default.contentsOfDirectory(atPath: path)

print("\(contents)")

}

}

}


To show that there is content in the directory:

MBP13:~ rbnerf$ ls -l /Users/rbnerf/Pictures/TestLibraryA.photoslibrary

total 0

drwxr-xr-x@ 3 rbnerf staff 96 Jul 30 2018 Attachments

drwxr-xr-x@ 5 rbnerf staff 160 Jan 1 09:24 Masters

drwxr-xr-x@ 10 rbnerf staff 320 May 25 12:33 database

drwxr-xr-x@ 7 rbnerf staff 224 May 27 10:31 private

drwxr-xr-x@ 9 rbnerf staff 288 May 27 10:31 resources

It sounds like you need to use one of the lower-level methods that allows an options parameter that you can set to zero. There is an option for NSDirectoryEnumerationSkipsPackageDescendants that is probably being set as the defeault.

Let’s focus on the app case for the moment. I took your code, tweaked it to use a path appropriate for my Mac, put it in a new app created from the macOS > App template, and ran it. As you reported, it failed to return any results. I then went to the App Sandbox slice of the Signing & Capabilities editor and changed the Pictures Folder popup to read-only. Now it prompts for access and produces results:

directory exists
["database", "Masters.legacy", "Plugins", "ProjectDBVersion.plist", "resources", "originals", "Data.noindex", ".ipspot_update", "Projects.db", "iPhotoLock.data", "Library6.iPhoto", "Library.iPhoto", "private", "Modified", "external", "Library.data", "iPhoto.ipspot", "Data", "iPhotoAux.db"]

I then created a new app, exactly as above, but this time I removed the App Sandbox slice from Signing & Capabilities and added the Hardened Runtime instead. The code returned no results again. However, I can regain access by checking Photo Library in the above-mentioned Hardened Runtime slice.

This is pretty much as I expected. There are two standard paths for apps to access the photo library:

So, are you planning to eventually ship an app? Or is a command-line tool your final product?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] A sandboxed app can be distributed via the Mac App Store but a non-sandboxed app must be distributed independently. Independent distribution requires notarisation and notarisation requires the hardened runtime.

The Apple Thought Police ate my previous detailed post, so I'll redo in brief...

The suggestions about Sandboxing and Hardening were somewhat useful, but I still can't get

FileManager.default.contentsOfDirectory(atPath: path) to return contents for a photos library in ~/Pictures.

It will only return contents for a photos library in other directories, e.g. my home directory, or on an external disk.

The Sandboxing and Hardening options have different focus:

  • Sandboxing is concerned with access to ~/Pictures
  • Hardening is concerned with photos libraries

Neither seem essential, because I could access the libraries not in ~/Pictures without either Sandboxing or Hardening being used.

As an undocumented "feature", I regard this as a bug.

I still can't get

FileManager.default.contentsOfDirectory(atPath:path)
to return contents for a photos library in
~/Pictures
.

How were you testing that? From an app? Or from a command-line tool?

And, repeating my question from yesterday, what is your planned deployment vehicle? An app? Or a command-line tool? Or something else?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

The "app" that I used for testing is the same as before, except that I use different photo libraries:

override func viewDidLoad() {

super.viewDidLoad()


let fileManager = FileManager.default

//let path = "/Volumes/Little4a/PhotosByTopic/EagleHarbor.photoslibrary" //E

let path = "/Users/rbnerf/Pictures/TestLibraryA.photoslibrary" //A

//let path = "/Users/rbnerf/TestLibraryB.photoslibrary" //B

//let path = "/Users/rbnerf/Pictures/" //P

print ("\(path)")

var isDirectory = ObjCBool(false)

if fileManager.fileExists(atPath: path, isDirectory: &isDirectory) {

if isDirectory.boolValue {

print("directory exists")

let contents = try? FileManager.default.contentsOfDirectory(atPath: path)

print("\(contents)")

}

}

}

I have run the app with the 4 combinations of Sandboxing (with ~/Pictures readonly )and Hardened Runtime (with Photos), using each of the three libraries, (E,A & B) [B is a duplicate of A]

The contents are non-nil for E & B whenever Sandboxing is off, Hardening has no effect. No combo produced non-nil content for A


My intent is to make several apps for my own use, and see whether there is sufficient utility to offer them to others.

I want to browse the file system tree to analyze various topics in 20 years worth of files. For example:

  • Which images have been used in documents, and where? How many duplicate copies?
  • What were the dates when I was working on particular Xcode projects? How do I distinguish projects abandoned as dead ends from those that are still the "best" version?
  • What topics was I using the Mac for over the 20 years?

using each of the three libraries, (E,A & B)

Which of these is the one currently active in Photos?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

None of the above. Photos was looking at another library on the external volume.

Honestly, I can’t explain all the results you’re seeing. If I try to access the equivalent of A, it works as expected. That is:

  • If I have the hardened runtime disabled, the access prompts and succeeds.

  • If I have the hardened runtime enabled without

    com.apple.security.personal-information.photos-library
    , the access fails without a prompt.
  • If I add

    com.apple.security.personal-information.photos-library
    , the access prompts and succeeds.

Note I reset this access between runs using:

% tccutil reset Photos com.example.my-bundle-id

Usually I’d do this test on a VM (and reset the system state by restoring from a snapshot) but I don’t have time for that today.

All of the above seems pretty reasonable to me. I’m not sure what’s going on with your setup and I don’t think I’m going to be able to help you much more in the context of DevForums. My recommendation is that you open a DTS tech support incident so that I can spend more time digging into the specifics of your problem.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"