Next week, we’ll be releasing v2.1.2 of SuperDuper with various improvements, including Growl support. I think you’re all going to like it.
We’d been investigating various methods of notifying users of successful and failed copies for some time, especially for users who are backing up headless or remote systems, and decided to leverage Growl’s well-tested and mature functionality rather than roll our own. It’s a nice package, and I’m glad it’s out there.
There’s a ton of flexibility built into Growl: you can show status visually with various types of floating panels and pop-ups, mail to any account, and even forward notifications to other machines on your network.
Putting this into SuperDuper! initially seemed pretty easy, but we ran into some unusual problems with our AppleScript-based “schedule driver” that I thought might prove interesting.
Obviously, we couldn’t require Growl—a 3rd party application—to be present on a user’s system. The idea here is to use Growl if present, not mandate it. But, our schedule driver—which can be extended by the user—is compiled “on-the-fly” when a schedule is created or modified.
But, while AppleScript has various techniques for dealing with a missing application or dictionary when running a compiled script, it really, really, really wants it to be present during compilation. So, a statement like
tell application “GrowlHelperApp”
notify with name “Scheduled Copy Succeeded” title “SuperDuper! Copy Succeeded” description “Copy was successful.” application name “SuperDuper!”
end tell
won’t work if GrowlHelperApp isn’t present on the User’s System. Drat.
AppleScript does, however, have something called “raw event syntax”. Basically, you pre-compile your statement, hand-compiling it into Apple Events. Thus:
tell application “GrowlHelperApp”
«event notifygr» given «class name»:"Scheduled Copy Succeeded”, «class titl»:"SuperDuper! Copy Succeeded”, «class desc»:"Copy was successful.”, «class appl»:"SuperDuper!"
end tell
But, this won’t work either: even though there’s nothing in the tell block that needs the dictionary, the tell itself will try to reference GrowlHelperApp, and fail. And if GrowlHelperApp is there, your AppleEvents will be magically transformed into AppleScript when you compile!
So, there’s no choice here—you have to have a tell block, or the events don’t go to the right application. But you can’t “tell” an application and have the compilation succeed if the user doesn’t have Growl installed! Or can you…
Fortunately, a little evil goes a long way in AppleScript. By using a string variable, rather than a string literal, we can prevent the compiler from loading the dictionary at compile time—and since we’re using raw event syntax, there’s no error. Thus:
set growlAppName to “GrowlHelperApp”
tell application growlAppName
«event notifygr» given «class name»:"Scheduled Copy Succeeded”, «class titl»:"SuperDuper! Copy Succeeded”, «class desc»:"Copy was successful.”, «class appl»:"SuperDuper!”
end tell
Bingo! This actually allowed compilation… and worked fine when tested in a simple script. But, in the real one, it didn’t work. Instead, I got the following runtime error:
Finder got an error: application “GrowlHelperApp” doesn’t understand the «event notifygr» message.
Now, of course, it does understand the message, because it worked in the simple case!
It took a while for me to figure it out, but the difference, was that in the real script, the Growl notification was in a nested tell block. And, even though the “nearest tell” is for GrowlHelperApp, for some reason this was seen as Finder’s GrowlHelperApp, rather than mine. (You’d think they’d be the same, but not so much.)
The solution, after much gnashing of teeth, was to explicitly reference the script’s main execution context, with:
set growlAppName to “GrowlHelperApp”
tell my application growlAppName
«event notifygr» given «class name»:"Scheduled Copy Succeeded”, «class titl»:"SuperDuper! Copy Succeeded”, «class desc»:"Copy was successful.”, «class appl»:"SuperDuper!”
end tell
I’m telling you, AppleScript has the unique ability to make me feel like the stupidest developer on the face of the earth. Kudos to you, AppleScript!
Anyway, expect this early this week…
11 Jun 2006 at 01:05 pm | #
A person’s skill & patience with Applescript is often inversely proportional to their skill & patience with other programming languages. I’ve known plenty of amazing Applescript programmers who are perplexed by, say, Objective-C—and the other way around.
11 Jun 2006 at 03:23 pm | #
Very apt comment by John. I find programmers frequently (but not always) fall into one of two camps - those who like more “natural language"-oriented programming, like Applescript, Perl, etc, and those who like very deterministic languages like C, Java, etc.
There is very different emphasis in the two camps - the first focuses on getting as much done in the smallest amount of code, and generally is very flexible with variable typing, implicit instructions, and very powerful syntax. At the same time, its power is its weakness - it makes it very hard to troubleshoot because the language itself can be fuzzy, and too helpful.
The second group of languages force you to think more like the computer - very little is taken care of for you, variables are firmly defined and difficult to translate. This means a lot more work to do seemingly simple things - to open all files in a folder and process them in some way will take paragraphs if not pages of code. However, the benefit is you can see *exactly* what the code is doing, and know exactly what side-effects (if any) are involved.
To each their own, and each has its uses. Personally, I tend to prefer the latter, but envy the power of the former. To me, a scripting language with very expressive commands, but strong variable typing would be ideal.
I tend to use AppleScript in small doses, as I’ve never quite come around to its syntax - using a very English-like syntax makes me think that the various other ways of expressing a command should be valid, yet they aren’t. So, I spend most of my time trying to get the particular syntax correct. And the automatic type conversion has bitten me numerous times on alias vs file vs paths vs whathaveyou. At the same time, I’d never try to write a lot of these in C/C++, as the connectivity to other applications, and the file manipulation capabilities are both so innately powerful.
For large projects, or anything that will end up for an end-user, I tend to use C/C++, as I can tightly control the data structures, and to a certain extent predict behvaior better. Of course, I avoid a lot of the headaches in that arena by relying on static compliation where I can and I don’t tend to write programs that need multithreading or shared ownership of memory.
11 Jun 2006 at 04:06 pm | #
In my case, I think it’s the natural syntax that throws me off. Dynamic languages aren’t really much trouble, but the relatively arbitrary and “chatty” nature of AppleScript, with words like “my” having huge overloaded meaning, while words like “the” get by meaning nothing, just addles my little, shrinking brain…
12 Jun 2006 at 06:57 pm | #
Could you try something like the following?
on isAppRunning(s)
set isRunning to false
tell application “System Events”
try
set isRunning to exists application process s
end try
end tell
return isRunning
end isAppRunning
display dialog (isAppRunning("GrowlHelperApp"))
20 Jun 2006 at 03:25 pm | #
Although this example is slightly differnet than your case the “using terms from” feature might be helpful:
http://developer.apple.com/documentation/AppleScript/Conceptual/StudioBuildingApps/chapter04/chapter_4_section_2.html#//apple_ref/doc/uid/20001251-BAJGFJIH
20 Jun 2006 at 04:11 pm | #
Two responses:
Andy: no. That will tell you whether Growl is running, but growl’s execution status is irrelevant during compilation, which is the real issue here. The problem was how to get the script to compile when Growl wasn’t installed on the user’s system. (The runtime conditional, though, uses System Events, yes. But the primary problem isn’t a runtime one, it’s a compile-time one.)
Ron: again, no. The problem is trying to get things to compile. “using terms from” only works if the the thing is there to use terms from. But, when a user’s system doesn’t have Growl present, the compilation will fail… which leaves me in the original situation.
Thanks for both of your suggestions, though.