One of the cool features of Leopard was the addition of CalDAV support to iCal, which allows you to read/write/edit/delete calendars hosted by third parties like Google Calendar. Similar functionality was available on the iPhone, but only via Apple’s MobileMe service. That’s changed with iPhone OS 3.0.

If you’ve configured a bunch of CalDAV calendars in iCal, unfortunately they’re synced to the iPhone as read-only calendars which is a real pain. In lieu of a fix by Apple, we’ll need to remove the iCal CalDAV accounts from the iTunes calendar sync list, and configure them on the iPhone itself.

To set up a new CalDAV account, you’ll need to navigate through the following screens on your iPhone:

Settings → Mail, Contacts, Calendars → Add Account… → Other → Add CalDAV Account…

Once you’re there, you’ll be presented with a dialogue that lets you enter a CalDAV server, username, password and description. This dialogue is incredibly misleading, because the “server” field will not only accept a hostname like “www.google.com”, but also a full CalDAV URL like “https://www.google.com/calendar/dav/<CALENDAR_ID>/user”. If you’re using Google Calendar with only one calendar, enter your Google Calendar credentials like the following example:

If you’ve got multiple CalDAV calendars, it starts to get a little bit hairier. Initially, I emailed myself a list of my CalDAV URLs (4 in total) and used the new copy-paste features to manually configure each account using the previous instructions, only instead of using “www.google.com” in the server field, I pasted the entire CalDAV URL instead. As you can imagine, this is incredibly tedious.

Fortunately Apple also released a new version of the iPhone Configuration Utility (ICU), which is a really handy tool for creating iPhone configuration bundles for deployment in enterprise environments. Among other things, this new version allows you to configure CalDAV accounts. If you haven’t already, you should grab a copy of ICU for OS X or Windows.

Launch ICU, create a new configuration profile, and open the CalDAV pane. For each CalDAV calendar you want to configure, you’re going to want to fill it in like this:

If you’re looking at the Principal URL field and wondering what ”<CAL_ID>” is, you’ll find it in the calendar settings page of Google Calendar under Calendar Address (it should look like an email address).

Once you’ve entered all your CalDAV accounts, plug in your iPhone, select it in ICU and open the Configuration Profiles tab. You should see the configuration profile you just created, with an “Install” button next to it. Hit the “Install” button.

You iPhone should show a screen like this:

If you hit “More Details”, you’ll see the CalDAV calendars listed:

Hit “Install”, confirm, and open up the Calendar App to see if everything works as expected. You should be able to create, edit and delete calendar entries in your CalDAV accounts, and have them update in iCal / Google Calendar. If you ever want to add a new CalDAV calendar, I think you need to remove the configuration via the iPhone Configuration Utility and re-install the edited configuration bundle.

Hopefully it doesn’t take too long for Apple to make the CalDAV integration between iCal and the iPhone more seamless.

UPDATE: Due to changes in the public release of iTunes 8.2, you will now need to quit iTunes and run a command in your Terminal before proceeding with the instructions below. The command is: “defaults write com.apple.iTunes carrier-testing -bool TRUE”.

UPDATE: I’ve downloaded the Telstra and Vodafone bundles and made similar modifications, so they’re now available in the guide. If you’re using Telstra/Vodafone and my modified settings bundles work, please let me know in the comments as I’m unable to test it myself.

The following instructions are intended for members of the Apple Developer Connection (ADC), running an iPhone with a developer version of the iPhone OS 3.0 software. This guide also only provides Australian carrier settings for Telstra, Vodafone and Optus (plus resellers like Virgin), but if you follow the link to Crunchgear in the first step there are instructions on downloading and modifying the carrier setting bundles.

  1. Download Telstra_au.ipcc, Vodafone_au.ipcc or Optus_au.ipcc, which were created as per the AT&T instructions on Crunchgear (note that the file should have an ”.ipcc” extension, not ”.zip”. If your downloaded file has a ”.zip” extension, unzip it to produce a “Carrier_au.ipcc” file)
  2. Open iTunes
  3. Plug in your iPhone
  4. Select you iPhone under devices
  5. In the Summary tab, ⌥-click (option-click) the “Check for Update” button
  6. Select the previously downloaded Telstra_au.ipcc / Vodafone_au.ipcc / Optus_au.ipcc file and click “Open”
  7. Once the settings have been copied, disconnect your iPhone
  8. Restart your iPhone
  9. Open the Settings application on your iPhone, and navigate to General, then Network, and finally Internet Tethering
  10. Turn Internet Tethering on. At this point, tethering is now possible via USB when your iPhone is plugged in. If you’d like to use Bluetooth, continue reading
  11. You will be prompted to turn bluetooth on if it’s not on already. Select yes if prompted
  12. On your laptop, turn on bluetooth and open the Bluetooth Preference pane
  13. Click “Set Up New Device…”, which will open the Bluetooth Setup Assistant
  14. Select “Mobile Phone” as the Device Type
  15. Select your iPhone from the device list
  16. A number should appear on your laptop, and your iPhone should prompt you for a pin
  17. Enter the pin from your laptop into your iPhone
  18. Once paired, make sure you keep “Use device as network port” selected
  19. In the Bluetooth Preference Pane, make sure “Show Bluetooth status in the menu bar” is selected, since it makes things easier for starting and stopping tethering
  20. Click the Bluetooth icon in your status bar, where you should now see your iPhone under devices
  21. Click on the menu item for your device, and select “Connect to Network”
  22. On your iPhone, you should now notice that the top of the screen has a blue bar titled “Internet Tethering”
  23. You can now use the Internet!
  24. As an optional extra, you can open Network Preferences and rename the two new network devices to “iPhone USB” and “iPhone Bluetooth”

Hope that helps someone! Not really a difficult process, but it can be a bit fiddly. Downloading my Optus_au.ipcc file also saves mucking around in Property List Editor too, which most people will attest is a bit of a pain.

I wrote an article last year about the lack of Apache support for HTTP requests with the “Transfer-Encoding: chunked”. Of course, this problem really only affects those of us who use Apache modules that were developed against the Apache 1.3 API (pre-HTTP 1.1). One such module was Phusion Passenger – also known as “mod_rails” – which I was using at the time to provide a web service to J2ME MIDlets and an iPhone application (among other things). To get around the lack of support, I presented a solution involving the use of mod_proxy to buffer the chunked request and rewrite it as a new request before passing it to the non-HTTP 1.1 compliant backend.

Thankfully as of two days ago, that hack is no longer required if you’re using Passenger.

Hongli Lai (the lead developer of Phusion Passenger) has checked in a change that enables support for chunked transfer-encoding, which means no more dreaded “HTTP/1.1 411 Length Required” responses. So the fix is checked into the tree, and according to the bug report will be made available in the next release of the Passenger gem – version 2.1.4.

A few months ago, I wrote an article (“Transfer-Encoding: chunked”, or, Chunky HTTP!) describing a solution for accepting chunked HTTP requests made by J2ME devices to non-HTTP 1.1 compliant servers unable to accept such requests (Mongrel, Lighttpd, Nginx etc.).

One of the commenters was concerned that J2ME developers suffering from this problem might find it difficult to find my article since I didn’t really mention J2ME MIDlets much at all. So, if you’re a J2ME developer suffering from problems like those described here, here or here...check out the original article for a simple solution.

A comment was made in my previous article, Memory issues with NSMutableURLRequest’s setHTTPBody: method in iPhoneOS 2.1, stating that the “c” of “chunked” in the “Transfer-Encoding: chunked” header is similarly capitalised in OS 10.5.5, which means that the flaw in Apache’s mod_proxy is exposed by more than just the iPhone.

So if you’re trying to do chunked HTTP POST requests using the CFNetwork stack, make sure your Apache configuration can deal with the chunked header:

