It is a general-purpose file manager (a "Finder replacement" for former Windows users), which is why it's essential for the app to have a user-defined, permanently-living bookmark.
Got it. That's fine, just want to make sure I understand the general context.
I restore an existing bookmark from @AppStorage (UserDefaults-backed), and it always restores all access - including ~/Library/Mobile Documents - without presenting the panel again.
Huh. That's interesting, because the current theory on the bug side is that the bookmark isn't preserving a secondary attribute for user intent that the open panel includes. However, that wouldn't really make sense if you were always working out off a bookmark.
Similarly, clarification here:
This is consistent with the user-directory-class difference you described - standard user directories like ~/Documents recover via bookmark re-resolution mid-session, while ~/Library/Mobile Documents does not.
Making sure I'm understanding correctly, bookmarks targeting ~/Documents/ and ~/Downloads/ continue to work but ~/Library/Mobile Documents does not? Have you identified any other directories that fail at the same time? What about bookmarks to objects inside ~/Library/Mobile Documents? If this were purely about user intent issues, I'd expect "all" of them to fail at the same time.
One other test I'd throw into the mix - I would create a special bookmark (probably several) that specifically targets locations that are completely outside the user’s normal interaction locations but that you can't directly access (meaning, the user has to give you access to them). For example, some objects on an external volume.
Then, go into your normal reproduction process but making sure you DON'T interact with those "special" bookmarks/locations in any way. Finally, once the problem starts happening, see if you can resolve and access those "special". The point of this test is to specifically confirm that the low-level resolution process still "works" and that this isn't a broader/systemic failure. On the surface, you could do that by just resolving any bookmark, but the problem here is that objects that manage access in the kernel are tied to file system locations, not specific references. That means it's possible for:
-
Your app to lose the ability to access new locations (typically because it's leaked/consumed to many security scope grants).
-
Your app to still be able to access many locations (because the existing scope grants "happen" to cover those locations).
This can end up making a general breakdown look like a more specific failure, just because your existing set of grants (#2) happen to cover the "right" locations.
While this would probably be the easiest option, I'm afraid that App Review will likely push back on Temporary Exceptions for a general-purpose file manager.
Yes, absolutely. I think there may be value in testing both FDA and the access entitlements as a way to investigate what's going on, but they're definitely not what I would ship in a consumer product or expect the App Store to approve.
This is the most interesting suggestion - I hadn't considered it. Currently, the original URL from the panel is simply autoreleased by ARC once the initial setup is done. I'll keep the original URL with the intentional multiple startAccessingSecurityScopedResource() calls and test whether that keeps ~/Library/Mobile Documents alive across daemon restarts.
SO, the other variation on this approach, and what I would probably do if I were starting "from scratch", is to intentionally move ALL file handling/interaction out of your main "app" and into a helper tool. The app the user interacts with is (essentially) written as a "viewer" which then "displays" the file data your helper tool is accessing and retrieving. If/when "anything" goes wrong, your app just kills the helper tool, relaunches it, and then keeps going.
It's considerably more work [1], but the advantage of this approach is that it "hardens" your overall app experience against "whatever" happens in a way that the other approaches simply won't.
[1] I'm not sure how well this would work in practice, but in theory, you could move the VAST majority of your app’s entire implementation (all of its UI/views/etc.) into an app extension, which your app basically just presents one (or more) giant "view(s)". Your main app job is to present that view and maintain state information about where/what the user is doing (which the extension feeds back to it). If/when anything does wrong, your app simply terminates the existing extension and launches a new instance, using its state data to return back to its previous, exact, state [2].
[2] If your app state information is good enough, it's also possible for your main app to do exactly the same thing for "itself". When anything does wrong, your app captures its current state, uses NSWorkspace to kick off a new launch of itself, then terminates itself.
In both of the above cases, this would be somewhat user visible but, if done really well, minimally disruptive to the user.
P.S. One more thought: is it possible the re-resolution simply happens too fast?
It's certainly possible that timing is a factor here; however, it's not clear which daemon that would involve and why. Generally speaking, the resolution and security control process doesn't actually involve cloudd (it happens at a lower level of the system), so, theoretically, the cloudd itself shouldn't matter.
Having said that, it's certainly worth testing this:
I'm going to also test a delayed retry (say, 10s) before concluding the state is unrecoverable.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware