Introducing Harmonize
August 3rd, 2006
Fair warning: this post is a combination of ranting, announcement, and self-promotion. Self-rannouncetion, if you will.
At work, we have the common problem of having a bunch of calendars and no way to effectively share them. We’re a mac shop so we all use iCal, and most of us publish our calendars, but that’s not quite good enough. iCal publishing (and really all .ics publishing) is a one-way affair. Others can’t edit your calendars, which is a handy feature to have around. One could use .Mac for this (sort of) but it costs money and is sort of an unknown quantity (to us).
So, it was decided that we would write our own. There are a few other applications out there that do calendar syncing, but we also need to synchronize contacts, and we need to also publish those data to web services. We weren’t able to find anything that did all of that. As an aside, If all you need are calendars, I would suggest having a peek at SyncBridge, developed by some smart dudes, and quite possibly the very first commercial RubyCocoa application. The public beta has been out for a few weeks, and I can tell you from experience that it works extremely well.
Starting with Tiger, OS X provides the Sync Services API. Sync Services is what makes iSync tick. All of the data-centric built-in apps use it: Address Book for contacts, iCal for events and todos, Safari for bookmarks, Mail.app for rules and accounts and so forth, Keychain for the stuff it stores… you get the idea. Sync Services is very cool. You can even register your own schemas and keep arbitrary data in sync between macs or applications. See? Very cool.
Anyway, for the past couple of weeks, I’ve been working on a little library that I’m calling Harmonize. This is the open source component of the application I mentioned earlier that we’re building. Harmonize wraps up the Sync Services API in an easy-to-digest, idiomatic Ruby interface. The folks behind SyncBridge were kind enough to release a small wrapper that brings the Sync Services API into the RubyCocoa namespace and allows you to access it. I’m using that and then wrapping it in a further layer of delicious Ruby goodness.
Why bother when there’s already a wrapper? Well, while RubyCocoa is a great project and immensely useful, it attempts to provide a fairly direct mapping of the Objective-C Cocoa interfaces, which in turn makes it fairly awkward for a rubyist.
An example is in order, I think. If you don’t know Obj-C syntax (like me), this is going to look really weird, but hopefully it’ll still make sense.
1 2 3 4 5 6 |
ISyncSession *session = [ISyncSession
beginSessionWithClient:client
entityNames:entityNames
beforeDate:[NSDate dateWithTimeIntervalSinceNow:5]
];
|
This calls ISyncSession’s class method beginSessionWithClient:entityNames:beforeDate:. In the above example, client (which is an ISyncClient object created elsewhere) is passed, along with values for entityNames and beforeDate. The value passed to the beforeDate parameter is the result of calling NSDate’s class method dateWithTimeIntervalSinceNow with the argument 5. The result of all that is stored in an ISyncSession object.
RubyCocoa provides two ways to translate that method call. The first is a direct translation of the method name (except s/:/_/g), with the arguments at the end:
1 2 3 4 5 6 |
session = ISyncSession.beingSessionWithClient_entityNames_beforeDate( client, entityNames, NSDate.dateWithTimeIntervalSinceNow(5) ) |
The second way is to split the Objective-C method name on the colons, and supply those sections of the method name inline, with the arguments. One of the pitfalls here is that order is significant; if you switch entityNames and beforeDate (and their respective values) in the following example, RubyCocoa won’t find the method you’re talking about:
1 2 3 4 5 6 |
session = ISyncSession.beginSessionWithClient( client, :entityNames, entityNames, :beforeDate, NSDate.dateWithTimeIntervalSinceNow(5) ) |
Depending on your background, you may prefer the look of one or the other. Since I’m a Rubyist by way of Perl and PHP, I prefer the second (for the same reason that it helps me to think of them as named params, even though they’re really not.)
The point of all that is that accessing the Cocoa API from Ruby with RubyCocoa is very powerful and very ugly. There’s also a fair amount of low-level stuff you need to do to pull off a sync. And thus we get back around to Harmonize.
My goal is to make the interface as clean and idiomatic as possible. To continue the above example, all of that code will be hidden away in the Harmonize library, allowing you do to something like this…
1 2 3 4 5 6 7 |
#!/usr/bin/env ruby require 'harmonize' include HarmonizeClient session = startSession |
... and you’ll have yourself a session. Harmonize will take care of the nitty-gritty of determining if the sync engine is up, fetching an instance of your client if it’s already registered, registering it if not, and similar housekeeping tasks. Of course the API might change; none of it has been written yet. I’m starting with writing a couple of small sync scripts and then extracting into the library. So far it’s working very well!
Anyhow, this is just a taste of what you’ll be able to do with Harmonize. If you’re interested, I submitted a proposal to give a talk on it at RubyConf in October, but it was rejected. If you’re so inclined, you can read it here. I do intend to have enough of it done that I can show it off in the hallway track at RubyConf, so stay tuned for more updates coming soon.
6 Responses to “Introducing Harmonize”
Sorry, comments are closed for this article.
August 3rd, 2006 at 10:40 PM
Looks promising. I’m just starting to get my feet wet with RubyCocoa, and this certainly looks helpful for some of the things I’d like to do.
August 4th, 2006 at 10:03 AM
Nice one Ben! Seriously cool fu and kicking about Mac OS X guts as well. Waving from the future…
August 7th, 2006 at 02:22 PM
Nice! We should chat about RubyCocoa next meeting as I use it a lot too.
October 25th, 2006 at 04:07 AM
I’m working on a side project involving Sync Services, and I’d love to harness Ruby to do it. Do you have a time frame for release your wrapper? Alternatively, I couldn’t track down the wrapper you said SyncBridge released. Could you point me to it?
Great stuff!
October 25th, 2006 at 04:51 AM
Zach – there’s a download page for the wrapper at http://www.infurious.com/downloads/
Ben, I’d love to see whatever you have done on this. I have a sync services project I’m working on and I’d much rather use RubyCocoa.
Keep us informed!
October 25th, 2006 at 06:41 AM
Zach, Brian:
I’ll have some news soon. I realized that I never mentioned the svn address, so I’ll be posting again with that (as well as slides from my RejectConf talk and some other things) soon.