RequestHeader edit Transfer-Encoding Chunked chunked early

The iPhone developers at my work have been tearing their hair out over the last few days, trying to resolve the last few memory issues in our iPhone application before we send it off for approval by Apple. One of the problem areas they’ve noticed is when a photo is uploaded to our service via HTTP POST (both from the camera and otherwise).

Not sure who had the bright idea, but one of the developers decided to try passing in the HTTP body as an NSInputStream (via NSMutableURLRequest’s setHTTPBodyStream:), rather than as NSMutableData (via NSMutableURLRequest’s setHTTPBody:). Magically, this seems to have solved the memory leak issues.

Unfortunately, it created another issue.

You might remember that 3 months ago I wrote an article on how much of a pain in the arse it was to accept HTTP POST requests from user agents specifying a Transfer-Encoding HTTP header value of chunked (resulting in a POST request with no content length). In that article, I proposed a solution using Apache 2 and mod_proxy’s proxy-sendcl option to get things working again. This worked fine for our J2ME clients, but when our iPhone application started blowing chunks at us, our server crapped out with the dreaded 500 error I thought I’d fixed for good:

Chunked Transfer-Encoding is not supported

After whipping out Wireshark, we realised that there was a tiny difference between between what our J2ME client was doing and what the iPhone was doing.

This is the header the J2ME app was sending:

Transfer-Encoding: chunked

And this is the header the iPhone was sending:

Transfer-Encoding: Chunked

As much as I’d like to think the different casing of chunked and Chunked wouldn’t affect the behaviour of mod_proxy, it seems it does. Fortunately, we can work around this problem too by using Apache’s mod_headers module. This allows us to do the following:

RequestHeader edit Transfer-Encoding Chunked chunked early

When combined with the solution from my previous article, this leaves us with the following complete solution:

ProxyRequests Off

<Proxy http://localhost:81>
  Order deny,allow
  Allow from all
</Proxy>

Listen 80

<VirtualHost *:80>

  RequestHeader edit Transfer-Encoding Chunked chunked early

  SetEnv proxy-sendcl 1
  ProxyPass / http://localhost:81/
  ProxyPassReverse / http://localhost:81/
  ProxyPreserveHost On
  ProxyVia Full

  <Directory proxy:*>
    Order deny,allow
    Allow from all
  </Directory>

</VirtualHost>

Listen 81

<VirtualHost *:81>
  ServerName ooboontoo
  DocumentRoot /path/to/my/rails/root/public
  RailsEnv development
</VirtualHost>

Hopefully this saves someone a little bit of time :).

Providing a web service for a bunch of browsers is a relatively straightforward affair. It’s really only once you jump out of the back-end and into the front-end side of things where issues like browser incompatibilities start to become a problem. Thankfully, I feel like I’m in the position where I think I’ve got my head wrapped around what’s involved in providing solutions to these problems.

And then mobile phones came along.

The service I’m working on at the moment is consumed by a bunch of clients, including but not limited to web browsers, WAP browsers, the Flash player, iPhones, J2ME devices. It’s the last one that’s causing headaches at the moment.

You see, despite the fact that HTTP/1.1 is about 9 years old, not all web servers support the features that were introduced. The particular one I’m talking about is chunked tranfer encoding, but I’m sure there are many others.

To give you a general idea, the HTTP implementations on many mobile handsets will decide to use a chunked transfer encoding if the payload of a PUT/POST request is over an arbitrary threshold. This causes issues with servers like Nginx, Lighttpd, Mongrel, and Thin, since most of those assume that an incoming HTTP request with a payload will also include a Content-Length header.

Well guess what? As of 9 years ago, that hasn’t been the case.

