defaults and dates

Not sure where I got this code. I suspect Shane posted at some point or in one of his books and I altered… I have a library file and main script calling it. It either returns a _NSTaggedDate error or causes the script to quit unexpectedly. Ive tried a few options…

Main Script code…



use AppleScript version "2.4"
use framework "Foundation"
use framework "AppKit"
use defaultsLib : script "defaultsLibBeta"
use scripting additions

property NSApp : a reference to current application's NSApp
property NSUserDefaults : a reference to current application's NSUserDefaults
property theDefaults : missing value

set theDefaults to current application's NSUserDefaults's standardUserDefaults()

set defaultsObj to defaultsLib's makeDefaultsWithID:theID factoryValues:(missing value)
set lastDate to theDefaults's objectForKey:"lastDate" ---causes NSDate Error
--tried this also
--set lastDate to defaultsObj's dateForKey:"lastDate" ---unexpectedly quits
	
set lastDate to my cocoaToASValue(lastDate) 
set nowDate to current date
set todayNow to date string of (current date)
set todayStart to date (todayNow & "at 12:00:00 AM")
--
if lastDate is not missing value and lastDate < todayStart then
---do something
end


Library script ---------------------------

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"

-- classes, constants, and enums used
property NSUserDefaults : a reference to current application's NSUserDefaults
property |NSURL| : a reference to current application's |NSURL|

script forInheritance
	-- required for inheritance
end script

