Friday, December 14, 2007

Die, dammit

Fresh from my exploits with the scripting bridge yesterday, I figured it would be a simple matter to script the creation and deletion of a rule in the Mail app.

The code for making a new rule in Mail with the scripting bridge is beautifully concise, being a mere 9 lines of very straightforward code of the general pattern:
- Make the instance of the scripting class X, with properties P
- Get the element that is the collection of things X in the containing class (in this case the Mail app itself)
- addObject X to this collection

On the first test run I had a perfectly specified new rule in Mail (as checked by looking at the Mail preferences' rules panel).

So, removing a rule from this collection should be a doddle, right?

The scripting bridge docs talk about adding and removing objects from Element Arrays, in the section on "Using Element Arrays" in the Scripting Bridge Programming Guide for Cocoa. To add an item, you use the aforementioned 'addObject' method on the instance of SBElementArray you've acquired from the bridge (in this case, the 'rules' element of Mail). Perfect.
To remove an item from the array, there's a nice symmetrical removeObject method, and this is discussed at the end of the 'Using Element Arrays' section. Perfect (I thought).

So, my rule deletion code was written to look up the element I needed to delete (by name), and then to use the removeObject on the rules element, with the returned object from the lookup. The code was written, time to test...

The code ran, everything appeared to work in the debugger (particularly getting the element array and the lookup of the named rule object). Great. So, I go to the Mail app to check the rules in the preferences panel... oh... the rule is still there.

Instant realisation sets in that I'm doing something wrong, and that the removal of objects is not symmetrical with their addition (despite the fact that I was thinking that something in Mail would be observing the rule element array).

I figure that I'm probably not driving the Mail app properly again, or possibly this is a asymmetric pattern implicit in AppleScript itself. Oh wait... [dim recollection of AppleScript occurs]... I think you have to 'delete' objects directly, rather than removing them from elements (at least in _most_ applications).

I decide to do something I should have done at the beginning of the 'erase rule' exercise: test some actual AppleScript in Script Editor. Sure enough, the following works:

tell application "Mail"
set foo to the last item of rules
delete foo
end tell

So, my problem is basically solved I can use the delete command on the rule I've lookup up from the rules element of Mail, expressed in the scripting bridge as:
[rule delete];

Within the 'box' of the scripting bridge and its own documentation, I was suckered into the sense that deletion from an element array could be accomplished by 'removeObject' (with the unlinked object eventually being garbage collected) - but of course things don't work like that in AppleScript in general, and it's almost certainly the case that SBElementArray you actually get to deal with has nothing really to do with the copy of the elements in the application - certainly in the case of objects being deleted from it. I suppose there must be a reason why removeObject is supported on SBElementArray (perhaps if you are able to build an array up before sending the whole thing over to the target app - you might want to add and delete objects), but I'm pretty sure that its usefulness is entirely local and not to be used to actually remove objects from elements in most applications - which is the impression that is easy to form if you simply read the scripting bridge documentation without considering how AppleScript normally works.

As usual though: all's well that ends well.


has said...

So, removing a rule from this collection should be a doddle, right?

As a wise engineer once put it: "The more they overthink the plumbing, the easier it is to stop up the drain." Scripting Bridge has a tendency for overthinking things, and the AppleScript world has a considerable talent for stopping up the resulting drain.

I suspect this particular problem is a simple matter of forgetting to map some NSMutableArray methods to nominally equivalent Apple events, in which case you might like to file a bug report/feature request on that. Alternatively, you could always try objc-appscript (see my sig) for a more straightforward approach to Apple event IPC.



Luke Evans said...

Hi has,

Yeah, I wondered whether it might still be a bug. Though, like most things in life, ones motivation to care drops off as soon as you find the workaround and get some kind of satisfaction :-)

I probably should file that bug though - if the intent of the design is that you should be able to remove/delete objects in this way.

Thanks for the pointers to your appscript stuff.

Luke Evans said...

Duty to King and Country done:

Bug ID# 5650272 filed.