Take a look at this request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /search.json HTTP/1.1
User-Agent: curl/7.16.4 (i486-pc-linux-gnu) libcurl/7.16.4 OpenSSL/0.9.8e zlib/1.2.3.3 libidn/1.0
Host: ooboontoo
Accept: */*
Transfer-Encoding: chunked
Content-Type: multipart/form-data; boundary=----------------------------ab5090ac7869


92
------------------------------ab5090ac7869
Content-Disposition: form-data; name="query"

zoooom
------------------------------ab5090ac7869--


0

Notice anything? If not, try and find a Content-Length header. Pre-HTTP/1.1 you’d expect to get an HTTP 411 error (length required), but after HTTP/1.1 it’s pretty clear what the HTTP/1.1 applications are obliged to do:

All HTTP/1.1 applications that receive entities MUST accept the “chunked” transfer-coding (section 3.6), thus allowing this mechanism to be used for messages when the message length cannot be determined in advance.

That’s why I find it so surprising that hacks are involved in allowing mobile clients to POST/PUT to what I’d traditionally thought of as HTTP/1.1 compliant web servers.

But anyway, you want to see the solution right?

Well, our initial solution involved Gerald writing a little web server in Python which went by the name of “Dechunker”. You can imagine what it did, but we quickly found that while it was the simplest way to avoid the problem, it also meant that over time we would end up needing to implement the functionality that was already available in most other web servers. Servers like Apache2 and Lighttpd have become incredibly hardy over the years, and we’re not going to achieve that overnight.

So I then took another look at Apache2, knowing that some modules do support chunked transfer encoding while others don’t. What I discovered was that Apache’s mod_proxy module could be used in front of anything that doesn’t support chunked encoding, since it can be configured to “dechunk” requests before passing them to a backend.

It looks a little something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ProxyRequests Off

<Proxy http://localhost:81>
  Order deny,allow
  Allow from all
</Proxy>

<VirtualHost *:80>
  SetEnv proxy-sendcl 1
  ProxyPass / http://localhost:81/
  ProxyPassReverse / http://localhost:81/
  ProxyPreserveHost On
  ProxyVia Full

  <Directory proxy:*>
    Order deny,allow
    Allow from all
  </Directory>

</VirtualHost>

Listen 81

<VirtualHost *:81>
  ServerName ooboontoo
  DocumentRoot /path/to/my/rails/root/public
  RailsEnv development
</VirtualHost>

As you can see I’ve got Apache2 listening on port 80, which uses the “proxy-sendcl” environment variable available in mod_proxy to repack the HTTP body and add a Content-Length header to the request. This request is then passed back to a virtual host running on port 81, which is configured to use Phusion Passenger.

Turns out it’s pretty simple, and from what I’ve seen there haven’t been any negative performance impacts by proxying all requests. It’s not a permanent solution, and as soon as Phusion Passenger fixes the chunked encoding bug, I’ll drop mod_proxy from our configuration.

Hope that helps someone!

I’ve been working on a JSON API for mobile clients recently, and in doing so I’ve realised how much you need to repeat serialization options throughout Rails applications despite options generally being model specific.

This little patch solves that problem by allowing you to decorate your Rails models with model-wide serialization options, like so:

1
2
3
4
class Article < ActiveRecord::Base
  has_many :comments
  serialization_options :include => :comments
end

This means that whenever you call to_json or to_xml on an instance of Article, you’ll get the comment association thrown in for you. You’ll find you can clean up your Controllers and remove explicit calling of to_json, which previously would have looked like this:

1
2
3
respond_to do |format|
  format.json :json => @article.to_json :include => :comments
end

But can now be change to this:

1
2
3
respond_to do |format|
  format.json :json => @article
end

While it’s very simple, some people might find it useful. If you do, chuck this in your /lib directory and require it in environment.rb.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module SerializationOptions
  def serialization_options(options = {})
    class_inheritable_accessor :serialization_options
    self.serialization_options = options.dup
  end
end

ActiveRecord::Base.send(:extend, SerializationOptions)

class ActiveRecord::Serialization::Serializer
  alias_method :old_initialize, :initialize
  def initialize(record, options = {})
    if record.respond_to? :serialization_options
      options = record.serialization_options.merge(options)
    end
    old_initialize(record, options)
  end
end

Opening session at Web Directions today was given to Andy Clarke, who proceeded to wrap it up with an announcement of a group put together to tackle the recent issues regarding submission of proposals and recommendations to the W3C. The eleven involved are:

  • Cameron Adams
  • Jina Bolton
  • Mark Boulton
  • Dan Cederholm
  • Andy Clarke
  • Jeff Croft
  • Aaron Gustafson
  • Jon Hicks
  • Roger Johansson
  • Richard Rutter
  • Jonathon Snook

From what was explained, their aim is to work through the CSS specifications and give feedback and examples for some of the more difficult issues, and then provide a body of work to the W3C and/or browser vendors with the hope that it things along a little faster than is currently the case. More details are sure to appear on the CSS Eleven website.

Here’s a snap of his slide:

Despite the speculative bullshit that always seems to spout from Walt Mossberg and Robert Scoble’s mouths, we still don’t really know what’s driving Apple’s decision to not support Flash content on the iPhone. They’ve also removed the Flash content from their website, which leads me to believe that this issue runs far deeper than I previously thought.

Adobe’s recent announcement of H.264/AAC support (among other things) in their Flash Player 9 product, has overnight turned Adobe’s Flash Player and Apple’s Quicktime Player into competing products. If you read between the lines, however, you might see something interesting:

“Adobe has licensed the x86, PowerPC and ARM versions of MainConcept’s H.264 and AAC decoders”

Keyword, “ARM”. Previously, Adobe have not had a Flash Player 9 SDK for the ARM architecture. For this reason, products like the Opera Browser on the Nintendo Wii have needed to settle for Flash Player 7, or in the case of Apple’s iPhone they’ve decided not to settle at all and instead ignored support for Flash altogether. Now, it seems that Adobe might be planning on releasing an SDK for the ARM architecture since the H.264/AAC support only affects the version 9 product.

Once Adobe releases an SDK, Apple will have the means to support the plugin on the iPhone. As an added benefit, they won’t need to worry about licencing On2’s VP6 codec since the H.264 videos which currently play on the iPhone, iPod and iTV will also play within the Flash Player too. Not only that, but the annoucement from Adobe also mentions support for reading iTunes metadata (“list” atom) embedded in audio and video files.

Adobe are playing straight into Apple’s hands!

So what’s Apple going to do about it? Should they embrace these changes and welcome Adobe with open arms? Keep in mind that according to the Adobe FAQ, “new releases of Flash Player take approximately 12 months to reach 90% penetration”. We’ll see this Flash Player in the wild as a release version sometime in September, so I expect Apple to have at least polarised by then.

I honestly hope that they chose to support the Flash Player. By doing so, they will put the power back in the hands of the content providors. While this doesn’t mean I expect them to re-instate Flash content on their own website, at least we’ll be left with a choice in the matter.

This is in response to Greg Borenstein’s article titled A Beginner’s Guide to Practical Syntactic Magic: the tale of Hpricot’s sudo-constructor and Stuart Halloway’s follow-up article, With great power comes great responsibility.

Both articles make reference to the odd method-naming tricks used in Why The Lucky Stiff’s Hpricot library for parsing XML (commonly HTML). In light of those tricks, I have a question for you all…

Which of the following two snippets of code would you:

  • Prefer to read
  • Be inclined to write
This:
1
2
3
doc = Hpricot.parse(open('http://www.atnan.com'))
title = doc.at('title')
articles = doc.search('.entry')
Or this:
1
2
3
doc = Hpricot(open('http://www.atnan.com'))
title = doc % 'title'
articles = doc/'.entry'
I’m interested in hearing your feedback in the comments below :)

One of the things I found difficult to understand when it comes to writing a Facebook application is how to add a “dynamic” panel to a users’ profile page, and how to auto-add entries to their mini-feed. A couple of examples include:

  • Putting “Nathan has added 5 photos to Flickr” or “Nathan is listening to Computer Camp Love by Datarock” on my mini-feed
  • Adding a dynamic panel to my profile which shows the last 10 patches I have submitted to Ruby on Rails

It turns out this is a fairly trivial exercise with Matt Pizzimenti’s RFacebook gem. The following is a quick run-down of what’s required for adding content to the profiles of users who have installed your (Rails based) Facebook application via a push strategy. I’ve done a gem unpack of hpricot-0.5, json-1.1.0 and rfacebook-0.6.2 to RAILS_ROOT/vendor/plugins.

The “Infinite Session”

In order for you to be able to add content independently of a Facebook user being logged in, you need to have a way of authenticating yourself. When a user is browsing your application through the Facebook interface, the RFacebook library gives you access to the fbsession variable which represents the user who is currently logged into Facebook. The problem is, this session will expire. You can find out when a session will expire by checking the value of fbsession.session_expires.

But surely we don’t want to force a user to log in to Facebook every time we want to update their profile with notifications / new content from our third-party application? So how do we get a session that doesn’t expire? The trick is providing a link to the following URL:

http://www.facebook.com/code_gen.php?v=1.0&api_key=YOUR_API_KEY

Once the user clicks the “Generate” button on that page, every time you call auth.getSession, you’ll get an identical session ID which has a fbsession.session_expires value of 0. While the user is interacting with your Facebook application, you should serialise this to some kind of storage such as the ActiveRecord model of your user so you can use it later:

1
2
3
4
5
6
7
8
9
10
11
class FacebookUser < ActiveRecord::Migration
  def self.up
    create_table :facebook_users do |t|
      t.column :user_id, :string, :null => false
      t.column :infinite_session, :null => false
    end
  end
  def self.down
    drop_table :facebook_users
  end
end

And the sample controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
require 'facebook_rails_controller_extensions'
class FacebookController < ApplicationController
  include RFacebook::RailsControllerExtensions
  
  FB_APP_URL = 'http://apps.facebook.com/my-app-name'
  
  before_filter :require_facebook_login
  before_filter :require_configuration, :except => :configure
  
  def facebook_api_key() 'YOUR_API_KEY'; end
  def facebook_api_secret() 'YOUR_API_SECRET'; end
  def finish_facebook_login() redirect_to FB_APP_URL; end
  
  def require_configuration
    @user = FacebookUser.find_by_user_id(fbsession.session_user_id)
    redirect_to :action => :configure unless @user
  end
  
  def configure
    @user = FacebookUser.find_or_initialize_by_user_id(fbsession.session_user_id)
    unless fbsession.session_expires
      @user.infinite_session = fbsession.session_key
      @user.save!
    end
  end
end

This means we can now run a little script (via cron, if you wish):

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env /path/to/application/script/runner

require 'facebook_rails_controller_extensions'

session = RFacebook::FacebookWebSession.new('YOUR_API_KEY', 'YOUR_API_SECRET')
FacebookUser.find(:all).each do |user|
  session.activate_with_previous_session(user.infinite_session, user.user_id)
  session.profile_setFBML(:uid => user.user_id, :markup => 'My really dynamic content')
  session.feed_publishActionOfUser(:title => 'added some really dynamic content')
end

You could run this script every 5 minutes, or perhaps even directly call session.feed_publishActionOfUser and session.profile_setFBML from your application when the user does something (such as creating a resource).

If you’ve got any examples of where you’ve used this technique, or have any suggestions as to how it could be done better, I’d love to see them. Please comment below :).

Can AS3 do REST, or not?

June 12th, 2007

I just finished reading Thijs van der Vossen’s article, Flex can’t do REST, and I figured I’d weigh in a with a few of my experiences given that I’ve been doing quite a bit of playing with Rails, Actionscript 3, the Flex framework and Air (previously Apollo).

First and foremost, Thijs is correct and Harris Reynolds must either have a hidden agenda or is kidding himself (perhaps both?).

The issue they’re both talking about lies outside the realm of Flex classes, and right down at the Flash Player level. Brian Riggs’ article that was linked to by Thijs, Making HTTP calls in ActionScript 3, refers to the problematic class as being “AS3’s HTTPService class”, which might have throw people onto the wrong trail. The issue is with flash.net.URLLoader (Flash), not mx.HTTPService (Flex).

So what is this “issue”? It’s all in the response headers, actually. While you can get access to the response codes by listening to the HttpStatusEvent.HTTP_STATUS event when using URLLoader, it’s not going to help you with getting access to response headers. I’d put money on the fact that Adobe is unwilling to provide access to the headers because the functionality is not available in all browsers. Their opinion – as a guess – is that the Flash plugin should operate in an identical fashion across all browsers.

Sucks to be us.

The “proof” of all this can be seen when you take a look at the Air API docs for the HTTPStatusEvent class. There’s a responseHeaders property which spits out an array of URLRequestHeader objects…exactly what we want.

Perhaps someone from Adobe could give us a run-down of whether this the product of internal policies regarding Flash plugin behaviour across browsers?

UPDATE: I've removed the link to the now defunct Tilefile videos, as well as the links to the raw H.264 MPEG4s. The videos are now available on Vimeo, and embedded in the article.

I love time lapse photography, so the minute I saw Google Street View my first thought was that now I could create timelapse style videos of places I've never been!

Behold, the Golden Gate Bridge, seen through the eyes of the Google Street View car:


Golden Gate Bridge via Google Street View from Nathan de Vries on Vimeo.

Oh, and here's a nice panoramic video of Las Vegas Boulevard that I prepared earlier:


Las Vegas Boulevard via Google Street View from Nathan de Vries on Vimeo.

Code will come later, but to give you a brief idea of how this is done - I've written a crawler in Ruby which builds a matrix of panorama nodes. I then traverse the graph to find paths that can potentially produce nice videos, stitch the multi-image frames together with ImageMagick's montage application, and then create a MJPEG stream by concatenating each individual frame into a single file. This then gets encoded as an H.264/MPEG-4 video using FFMPEG.

I'll probably get more time to play on the weekend and release the source then when it's in a more usable state.

Shows how much I follow the progress of RoR SVNJamis Buck checked in changeset 6487 2 months ago, which as far as I can tell is derived from my technique of checking dependencies as described in Using Capistrano to check for deployment dependancies.

Nice work Jamis, looks good!

Given that we’re using my original technique at my work, we haven’t had the opportunity to switch over to Capistrano 2 yet…but I’ll get there. I think I’ll need to submit a few patches, though, given that there are some things that aren’t supported yet such as executing a command and checking the results to see if it matches a regular expression. Something like this:

1
2
3
4
5
mysql_setting_max_connections:
  - 2000+
  - echo "SHOW VARIABLES LIKE '%max_connections%'" | mysql
  - !ruby/regexp /max_connections\s*[2-9]000/
  - !ruby/regexp /\d+/

You’ll notice that this is YAML (YAML Ain’t Markup Language). I’m storing dependencies in a file called dependencies.yaml, which gets auto-loaded when I load my DependencyChecker module/plugin. I could also do this:

1
2
3
4
5
python_pil:
  - 1.1.x
  - python -c "import Image; print Image.VERSION"
  - !ruby/regexp /1\.1\.\d/
  - !ruby/regexp /\d\.\d\.\d/

Or perhaps:

1
2
3
4
5
nptl:
  - present
  - /lib/libc.so.6
  - !ruby/regexp /Native POSIX Threads Library/
  - !ruby/regexp /Native POSIX Threads Library/

Doing it like this means I get platform-agnostic dependency checking, which would be a great feature for Capistrano 2’s dependency checking implementation.

Feedback?