Archive for August, 2005

Thinking of Music

A few interesting, music related things I’ve been playing with recently that I thought I’d share.

  1. Pandora - Given a song, an artist, the site creates a stream of music that sounds like what you gave them. You can then adjust what each station delivers to you, based on you saying you like or dislike a particular track.

    Currently in invitation only preview mode, send them an email and you might be lucky. It is a great way to discover new music. Downside is that it is based on a very good AJAX style web interface, which means getting it to play through my Airport Express isn’t fun. But then it is a preview…

  2. Cover Flow - Still in tech demo phase, but showing ridiculous amounts of promise, a visual browser for albums. Integrated into iTunes and a host of web sites so most of the cover art turns up, with drag and drop for those that don’t.

    Gives back the feel of flicking through your albums, which is something I missed when I was at a loss as to what I feel like listening to.

    Only for Mac OS X 10.4 at the moment

  3. Delicious Library - Somewhat over-hyped recently, this is still a really good start at a product. Provides a digital track record of your physical assets (CDs, Books, Movies, Games) and the ability to remember who has borrowed what.

    I’ll be more sold on this when I can see what is in the library of my friends. It is more interesting to know what I can borrow from others as I already know what I have (or had, given how good I am at tracking borrowers). To that end, the export functionality is somewhat lacking.

  4. MPC (& MpcOSX) - Music Player Daemon. Server side music player with a host of clients. Incredibly useful if you want to set up a server somewhere with all of your music that can be controlled from just about any type of client.

    The existing clients are still a bit alpha in terms of feel, but the idea is solid.

    See linux.ars for a bigger write up (towards end of post).

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!

Adding Filtering to list by subclassing an NSArrayController

In the previous series of articles, we’ve covered getting started, basic Address Book usage, and making an app bundle. This time, we’ll look at adding in some basic filtering to the list of dates.

To add filtering, we need to extend the NSArrayController class. The first step is to create a new python class, ArrayControllerWithSearch. This will extend the standard array controller with a search method.


from PyObjCTools import NibClassBuilder

class ArrayControllerWithSearch(NibClassBuilder.AutoBaseClass):
    searchString = None

    def search_(self, sender):
        self.searchString = sender.stringValue()

This doesn’t do anything particularly interesting, except to store the search string for the other methods to use. The AutoBaseClass means that the class we are going to derive from will be specified in Interface Builder and then allocated dynamically.

The next step is to over-ride the method that actually lists the objects in the array, arrangeObjects_. (recalling that the underscore is required for when Objective C wants a ‘:’).


def arrangeObjects_(self, objects):
    # retrieve a reference to the same method in the base class
    supermethod = super(ArrayControllerWithSearch, 
        self).arrangeObjects_

    if self.searchString is None or self.searchString == '':
        return supermethod(objects)

    return supermethod(list(filterObjects(objects, self.searchString)))

The supermethod variable is actually a function pointer to the arrangeObjects_ method on the super class. This gets used under either case, so it is easier to have the code to access this in one place.

filterObjects will be defined next. The search is going to be rather simplistic in that it will check if the search string is contained within the name of the person.


def filterObjects(objs, searchString):
    lowerSearch = searchString.lower()
    for obj in objs:
        if(lowerSearch in obj['name'].lower()):
            yield obj
            continue

Also, add an import statement for the class in the main application (main.py):


from PyObjCTools import AppHelper

import DateListDelegate
import ArrayControllerWithSearch

AppHelper.runEventLoop(argv=[])

In Interface Builder, subclass NSArrayController in the Classes tab, and call the sub class the same name as your custom controller (ArrayControllerWithSearch). In the Attributes section of the Inspector (with the Classes tab still selected), add a new Action, called search:

From the Instances tab, select the NSArrayController for the list of names. In the Inspector, select Custom Class, and then select the new controller class:

ArrayControllerWithSearch subclass

Add a search control to the form. This will call the search_ method added to the controller.

Search control

Now, Ctrl-drag from the search control to the array controller in the Instances tab. This creates a connection. Select the search: action and then press the Connect button.

Congratulations! You now have a searchable list of birthdays for all your friends and family, integrated into your Address Book.

The complete example is available here and includes a working application bundle in dist/.