Problem reading Photos preferences using ASObjC

I’m currently trying to optimise some code for getting the location of the default Photos library. In the previous script, I used do shell script and defaults to dump the entire contents of the Photos preferences into a plist and @ShaneStanley provided me a way to use ASObjC to extract the key value I need.

However, as I revisit this and the larger script it is to be part of, I realised that if I’m using ASObjC it would be more efficient to access the defaults system directly with ASObjC instead of using do shell script and creating the temporary file. I thought this would be fairly straightforward and eventually came up with the following script, which I believe should work but doesn’t:

use framework "Foundation"

property pPrefsDomain : "com.apple.Photos"
property pPrefItem : "IPXDefaultLibraryURLBookmark"

set vPrefsData to current application's NSUserDefaults's alloc()'s initWithSuiteName:pPrefsDomain
set vPhotoLibLoc to (vPrefsData's objectForKey:pPrefItem)
--> missing value

In trying to understand the issue, I used @tempelmann’s excellent Prefs Editor to look for a different key to query. When I open the com.apple.Photos domain in Prefs Editor, it alerts me that there are more than one preference files for that domain:

Other	/Users/Jolin/Library/Containers/com.apple.Photos/Data/Library/Preferences
User	/Users/Jolin/Library/Preferences

Sure enough, if I set pPrefItem to the one key which appears in the ~/Library/Preferences/ file (NSUserKeyEquivalents), the script works. So it seems that the initWithSuiteName:pPrefsDomain is setting the domain to ~/Library/Preferences/com.apple.Photos.plist only. I thought it would provide a synthesised domain including the file in Containers or at least ignore the unused file in ~/Library/Preferences. The shell defaults command works and returns the ‘proper’ preference domain. How can I get NSUserDefaults to do the same? Thanks for any help.

As far as I can see, you can’t unfortunately. That was the first thing I looked at.

1 Like

Thanks for confirming. Frustrating, but good to know it wasn’t something obvious I’ve missed. And your previous solution works well, so this is arguably unnecessary anyway!

I had one more run at this as another thought occurred to me as I was making a coffee :grin:. I wondered if passing a path to initWithSuiteName instead of just the domain identifier would work (since the defaults command allows this), and it does! This can be simply achieved in my code above by changing the value of pPrefsDomain:

property pPrefsDomain : "/Users/Jolin/Containers/com.apple.Photos/Data/Library/Preferences/com.apple.Photos"

(Note that when specifying a path for NSUserDefaults and defaults command, you should omit the .plist from the end of the filename as per Apple’s documentation.)

So the script worked with that change. However, I’d prefer something more generalised for potential future reuse. The two issues I had are:

  1. Anyone using this script shouldn’t have to investigate whether the app they are targeting is sandboxed or not (using the Containers folder for preferences location).

  2. I don’t want a hardcoded username in the script. That will cause it to break if my username or home folder changes in the future and requires editing if anyone else wants to use the code.

So my final script for reading a preference value using the MacOS defaults system both constructs the path using path to library folder and checks whether there is a file for the specified domain in ~/Library/Containers/, to determine which domain to pass to NSUserDefaults. Except for the last line in the following script, it is generic – you can retrieve any preference value by setting pPrefsDomain and pPrefItem to the appropriate values. The last line is specific to my current use case: it converts the bookmark data type returned as the Photos library location to an AppleScript alias.

-- This script uses MacOS’s ‘user defaults’ system to get the preference value ‘pPrefsItem’ from the app specified by ‘pPrefsDomain’.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"

property pPrefsDomain : "com.apple.Photos"
property pPrefItem : "IPXDefaultLibraryURLBookmark"

set vSandboxedPrefsPath to (POSIX path of (path to library folder from user domain)) & "Containers/" & pPrefsDomain & "/Data/Library/Preferences/" & pPrefsDomain
tell application "Finder" to if exists vSandboxedPrefsPath & ".plist" as POSIX file then set vSandboxed to true
if vSandboxed then
	set vPrefsPath to vSandboxedPrefsPath
else
	set vPrefsPath to pPrefsDomain
end if

set vPrefsData to current application's NSUserDefaults's alloc()'s initWithSuiteName:vPrefsPath
set vPrefValue to (vPrefsData's objectForKey:pPrefItem)

set vPhotoLibLoc to (current application's NSURL's URLByResolvingBookmarkData:vPrefValue options:0 relativeToURL:(missing value) bookmarkDataIsStale:(missing value) |error|:(missing value)) as alias

2 Likes

Yep, passing a full path to the plist file is the sanctioned way to do it. Prefs Editor does this, too.