nick.recoil.org

MacRuby – Expressiveness with power

In the last few months, I’ve made time to migrate some old RubyCocoa code to the latest and greatest version of MacRuby. Not only did I see a huge performance increase across the board, I found I could also create a stand-alone binary which could easily be distributed to end users. The entire framework is fast becoming a first class citizen on Mac OS X.

It was during the porting process that I ran into a small issue when attempting to read ID3 tags from mp3 files. I’d previously used the native Ruby gem ruby-mp3info, but this had some compatibility issues with MacRuby 0.6, and refused to install. After hacking around with the gem source, I worked around the issues, but the resulting performance was awful, so I reviewed my choices:

  1. Investigate the cause of the performance problems in ruby-mp3info
  2. Switch to using id3lib-ruby
  3. Investigate bundles and use a C/C++ library directly

I didn’t sink a lot of time into fixing ruby-mp3info’s specific problems on what is still a relatively niche platform, and I felt that using id3lib-ruby would complicate my ability to create standalone binaries. id3lib-ruby is so named because it depends on the libid3 C++ library, which would introduce another layer of complexity into the build process. Lastly, the id3lib gem didn’t support the latest version of ID3 tags, and wasn’t under active development.

That left the last option, which I had always wanted a good excuse to investigate. I had originally looked into this when I originally started looking at RubyCocoa in Summer ’09, but linking with native Objective-C seemed excessively difficult and poorly-documented.

One of the many improvements that the MacRuby project brought over RubyCocoa was to make this functionality as easy as loading bundles in native Objective-C applications. This is achieved with use of the NSBundle class, so I thought all I needed to do was to take a C or C++ ID3 library, and wrap it in a very small Objective-C wrapper. Through the magic of runtime introspection, all of the wrapper’s methods would become available when you loaded the bundle into the MacRuby environment.

After reading up on existing tutorials, I managed to get this to work, and because it’s using the native compiled code, it’s extremely fast. This process of using interpreted Ruby code with compiled C/C++/Objective-C code is tremendously powerful, and allows for a mix and match approach to development that enables both rapid prototyping, and an ability to optimise for performance, from within a single codebase.

So powerful is this technique that I wanted to write up my experiences for others, who like myself, had maybe had just enough experience with Ruby (through Rails) and iOS development to feel that merging them together might be fun. With Mac OS X as my primary desktop environment, I’ve now achieved a triumvirate of control:

  1. I can write web-based code that lives in the cloud and is available from anywhere in the world.
  2. I can write mobile applications that I can carry with me at all times.
  3. I can write larger, more flexible and powerful applications when a desktop environment is appropriate.

I can remember buying my first Powerbook back in 2002 and getting the distinct feeling that Mac OS X had successfully channeled all of the potential energy that the original NeXTSTEP had so successfully created. That this was a futuristic platform that eagerly awaited the craftsman’s touch. Something that could be deftly moulded it into whatever shape you could conceive, if only you invested a little time in the tools.

Eight years on, the choice has grown richer. MacRuby occupies a unique place within the spectrum of development tools on the Mac, offering a high level ease in harnessing low level power. If you haven’t looked at it yet, I can recommend a good tutorial.

mythRecommend

I’ve pushed my beta code for mythRecommend to Github. It’s a system which allows you to subscribe to a source of regular TV recommendations, and have those recommendations automatically get translated into concrete recording schedule for a MythTV system.

Once a recording has been made, the recording schedule is made dormant, so it will not clutter your existing schedule list as time goes on. You can decide if you would like to turn that recommended program into a permanent schedule or not, and reinstate it if desired.

I hope to add more feed parsers as the code matures, but as things stand there is only one written, specifically for The Guardian here in the UK.

Scripting MythTV

At the end of June, I gave a short talk to the London Ruby Users Group on some code I’ve been working on to enable me to interface with MythTV on a programatic level. MythTV is Open Source software which allows the recording and playback of TV on a Linux box, turning it into a PVR. I use this at home to record TV from Freeview.

MythTV has a web interface written for it in the form of MythWeb, and it allows decent remote interaction with the system. It is also my main mode of interaction when trawling for interesting programmes to record. It’s much more convenient to use a keyboard and mouse to navigate, rather than a remote control. This is especially true here in the UK, where there are many Freeview channels that could contain content of interest.

Creating a pleasant API

However, if you’re looking to play around with integration, and prototype some ideas you have to help you navigate and interact in different ways, you’re not going to have an easy ride. There’s no single clean implementation of the functionality you can access in any one language. PHP drives the main MythWeb code, but it also mixes in some Perl to access some fairly major functionality (downloading/streaming the recorded programmes). There are also Python bindings within the MythTV codebase, but I’m not aware of anything substantial built on top of them, and what is implemented is mainly concerned with metadata.

The main functions I wanted a MythTV integration library to offer were the ability to list what had been recorded, obtain a thumbnail for that recording, and to be able to stream the data to you. This was achieved in the 0.1.0 release. Now we’re up to version 0.2.0, it now supports listing of EPG data, and creation/editing of recording schedules. This paves the way for experiments in automatic scheduling of recordings. There are many potentially interesting sources of TV reviews to scrape. For instance, if you like what The Guardian has to say about Television in its Watch this section, then extracting the titles from that page, and feeding them to ruby-mythtv is easy.

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
  require 'ruby-mythtv'
 
  # Connect to the server
  mythbackend, mythdb = MythTV.connect(:host => 'mythtv.localdomain',
                                       :database_password => 'password')
 
  # Find matches on our search term, and limit the results to 5 matches
  programs = mythdb.list_programs(:conditions => ['title LIKE ?', "%Bruce Parry%"],
                                  :limit => 5)
 
  # Take the first program match, and convert it to a recording schedule
  new_schedule = MythTV::RecordingSchedule.new(programs[0], mythdb)
  new_schedule.save
 
  # Signal the backend of recording changes for our recording schedule entry
  mythbackend.reschedule_recordings(new_schedule.recordid)
 
  # Let the backend resolve matches
  sleep(1)
 
  # Enumerate the list of pending recordings, find ours, and check for any conflicts
  pending_recordings = mythbackend.query_pending
  conflicts = pending_recordings.find { |p| p.recordid == new_schedule.recordid &&
                                            p.recstatus_sym == :rsConflict }
 
  # If conflicts is empty, then all is good. If it is populated, then action needs
  # to be taken, such as bumping the priority, or removing the clashes....

Ruby-mythtv is present both on Rubyforge and Github. Major release versions will be present on Rubyforge, and development versions will be present on Github. This method allows me to push out code which I consider functional, but not necessarily finalised. Release early, release often, as the saying goes, and this is all the more relevant when forking projects and sharing ideas is central to the way Github works. There’s nothing worse than code which is “almost there”, languishing in your “I really should finish it” folder, destined never to see the light of day.

Mongrel init.d script

I’ve slightly modified Bojan Mihelac’s Mongrel init.d script to cope with the situation where there are stale PID files left from a server failure.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

#!/bin/env ruby
#
# mongrel Startup script for Mongrel by Tim Morgan, modified by bmihelac and Nick Ludlam
# Originally from http://source.mihelac.org/2007/3/27/customized-mongrel-startup-script
#
# chkconfig: 2345 85 15
# description: mongrel manages Mongrel
#

apps = [
  {:app => 'app1'},
  {:app => 'app2'},
  {:app => 'app3'}
]

default_port = 8000
default_options = {
  :app_dir => '/var/www',  :environment => 'production'
}

pid_location = "log/mongrel.pid"

if ['stop', 'restart'].include? ARGV.first
  apps.each do |app|
    options = default_options.merge(app)
    path = File.join options[:app_dir], options[:app], "current"
    puts "Stopping #{path}..."
    `/usr/local/bin/mongrel_rails stop -c #{path} -P #{pid_location}`
  end
end

if ['start', 'restart'].include? ARGV.first
  apps.each do |app|
    options = default_options.merge(app)
    path = File.join options[:app_dir], options[:app], "current"
    port = options[:port] || default_port
    pid_file = File.join path, pid_location

    # Check and remove stale PID file. Platform needs "ps -p" support
    if File.exists?(pid_file)
      old_pid = File.read(pid_file)
      `ps -p #{old_pid}`
      if old_pid.to_i > 0 && $?.exitstatus == 1
        puts "Removing stale PID file"
        File.unlink(pid_file)
      end
    end

    puts "Starting #{options[:app]} on #{port}..."
    `/usr/local/bin/mongrel_rails start -d -p #{port} -e #{options[:environment]} -c #{path} -P log/mongrel.pid`
    default_port = port + 1

  end
end

unless ['start', 'stop', 'restart'].include? ARGV.first
    puts "Usage: mongrel {start|stop|restart}"
    exit
end

Mephisto plugin to allow self-updating links

I’ve placed a page under code which details a little plugin I’ve been working on for Mephisto. It’s designed to make links in your templates which need to point to the latest software release always valid. It scans a directory according to a shell glob expression, and gives back a block with the file path, name and version as local variables.

The shell glob expression can be passed in via the liquid template, as per normal with mephisto plugins, or it can be passed in as a special tag attached to the page which is being rendered. This allows per-page links, rather than per-template, and gives greater flexibility.

More information is on the dynamic file link page.

Flickr photostream

			Nick Ludlam posted a photo:	From the Android supplement from The Guardian on 29/08/2010. I was interviewed for a piece on the future trends in Mobile apps. www.guardian.co.uk/lg-talking-technology/the-future-of-apps			Nick Ludlam posted a photo:	My wonderful colleagues at BERG bought lots of cake for my birthday. And not just any old cake! This was from Konditor and Cook. It was delicious!We get bonus points for having to cut it with a craft knife, since it was either that or a scalpel.			Nick Ludlam posted a photo:	I've set up a custom Ruby script to scrape my balance information from the Three.co.uk website, and a custom app to receive the notifications. I've set it up to tell me my balance every day, since there is no automatic notification on Three as there is with O2.			Nick Ludlam posted a photo:	Just up the road from BRIG. Lovely coffee, and nice people serving it. The map shows you where it is!			Nick Ludlam posted a photo:	An image of an advertisement in the Sun Newspaper for Android phones sold by Carphone Warehouse here in the UK.Underneath is a section which lists 10 apps that "you need to get through the day". 8 are free, 1 is £1, and the last one did not even seem to be listed when I searched. "Where's The Train", the only non-free app, is listed as having "100 - 500 downloads"This is very typical of my experience with the Android Market. Unless your business model can support free applications, with your revenue coming from advertising, a website or service you sell, then you are unlikely to be able to recoup the time spent developing quality applications.			Nick Ludlam posted a photo:	I love their spoons. As you pull the spoon out of your mouth, you can feel the texture of the detailing on your top lip			Nick Ludlam posted a photo:	The girls pose for a quick picture before heading off to the race start line			Nick Ludlam posted a photo:	That 'Touch here if bicycle is damaged' button will be too tempting			Nick Ludlam posted a photo:	The new bike stands have just appeared on Shoreditch High St.

Delicious links