Archive for Python

iCal interactions

Apple was nice enough to provide a feature rich interface for the AddressBook, and so I started my investigations of iCal with high hopes. Alas, these were to be dashed, and in rather short order.

XCode documentation wasn’t enlightening, Apple’s web site was notably quiet … something was amiss.

This article by Rod Schmidt over at O’Reilly started to shine some light on matters. To quote some key pieces of the puzzle:

Apple doesn’t provide any APIs to read iCal’s data, but you can do it yourself.

And:

All the data for iCal is stored in .ics files in ~/Library/Calendars. There is one .ics file for each calendar you have created.

Ah ha! A starting point. Now to be honest I don’t have much in my iCal calendars, as I tend to use Exchange for calendaring, so I put a few things in and then went hunting. To my confusion, no such folder. It had to be somewhere.

According to a range of posts, Apple was definitely storing iCal information in a standard format, and with Spotlight now requiring everything to be files, it had to be somewhere.


% cd ~/Library
% find . -name '*.ics'
./Application Support/iCal/Sources/711885EF-B88B-44AE-B63E-A1409474922D.calendar/corestorage.ics
./Application Support/iCal/Sources/C28120BF-AE28-49AE-8869-E6616428A12A.calendar/corestorage.ics
./Caches/com.apple.iCal/inbox.calendar/corestorage.ics

This didn’t look as promising, but at least the .ics files have been located. Ignoring the cache directory, the other directory names didn’t look particularly nice, but included in each of them is a trusty Info.plist file that includes some useful properties.

  • Key - this appears to be the name of the directory and a unique key identifying the calendar.
  • Title - Only appears for normal calendars (see below).
  • Type - Two types that I’ve seen so far are com.apple.ical.sources.naivereadwrite (normal calendars) and com.apple.ical.sources.birthdays (birthday calendar).

At this point, I think it is safe to assume that the underlying structures changed somewhat in Tiger (or earlier) from the simplistic store mentioned in the O’Reilly article. Nothing too tricky though, so it should be possible to check for either type of directory store and locate the appropriate files.

If anyone has further information on iCal file storage, either in terms of links or corrections to anything I’ve mentioned, please post to the comments.

The next step would be to be able to manipulate these files. If I can avoid it, I’d rather not write a RFC2445 parser. Fortunately, several people have beaten me to it.

iCal.py provides a simple interface into the calendar file, with a simple class that wraps the file. The directory and file loading needs a bit of messing around with for Tiger.

I was looking for a bit more and found it in iCalendar by Max M. The library is LGPL’d which doesn’t cause me issues, but may have implications if you are wanting it for commercial software. Installation was typical Python style, pain free.

After installation and changing to one of the directories which contain my .ics files, the following allowed me to read the calendar (.ics) file:


>>> from icalendar import *
>>> cal = Calendar.from_string(open('corestorage.ics','rb').read())
>>> cal
VCALENDAR({'CALSCALE': 'GREGORIAN', 'VERSION': '2.0', 'PRODID': '-//Apple Computer\, Inc//iCal 2.0//EN'})
>>> for component in cal.walk():
...     component.name
...
'VCALENDAR'
'VEVENT'

The API provides a range of useful documentation and examples, with unit tests for all the features. These in themselves also provide useful code snippets for deriving usage. The API also supports creating and modifying .ics files.

My aim in all this is to be able to update the underlying data for iCal. Brief tests show that changing the .ics files while iCal is running doesn’t quite lead to user interface updates. On the bright side, the Index files seem to be re-generated on closing and opening iCal.

Further investigation of this should hopefully lead to some way of coaxing iCal into playing nicely. At the moment I suspect it may involve a bit of Applescript to drive the application into a refresh if required.

(Update: refer to comments for the insight I was missing. On Tiger, Sync Services does exactly what I’m wanting in terms of keeping a calendar in sync with my app. For earlier versions, I should be able to live with exporting a calendar file.)

PyObjC article available over at ADC

Apple has recently posted an introduction to PyObjC over at ADC. The article is courtesy of the Red Shed and even includes movies of the Interface Builder bits.

This is a sensational result for the PyObjC team for several reasons. First up, there are likely to be more developers writing PyObjC applications, which will result in an even larger community. Secondly, and perhaps more importantly, the article provides an official looking stamp for the community

My congratulations to all involved, both in terms of making PyObjC a reality and in terms of producing a Ruby on Rails style piece of marketing!

Opening an AddressBook entry with PyObjC

Having done some user testing on my DateList application, I discovered that they (me and my other test subject) expected something to happen when double clicking on an entry. It seems reasonable to me to open the person in Apple’s AddressBook as the action.

There doesn’t seem to be much information on this, however playing around with the birthday calendar (Tiger iCal feature), there is a URL that seems to work:


addressbook://E15E4D1E-AAE1-4AC2-85DB-98BB906EE988:ABPerson

Passing this to the open shell command opens the appropriate person in AddressBook.

The better question is how to construct one of these. To query an AddressBook entry, we first need to find one. AddressBook has a special reference for the current user called me.


>>> from AddressBook import *
>>> book = ABAddressBook.sharedAddressBook()
>>> book
<ABAddressBook: 0x1133d80>
>>> me = book.me()
>>> type(me)
<objective-c class ABPerson at 0xa4ac3034>

Now that we have a ABPerson, we can determine the UID. The UID property is actually on the base class, ABRecord.


>>> myUID = me.valueForProperty_(kABUIDProperty)
>>> myUID
u'E15E4D1E-AAE1-4AC2-85DB-98BB906EE988:ABPerson'

To open a URL, we need to construct a NSURL:


>>> from Foundation import *
>>> url = NSURL.URLWithString_('addressbook://' + myUID)
>>> url
addressbook://E15E4D1E-AAE1-4AC2-85DB-98BB906EE988:ABPerson

Finally, we pass the NSURL to the openURL_ method of the shared workspace. You get one shared workspace per application, and it can be used for useful things like opening urls, files, applications and some other services such as tracking changes.


>>> from AppKit import *
>>> ws = NSWorkspace.sharedWorkspace()
>>> ws
<NSWorkspace: 0x1134910>
>>> ws.openURL_(url)
1
>>>

openURL_ returns YES (1) if the URL was successfully opened, NO (0) otherwise. At this point AddressBook should open and have selected the entry that you have tagged as you.

Hacking the iTunes library with Python

Bob was having a bit of trouble with his Airport Express. Fortunately for the rest of us, his solution involved delving into the internals of the iTunes library for answers.

Interesting for the simplicity with which PyObjC allows Python idioms to mesh with Apple’s APIs for a neat solution. With a few lines in an interactive shell, Bob manages to locate all the problematic files and set the correct creator code

Ten Essential Development Practices

In this article Damian Conway outlines ten development practices that he considers essential for developing big, business critical systems. Also, incredibly useful for developing big, user critical Cocoa programs.

The examples are in Perl, but the development practices are basic good practice and can be applied to any language.

A short summary:

  1. Design the Module’s Interface First
  2. Write the Test Cases Before the Code
  3. Create Standard Documentation Templates for Modules and Applications
  4. Use a Revision Control System
  5. Create Consistent (Command-Line / User) Interfaces
  6. Agree Upon a Coherent Layout Style and Automate It (with perltidy)
  7. Code in Commented Paragraphs
  8. Throw Exceptions Instead of Returning Special Values or Setting Flags
  9. Add New Test Cases Before you Start Debugging
  10. Don’t Optimize Code—Benchmark It