Library for storing preferences

I’ve just released a Script Library for storing preferences and persistent values. This gets around the problem where applets cannot retain property values between launches because they are codesigned, or locked to avoid repeated authorization dialogs.

PrefsStorageLib takes advantage of the system-provided user defaults system, and values are reflected in the relevant property list (.plist) files in a user’s ~/Library/Preferences folder. It requires macOS 10.11 or later. You can get it here:

1 Like

Hi Shane,

Although I don’t codesign or market scripts, thanks for this work!

I notice that some of the sample scripts have a ‘load’ line with a version specification which I haven’t seen before:

use script "PrefsStorageLib" version "1.0.0"

I would assume the version string causes old scripts to break if the library is updated, which isn’t necessarily a bad thing, but sure makes the use of a “batch replace” script to update all scripts that use this after an upgrade seems like a good idea. Does a different version break the script or is it just there as a reminder as to what version you are using?

If it breaks the script, then an applescript that uses Spotlight to search script contents for the ‘use’ line and replaces it with the upgraded version number seems to be something which might be useful? If that is possible or practical….

Quite the opposite — it makes new scripts break if used with an older version of the library.

1 Like

I think this is a feature request or a bug report or both?

It seems if a property’s value is: missing value then SD errors and hangs and has to be force quit*.

use scripting additions
use script "PrefsStorageLib" version "1.0.0"
property testProperty : missing value

prepare storage for (path to me) default values {testProperty:missing value}
--set testProperty to value for key "testProperty"
--display dialog "This applet has been run " & runCount & " times."
--set testProperty to testProperty + 1
--assign value testProperty to key "testProperty"

I know that missing value isn’t one of the data types listed as storable, but that would be nice. That’s used a lot. If that’s not doable we can add a:

if x is missing value then set x to ""

*Didn’t SD used to show it’s crash reporter when there was a forced quit?

–> Script Debugger 7.0.7 (7A72)
–> Mac OS 10.11.6 (15G22010)

This works for properties but how about other persistent variable values?

Globals and handler in the top level of a script are also persistent. Do those cause the same issues and should this resolve that?

Maybe another feature request? Would it be possible to return the path to the file where these are stored, and/or a reveal in finder command?

But mostly pointless. If there’s no value set, then the value is missing — which is what missing value means, after all. In any event, this is beyond my control. I could have the code store an empty string — but as you point out, so can you, and with less effort and more control.

To be clear, you’re getting an error only because you’re trying to include missing value in the default value record, where the value is already effectively missing value. But you can still do something like assign missing value to key "testProperty". That effectively removes the value for testProperty, so it returns missing value.

I can’t reproduce the crash here.

It works for whatever values you want it to — they don’t have to be properties. Because you can access values anywhere, there’s no reason you can’t use this approach instead of properties, where access to values is required in multiple handlers.

By default, the prepare storage command also locks the actual script file (main.scpt) inside the bundle. This means top-level values (including properties) can’t be saved back after each run, and also means the script is never modified, and hence doesn’t trigger repeated authorisation dialogs for things like GUI scripting. If, for some reason, you don’t want it locked, pass false for the write lock parameter.

You’ll have to convince me there’s a reason. To be clear, the library doesn’t store them in a file: it passes them to the defaults system, which does the rest. So if Apple decided to change the location or file format tomorrow, it wouldn’t make any difference. (Not that they are likely, but they have changed the file format before.)

In that sense, where, how and even when they get transferred from the defaults system to disk (or vice versa) is not really your concern.

The cases where I would think this is needed are the scripts for myriad tools and Dialog tool box I’ve been working on. In your samples many of the properties are initiated as missing value and reset to missing value. So I wanted the storage state to be the same.

Part of that is to help me understand what’s happening. Do they all end up in the same file? What happens if I stop using a script and trash it, do the variables stay in some file forever? Suppose I want two different apps to access the same variable set, is that doable?

I use lots of properties in my scripts and rather than trying to figure out which ones would need to be persistent and which not, I’m building a handler that will simply save all the properties.

I’m also writing and reading some values to files. But that’s slow with large lists. I’m wondering if this might be faster.

Don’t include them in the default values parameter, and they won’t have an initial value (ie, they’ll be missing value). To reset them, use remove value for key or assign a value of missing value (which does the same thing).

Yes.

They stay there. This is no different from now, where any applet that, for example, shows a choose file or similar dialog, results in a preference file. It’s the same file. That’s why applets require a unique id.

It is — but not with this library. I’m trying to avoid mission creep: this is meant to be as simple to use as possible.

You know your scripts, but it seems to me potentially overkill.

It could be, but you’ll need to test to be sure. On the negative side, there’s slight overhead in that the items are converted to Cocoa arrays (and back on reading). On the plus side, your script simply passes the value on to a system daemon, so there’s no waiting for the write to finish.

Since I have a lot of scripts with a lot of persistent variable I’m writing a script that will run from the SD script menu and work on the from script. It will build a list of all properties (this includes top level variables and globals which would normally be persistent).

It then builds handlers to store and access the persistent values.

Still a work in progress. Need to add integer; real and files for type of variable, and figure out how handle any variable that’s not an approved type.

so far it seems to work

use AppleScript version "2.5"
use scripting additions
use script "PrefsStorageLib" version "1.0.0"

tell application "Script Debugger"
   tell its document 2 -- change this to 1 when made live
      set allTheProperties to every script property whose name is not "required import items"
   end tell
   set allPropRefs to {}
   set allPropNames to {}
   repeat with thisProperty in allTheProperties
      
      set propName to name of thisProperty
      --set the end of allPropRefs to (propName & ":" & missing value)
      
      if the contents of thisProperty is not missing value then
         if the type of thisProperty is list then
            set the end of allPropRefs to (propName & ": {}")
         else
            set the end of allPropRefs to (propName & ": \"\"")
         end if
      end if
      set the end of allPropNames to propName
   end repeat
end tell
set AppleScript's text item delimiters to {", "}
set allPropRefs to allPropRefs as text
set allPropRefs to "{" & allPropRefs & "}"

set storageHandler to {"On PersistentVariables()", "prepare storage for (path to me) default values " & (allPropRefs as text)}
repeat with propName in allPropNames
   set thisLine to "set my " & propName & " to value for key \"" & propName & "\""
   set the end of storageHandler to thisLine
end repeat
set the end of storageHandler to {"end", ""}
set quitHandler to {"on quit"}
set the end of quitHandler to {"my SavePersistentValues()", "continue quit", "end quit", ""}
set savingHandler to {"On SavePersistentValues()"}
repeat with propName in allPropNames
   set thisLine to "   assign value " & propName & " to key \"" & propName & "\""
   set the end of savingHandler to thisLine
end repeat
set the end of savingHandler to {"end", ""}

set AppleScript's text item delimiters to {return}
set hanlerCalls to {"my PersistentVariables()", "", "", "my SavePersistentValues()", ""}
set newHandlers to {hanlerCalls, storageHandler, savingHandler, quitHandler} as text
set the clipboard to newHandlers as text
return newHandlers as text

I’m getting the ‘prepare storage’ command was unable to initialize user defaults error on my exported RO apps. Do you know why that might be?

Can you send me one to have a look?

Sent an email. Hope that’s ok

That’s perfect, thanks.