I have been struggling with a weird quirk in AppleScript. Here is a test script:
property gg : "Hello Rob"
display dialog gg
set gg to "Goodbye"
If I run this script from the Script Editor, it works as expected. The first time it is run, it displays “Hello Rob” and any subsequent run it displays “Goodbye”.
But if I run this script from the Script Menu in iTunes, it does not work that way. Compile the script but do NOT run it and save it in ~/Library/iTunes/Scripts. EVERY time you run the script from the script menu in iTunes, it displays “Hello Rob”. Changes to the gg property are NOT remembered.
What is happening? Is there a way to use properties in a script that is called from the iTunes Script Menu where changes to the properties’ values are remembered between runs of the script?
Run repeatedly from the Script Editor or from an applet, the property is saved. Running from a script menu in an applications script menu recompiles the script every time so it takes the initial value every time.
Either save the script as an app (nuisance because you have to wait for it to start) or save the property yourself by writing it to a file and reading it back.
Thanks for the replies. Well starting with iTunes 7.3.2, scripts saved as apps CANNOT be launched from the iTunes Script Menu. (Only scripts can be launched from the iTunes Scripts menu). I do not know why Apple decided to limit the functionality of the iTunes Script Menu. Maybe it is a bug.
I also do not think that the script is recompiled every time. If you run my test script a couple of times from the Script Editor and then save it as a script into ~/Library/iTunes/Scripts, the script will display 'Goodbye" every time you run it. It appears that the value of the property when the script is saved is what iTunes uses. Very strange.
At this stage, it looks like I have three options:
Wait until Apple fixes the bug and allows me to launch apps from the iTunes Script Menu.
Run the Script oustide the iTunes Scripts Menu
Save and load the properties to/from a file.
Option 3 looks like the best approach even though it is a lot more work.
Here’s some subroutines I have to help with reading, writing, and deleting things from plist files. I hope they help.
(*this shows how to use the defaults subsystem to read, write, and delete key value pairs in preference files*)
(* the plist file is written in binary format... to convert it to text: do shell script "plutil -convert xml1 " & prefsFilePath & ".plist" *)
(*
keyType values can be:
string
data ==> the data must be in hexadecimal
integer ==> a number
float ==> a floating point number
boolean ==> true,false,yes,no
date ==> a date value
array ==> an array in the form defaults write somedomain preferencekeyP -array element1 element2 element3
dict ==> a new dictionary in the form defaults write somedomain preferencekeyP -dict keyP1 value1 keyP2 value2
*)
property prefFolderLocation : path to preferences folder from user domain as Unicode text
property prefFileName : "testPlist"
property prefsFilePath : prefFolderLocation & prefFileName
property keyName : "aString"
property keyType : "string"
set keyValue to "Hello Rob"
writeDefault(prefsFilePath, keyName, keyType, keyValue)
set gg to readDefault(prefsFilePath, keyName)
display dialog "This should be \"Hello Rob\"" & return & return & "The value is: " & gg
delay 0.5
set keyValue to "Goodbye"
writeDefault(prefsFilePath, keyName, keyType, keyValue)
set gg to readDefault(prefsFilePath, keyName)
display dialog "This should be \"Goodbye\"" & return & return & "The value is: " & gg
(*========== Subroutines ===========*)
on writeDefault(prefsFilePath, keyName, keyType, keyValue)
set keyType to ("-" & keyType) as Unicode text
if prefsFilePath contains "/" then
do shell script "defaults write " & quoted form of prefsFilePath & space & quoted form of keyName & space & quoted form of keyType & space & quoted form of keyValue
else
do shell script "defaults write " & quoted form of POSIX path of prefsFilePath & space & quoted form of keyName & space & quoted form of keyType & space & quoted form of keyValue
end if
end writeDefault
on readDefault(prefsFilePath, keyName)
if prefsFilePath contains "/" then
do shell script "defaults read " & quoted form of prefsFilePath & space & quoted form of keyName
else
do shell script "defaults read " & quoted form of POSIX path of prefsFilePath & space & quoted form of keyName
end if
end readDefault
on deleteDefault(prefsFilePath, keyName)
if prefsFilePath contains "/" then
do shell script "defaults delete " & quoted form of prefsFilePath & space & quoted form of keyName
else
do shell script "defaults delete " & quoted form of POSIX path of prefsFilePath & space & quoted form of keyName
end if
end deleteDefault
Side note: Your write handler is better, but I would consider using something like this so you don’t have to pass the filename every time:
script userDefaults
property _domain : quoted form of "Test" -- quoted form of either a "domain" or POSIX path
on readKey(_key, _defaultValue)
try
do shell script "/usr/bin/defaults read " & _domain & space & quoted form of _key
on error
return _defaultValue
end try
end readKey
on writeKey(_key, _value)
do shell script "/usr/bin/defaults write " & _domain & space & quoted form of _key & space & quoted form of _value
end writeKey
end script
-- Example
repeat
tell userDefaults to readKey("Test", "New value")
display dialog ("Current value: " & result) default answer ""
tell userDefaults to writeKey("Test", text returned of result)
end repeat
In my script the file was specified as a global variable, so it didn’t need to be passed anyway, but those subroutines were written for the general case. If he knows exactly the form he’s using in a particular script then of course they can be tailored for the specific case, so why stop with just not passing the file?
In this specific case we know several things…
properties are global values so we don’t need to pass any of them
the default keyType is string so it doesn’t even need to be declared at all
the default path to save the preference file is the user’s preferences folder, so we don’t need to specify a path.
the prefFileName is one word so it doesn’t need to be quoted
the keyName is one word so it doesn’t need to be quoted
the value will be in the plist file so we don’t need to error check during the read
As such, the script for this specific case could be written as follows if that’s what you want to do. By the way, I liked your error checking in the “read” subroutine and the way you defined the path to the defaults command… I’ve implemeted those fixes into my script library.
property prefFileName : "testPlist"
property keyName : "aString"
writeDefault("Hello Rob")
repeat
display dialog "Current Value: " & (readDefault()) default answer ""
writeDefault(text returned of result)
end repeat
on writeDefault(keyValue)
do shell script "/usr/bin/defaults write " & prefFileName & space & keyName & space & quoted form of keyValue
end writeDefault
on readDefault()
do shell script "/usr/bin/defaults read " & prefFileName & space & keyName
end readDefault
Thanks for all the replies. And yes Bruce using the System Script Menu is an option. I apologize for omitting it from my list of options.
Ii have also been trying to use another option but I can’t get it to work. Save the script as a Application and then create another script to launch this application.
e.g.
run application “ABCD”
And then place this script in the iTunes Script Menu. (Since iTunes 7.3.2, only Compiled Scripts show up in this menu).
But I can’t get it to work. Running this mini script in the Script Editor generates an error – “Connection is invalid”. But when i run this one line script from the iTunes Script Menu, I get the Beach Ball and it crashes iTunes.