on makeDefaultsWithID:scriptId factoryValues:initialValues
	script
		-- classes, constants, and enums used
		property NSUserDefaults : a reference to current application's NSUserDefaults
		property parent : forInheritance
		property scriptDefaults : missing value
		--- from logs portion
		property |NSURL| : a reference to current application's |NSURL|
		property NSFileManager : a reference to current application's NSFileManager
		property NSDirectoryEnumerationSkipsHiddenFiles : a reference to 4
		property NSString : a reference to current application's NSString
		-- Storage handlers
		
		-- This method is use for strings, records, lists, Cocoa dates
		on setObject:anObject forKey:aKey
			scriptDefaults's setObject:anObject forKey:aKey
		end setObject:forKey:
		
		on setolddate:aDate forKey:aKey
			scriptDefaults's setObject:aDate forKey:aKey
		end setolddate:forKey:
		
		on setInteger:anInteger forKey:aKey
			scriptDefaults's setInteger:anInteger forKey:aKey
		end setInteger:forKey:
		
		on setReal:aDouble forKey:aKey
			scriptDefaults's setDouble:aDouble forKey:aKey
		end setReal:forKey:
		
		on setBool:aBool forKey:aKey
			scriptDefaults's setBool:aBool forKey:aKey
		end setBool:forKey:
		
		-- File storage methods
		
		on setFile:aFile forKey:aKey
			set thePath to POSIX path of theFile
			set theURL to current application's class "NSURL"'s fileURLWithPath:thePath
			scriptDefaults's setURL:theURL forKey:aKey
		end setFile:forKey:
		
		on setAlias:anAlias forKey:aKey
			set thePath to POSIX path of anAlias
			set theURL to current application's class "NSURL"'s fileURLWithPath:thePath
			set theURL to theURL's fileReferenceURL()
			scriptDefaults's setURL:theURL forKey:aKey
		end setAlias:forKey:
		
		-- Retrievers, returning AppleScript values
		
		on stringForKey:aKey
			return (scriptDefaults's stringForKey:aKey) as string
		end stringForKey:
		
		on listForKey:aKey
			return (scriptDefaults's arrayForKey:aKey) as list
		end listForKey:
		
		on olddateForKey:aKey
			return (scriptDefaults's objectForKey:aKey) as date
		end olddateForKey:
		
		on recordForKey:aKey
			return (scriptDefaults's dictionaryForKey:aKey) as record
		end recordForKey:
		
		on integerForKey:aKey
			return (scriptDefaults's integerForKey:aKey) as integer
		end integerForKey:
		
		on realForKey:aKey
			return (scriptDefaults's doubleForKey:aKey) as real
		end realForKey:
		
		on boolForKey:aKey
			return (scriptDefaults's boolForKey:aKey) as boolean
		end boolForKey:
		
		on fileForKey:aKey
			set theURL to scriptDefaults's URLForKey:aKey
			return (theURL's |path|() as text) as «class furl»
		end fileForKey:
		
		on dateASKey:aKey
			set theDate to scriptDefaults's objectForKey:aKey
			return theDate as date
		end dateASKey:
		
		-- Handlers for dealing with AS dates
		
		on registerDateDefault:aDate forKey:aKey
			set theData to current application's NSKeyedArchiver's archivedDataWithRootObject:aDate
			set aDictionary to current application's NSDictionary's dictionaryWithObject:theData forKey:aKey
			scriptDefaults's registerDefaults:aDictionary
		end registerDateDefault:forKey:
		
		on setDate:aDate forKey:aKey
			set theData to current application's NSKeyedArchiver's archivedDataWithRootObject:aDate
			scriptDefaults's setObject:theData forKey:aKey
		end setDate:forKey:
		
		on dateForKey:aKey
			set theData to scriptDefaults's objectForKey:aKey
			return (current application's NSKeyedUnarchiver's unarchiveObjectWithData:theData) as date
		end dateForKey:
		
		
		-- Generic handlers for AppleScript values (dates, enumerators, etc)
		
		on registerASDefault:aValue forKey:aKey
			set theData to current application's NSKeyedArchiver's archivedDataWithRootObject:aValue
			set aDictionary to current application's NSDictionary's dictionaryWithObject:theData forKey:aKey
			scriptDefaults's registerDefaults:aDictionary
		end registerASDefault:forKey:
		
		on setASValue:aValue forKey:aKey
			set theData to current application's NSKeyedArchiver's archivedDataWithRootObject:aValue
			scriptDefaults's setObject:theData forKey:aKey
		end setASValue:forKey:
		
		on ASValueForKey:aKey
			set theData to scriptDefaults's objectForKey:aKey
			set aValue to (current application's NSKeyedUnarchiver's unarchiveObjectWithData:theData)
			set anArray to current application's NSArray's arrayWithArray:{aValue}
			return item 1 of (anArray as list)
		end ASValueForKey:
		
		
		
		on is_running(appName)
			tell application "System Events" to (name of processes) contains appName
		end is_running
		
		on itemExists(theItem)
			set pathStr to POSIX path of theItem
			return (NSFileManager's defaultManager()'s fileExistsAtPath:pathStr) as boolean
		end itemExists
	end script
	
	set ASPrefs to result
	set theDefaults to current application's NSUserDefaults's standardUserDefaults()
	theDefaults's registerDefaults:initialValues
	set ASPrefs's scriptDefaults to theDefaults
	return ASPrefs
end makeDefaultsWithID:factoryValues:

You don’t have an objectForKey: handler in your library.

Are you sure the defaults you’re reading has an entry for the key “lastDate”?

I’d also suggest that using my PrefsStorageLib is generally more reliable than the script-object approach that library uses. Available here:

https://latenightsw.com/freeware/

Shane,
I tried prefsStorageLib like it better BUT had similar errors. This forced me track down that I was comparing NSDate with AppleScript Dates. It seems to work in unreliably. So that’s Fixed now…

Now I have a new issues.
Testing code in Script Debugger works perfect.

Export to enhanced App and it gives this error:

You’re not pass the ID of the applet are you? If so, don’t – pass (path to me).

I have subroutines in the applet that use the prefsLib & then scripts that need to use the same ID/Prefs.

if I put this in all of the subroutines and applet will it work?


if current application's id = id of me then -- must be running as applet
	prepare storage for (path to me)
else
	set theID to "com.myinfo.myapp"
	prepare storage for domain theID
end

I tried and it worked until I put it into a library. I guess for now Ill put the subroutines in the Applet with the


prepare storage for (path to me)

and in the library I won’t call it from Applet. Hopefully Bundle works fine with


prepare storage for domain theID

I cant seem to get the error to go away in a bundle. Im using


prepare storage for (path to me)

but get error

You use “path to me” in applets only.

Shane
It’s still causing errors in a ScriptBundle being called by an Applet via Run Script. No matter what I get

If it is

prepare storage for (path to me)

and

if it is

prepare storage for domain theID

I have been troubleshooting it and the issue seems to be in the code


set theResult to NSUserDefaults's alloc()'s initWithSuiteName:domainString
if theResult = missing value then error "The 'prepare storage' command was unable to initialize user defaults. Unknown error"

Are you sure

NSUserDefaults's alloc()'s initWithSuiteName:domainString

will always return results when not an App?

This is how I do it and it works. Are you doing anything different?

(I have a script that builds a version of this from the script in the front Script Debugger tabs, and uses whatever properties it finds in that script, and it works every time).


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use script "PrefsStorageLib" version "1.1.1"
property prop1 : "A"
property prop2 : 2
property prop3 : {}

set myDomain to "com.myInfo.scriptInfo"
PersistentVariables(myDomain)
--StorePersistentValues()
--RetreiveStoredValues()

on PersistentVariables(myDomain)
	prepare storage for domain myDomain
	
	set my prop1 to value for key "prop1"
	set my prop2 to value for key "prop2"
	set my prop3 to value for key "prop3"
end PersistentVariables

on StorePersistentValues()
	assign value prop1 to key "prop1"
	assign value prop2 to key "prop2"
	assign value prop3 to key "prop3"
end StorePersistentValues

on RetreiveStoredValues()
	set my prop1 to value for key "prop1"
	set my prop2 to value for key "prop2"
	set my prop3 to value for key "prop3"
end RetreiveStoredValues

Short answer I don’t think so. It’s only happening in the Script Bundle not the applet. Using the prepare storage for domain “com.somedomain.appname” doesn’t work.

I am combining an Applet with script bundles inside of the applet and share the same properties. It was working before 8 very reliably. I think it has to do with Properties. SD8 clears them when saving. This is when things started breaking or there’s a new include Frameworks checkbox in Export which I have tried both ways. Not blaming SD8…Im assuming its me… I think its likely a clash of variable names. For example one of my scripts had a properties theDefaults : missing value and all had theID which I was using to label the Domain. Both are in Shanes library…but after changing still get same error.

Completely diff subject…Would love to see your script that creates the properties. Very cool.

Here’s the script. It works with ScriptDebugger and the script it produces (on the clipboard) will use Shane’s PrefsStorageLib which is available here:

https://latenightsw.com/freeware/
PrefsStorageLib 1.1.0

PrefsStorageLib is a library that makes it easy for scripts to store persistent values in their existing preference property list files using the standard application defaults system. This gets around the problem where applets cannot retain values between launches because they are notarized, or locked to avoid repeated authorization dialogs. Works with macOS 10.11 or later. (Updated March 11, 2020)

set saveTID to AppleScript's text item delimiters

set myText to GetScriptText(1) --Change to (2) if
-- you're editing in the first window and working on the second

set keysAndValues to {}
set AppleScript's text item delimiters to {" : "}
set x to 0
repeat with thisGraph in myText
	set x to x + 1
	set thisGraph to thisGraph as text
	try
		copy word 1 of thisGraph to paraLead
		if paraLead is "property" then
			set lastPropertyLine to x
			set thisKey to word 2 of thisGraph
			set keyValue to the rest of text items of thisGraph as text
			set the end of keysAndValues to {thisKey, keyValue}
		end if
	end try
end repeat
set AppleScript's text item delimiters to {""}

set defaultValues to {"{¬"}
set valuesForKeyLines to {}
set savingValueLines to {}
set backupWriteCalls to {}
set readBackupCalls to {}
set keyValueCount to count of keysAndValues
repeat with x from 1 to keyValueCount
	set thisKey to item x of keysAndValues
	set {keyName, keyValue} to thisKey
	if x = keyValueCount then
		set the end of defaultValues to {keyName, ":", "{}¬"} as text
	else
		set the end of defaultValues to {keyName, ":", "{}, ¬"} as text
	end if
	
	--set the end of defaultValues to {keyName, ":", "{}"} as text
	set the end of valuesForKeyLines to "	set my " & keyName & " to value for key \"" & keyName & "\""
	set the end of savingValueLines to "	assign value " & keyName & " to key \"" & keyName & "\""
	
end repeat

set the end of defaultValues to "}"
set AppleScript's text item delimiters to {return}
set defaultValues to defaultValues as text

set storagePrepCall to {("prepare storage for (path to me)")} as text

set AppleScript's text item delimiters to {return}

set PersistentVariableHandler to {"on PersistentVariables()", ¬
	storagePrepCall, ¬
	"end PersistentVariables"} as text

set SaveVariablesHandler to {"on StorePersistentValues()", ¬
	savingValueLines, ¬
	"end StorePersistentValues"} as text

set RetreiveVariableHandler to {"on RetreiveStoredValues()", ¬
	valuesForKeyLines, ¬
	"end RetreiveStoredValues"} as text


set persistentVariableCalls to {{""}, ¬
	{"PersistentVariables()"}, ¬
	{"--StorePersistentValues()"}, ¬
	{"--RetreiveStoredValues()"}, ¬
	{""}}


set item lastPropertyLine of myText to {item lastPropertyLine of myText, persistentVariableCalls}

set the end of myText to {¬
	PersistentVariableHandler, "", ¬
	SaveVariablesHandler, "", ¬
	RetreiveVariableHandler, ¬
	""}

set myText to myText as text

set myText to FindChange(myText, "√¬√", "¬" & return)
set the clipboard to myText

set resultAlertreply to display alert ("Paste in persistent variables") ¬
	message ("The Clipboard contains the entire script with persistant variable handlers" & return & return & "You may paste it into your script.") ¬
	as informational ¬
	buttons {"OK"} ¬
	default button 1 ¬
	giving up after 30

return myText as text

on FindChange(textToSearch, textToFind, textToReplace)
	set saveTID to AppleScript's text item delimiters
	set AppleScript's text item delimiters to textToFind
	set textToSearch to every text item of textToSearch
	set AppleScript's text item delimiters to textToReplace
	set textToSearch to textToSearch as string
	set AppleScript's text item delimiters to saveTID
	return textToSearch
end FindChange

on GetScriptText(DocNumber)
	--tell application "TextWrangler" to set myText to text of window 1
	tell application "Script Debugger"
		tell its document DocNumber
			set myText to its source text
		end tell
	end tell
	
	set myText to paragraphs of myText
	set the beginning of myText to ""
	set AppleScript's text item delimiters to {return}
	set myText to myText as text
	set myText to FindChange(myText, "¬" & return, "√¬√")
	set textHeader to {}
	
	if return & "use scripting additions" is not in myText then
		set the end of textHeader to "use scripting additions"
	end if
	if return & "use script \"PrefsStorageLib\"" is not in myText then
		set the end of textHeader to "use script \"PrefsStorageLib\" version \"1.1.0\""
	end if
	if return & "use script \"FileManagerLib\"" is not in myText then
		set the end of textHeader to "use script \"FileManagerLib\" version \"2.3.5\""
	end if
	set myText to (textHeader as text) & myText
	set myText to paragraphs of myText
	return myText
end GetScriptText

So you have two AppleScript Bundles running inside an appleScript app, right? Being called by the app?

Have you tried it this way:


use script "PrefsStorageLib" version "1.1.1"

prepare storage for alias applicationPath

You’re a genius! Sometimes the obvious solution is right in front of us. Works perfect Thanks again.

@Shane Just wanted to point out to you that this issue was resolved by workaround when sharing prefs between Scripts/Bundles. There seems to be a bug in PrefsStorageLib. If I specify the domain in a script bundle via

prepare storage for domain myDomain

and the script is triggered by another script, it is creating a plist file

not actually using the domain name specified. It is strange because if I run it from script debugger, it is fine. Run triggered by another script/app it fails to use the domain specified.

The workaround was to specify the location of the actual app that created the .plist file via Posix path. Instead of using (path to me) or domain which wouldn’t work, I ended up using

prepare storage for posixPathtoApp

If you mean the script is run by another script, that’s expected behavior.

I can see how there needs to be a solution to if it cannot figure out what to name the plist file, name it. I just thought that was the intent of the domain.

So…Not a bug…might good to document, that even if you specify the domain, it will be ignored if triggered by another app or script with the possible workaround.

Thanks as always for your awesome tools and support!

The key is that defaults are normally stored according to the host’s domain – that is, the application running the script. if you run a script other than as an applet, the host and the script will be two different entities.

This is covered in the Read Me – see the section Using PrefsStorageLib outside applets.

Makes a lot more sense now. Hopefully this helps someone in the future. Thanks again!