This is as days pass by, by Stuart Langridge

On tolerance

I've spent the evening fuming. Earlier this evening, Norm said:
I use a Mac because I value my time at more than £0/hour.*
I hate that sort of comment. It's a typical snide, sneering aside from a Mac user to someone who uses Linux. The subtext, which is hardly sub at all, is "clearly you don't value your time because you spend it playing around compiling your kernel and fighting your operating system". Suggesting that obviously I've never considered using a Mac because I'm too stupid to realise that my life would be so much easier. Like I spend all my day compiling a kernel, which I haven't done for about a decade. Like they've seen the great wide ocean of possibility in front of them and I'm still scrabbling around on the beach looking at stones.* I was all ready to fire back something equally snide. "Yeah, I value trust at something more than £0." Or "what do you use the time for, clicking 'no I don't want Quicktime Pro' dialogs???" Or something equally mature. Or maybe an email to Norm, saying: do you realise how offensive a comment that is? Do you realise how sneering and holier-than-thou it sounds? And then I thought, now, hang on, this is Norm. He's a sensible guy. The second half of his comment, after all, was "Free OSs are good, OS X is great." He uses Linux all the time. And then came the second, darker, thought: that his comment was in reaction to one of mine. The conversational thread went:
  • sil: although does anyone not run ff3.5? what is this, the middle ages? :)
  • anonymous: why on earth would any OS X users run FF 3.5? Firefox is slow, crashes frequently, and is generally quite buggy on Macs.
  • sil: feel free to adapt the question to "why is anyone using a Mac" as well if you like. :)
  • Norm: I use a Mac because I value my time at more than £0/hour. Free OSs are good, OS X is great.
And do you know, I was just as snide and nasty. That was a humbling thought. There's a lot of intolerant comment out there. There are plenty of people who really think that a modern Linux is still all about compiling your kernel and compiling all your programs and configuring your machine in emacs and not actually doing anything constructive. We, the Linux community, tend to either shake our heads and wait for them to catch up (in the best case) or flame the shit out of them like whiny teenagers banned from the cinema for throwing popcorn (in the worse cases). But there are an awful lot of people out there who think that Macs only have one mouse button and are just "shiny" with no underlying decency behind them. I try to not think that but, well, it's easy to make that generalisation just to score cheap points, just like it's easy to suggest that Linux users waste their time compiling kernels for the same cheap points. I should do that less. John Gruber said recently, "With regard to the freedoms that stem from the software being open source, something like Ubuntu isn’t just, say, ten times better than Windows or Mac OS X, it is infinitely better." Sometimes I forget that just because my stuff's infinitely better, it doesn't mean that I can divide by that infinity to conclude that their stuff's worth zero, and it certainly doesn't mean that they think it's worth zero.* So, sorry, Norm. I'll try and do better next time.

Building applications with Ubuntu One: OSCON presentation

This morning I did a talk here at OSCON 09 about Desktop Couch and Ubuntu One and how it all fits together. No nice presentation page this time, I'm afraid, just a PDF: Building Applications with Ubuntu One (3.1MB PDF) Update: I forgot I had a script which creates a presentation page. So, Building Applications with Ubuntu One, the presentation: view in the browser or download in ODP and PDF formats.

Desktop Couch initial code

The idea of putting CouchDB on every desktop has been getting a lot of play at GCDS this week, which is cool. Some of the stuff we've been working on is to do with the infrastructure of that, and we* found time to separate this out into a separate project, desktopcouch on Launchpad. There's a few things in there already:
  • Startup scripts for a "personal" desktop Couch
  • A D-Bus "port advertiser": because the scripts start up your desktop Couch on a random port, you need to find out which port that is, and so there's a tiny D-Bus API which returns the port number
  • The D-Bus API is designed to be run from D-Bus activation; if your desktop Couch isn't running then it is started by the D-Bus API. This means that your desktop Couch isn't running on startup, so startup time isn't affected, but the first time anything tries to use it it is started.
  • Pairing between two of your desktop Couches. This isn't finished yet, but this is for sharing your data between two of your machines. Say you've got a laptop and a netbook; you pair those two machines and then any data in one appears in the other. So, if you're storing your Firefox bookmarks in your desktop Couch then adding a new bookmark on your netbook will cause it to appear on your desktop. All this pairing works on your local network; it doesn't use a server out there on the internet. (Nice work, Chad!)
  • The Records API. As noted earlier in these pages we're all talking about having a few extra fields in each document in a desktop Couch to add some extra useful infrastructure. CouchDB documents with these fields in we're calling "records". record_type and record_type_version are to help you know what type of thing a record describes. The standard way of storing a list as a uuid-keyed dictionary helps when replicating data around between paired desktop Couches. This "Records API" is a Python API for accessing your desktop Couch; it handles the record_type stuff and the MergeableList stuff for you, and it also takes care of things like doing the D-Bus call to find out your desktop Couch's port number. (Note that it'll "fall back" to a system-level CouchDB on port 5984, so if you want to experiment you don't need a desktop Couch up and running!)

Firefox bookmarks in CouchDB

Me and Zachery Bir, as part of the Desktop CouchDB idea that myself and Rodrigo have been talking about, have been working on a Firefox extension to store your bookmarks in CouchDB. So they're there in the database, you can see them in the database, you can replicate the database around. All happens automatically. I rather like this kind of thing, and I'm told that people at GCDS have been getting excited about CouchDB too. I can't blame them; I'm really enjoying working with it, and this sort of thing is why! A quick screencast: edit one of your bookmarks in CouchDB and lo, it changes in the browser. Editing a Firefox bookmark in CouchDB (Ogg Theora video, 517K) (or on YouTube, thanks @janl) Of course, you don't have to edit things in the HTML interface to the database itself if you don't want to; if your bookmark records are being replicated to your other machines on your network, then a change to one is a change to all, and you can change data in Couch from pretty much any language you want -- I keep thinking about "dynamic bookmarks" that update over the course of the day or depending on what you've got running on your machine. But I'm getting ahead of myself here...yesterday, my contacts in Couch, today, bookmarks in Couch, tomorrow...the world? (update: oops, forgot a link to code! Bindwood on Launchpad. At the moment the code's distributed over various branches, but we'll be pulling stuff into trunk over the next couple of days.)

Can't publish HTML5 video

Mysterious. I can't publish Ogg video using the HTML5 video tag, and I don't know why. Observe (in Firefox 3.5, or I think beta Opera supports it):

My video (doesn't work)

<video controls src="http://www.kryogenix.org/random/couch-firefox-bookmarks-initial.ogv" type="video/ogg">  
Your browser does not support the video element.</video>

Mozilla's video (does work)

<video controls src="http://videos.mozilla.org/firefox/3.5/meet/meet.ogv" type="video/ogg">  
Your browser does not support the video element.  
</video>

Headers from each

aquarius@dell-desktop:~$ HEAD http://www.kryogenix.org/random/couch-firefox-bookmarks-initial.ogv
200 OK
Connection: close
Date: Mon, 06 Jul 2009 21:09:00 GMT
Accept-Ranges: bytes
ETag: "2b918c-81578-7e099f80"
Server: Apache/2.0.54 (Debian GNU/Linux) DAV/2 SVN/1.1.4 mod_fastcgi/2.4.2 PHP/4.3.10-18
Content-Length: 529784
Content-Type: video/ogg
Last-Modified: Mon, 06 Jul 2009 19:27:10 GMT
Client-Date: Mon, 06 Jul 2009 20:50:28 GMT
Client-Peer: 89.16.177.185:80
Client-Response-Num: 1

aquarius@dell-desktop:~$ HEAD http://videos.mozilla.org/firefox/3.5/meet/meet.ogv
200 OK
Connection: close
Date: Mon, 06 Jul 2009 20:50:35 GMT
Accept-Ranges: bytes
ETag: "a6d6f5-7c0181-da8672c0"
Server: Apache/2.2.3 (Red Hat)
Content-Length: 8126849
Content-Type: video/ogg
Last-Modified: Fri, 19 Jun 2009 15:05:23 GMT
Client-Date: Mon, 06 Jul 2009 20:50:35 GMT
Client-Peer: 63.245.208.152:80
Client-Response-Num: 1
X-Pad: avoid browser bug
I don't get it. What am I doing wrong? Is the mysterious X-Pad header something to do with it? Update: apparently it works for everyone but me, so I assume Firefox has cached the wrong content type somewhere (it was text/plain at first, oops).

Not blocking the UI in tight JavaScript loops

Everyone's written a JavaScript loop that just loops over all the {LIs, links, divs} on a page*, and it's pretty standard. Something like
var lis = document.getElementsByTagName("li");
for (var i=0; i<lis.length; i++) { // yes this could be more efficient, don't care
  // do something here to lis[i]
};
or, if you're using jQuery:
$("li").each(function() {
  // do something here to this
});
This is problematic if there are, say, 2000 LI elements on the page, and what you're doing in the loop is semi-intensive (imagine you're creating a couple of extra elements to append to each of those LIs, or something like that). The reason this is a problem is that JavaScript is single-threaded. A tight loop like this hangs the browser until it's finished, you get the "this script has been running for a long time" dialog, and the user interface doesn't update while you're in this kind of loop. You might think: aha, this will take a long time, so I'll have some sort of a progress monitor thing:
var lis = document.getElementsByTagName("li");
for (var i=0; i<lis.length; i++) { // yes this could be more efficient, don't care
  // do something here to lis[i]
  progressMonitor.innerHTML = "processing list item " + i; // fail
};
but that doesn't work. What happens is that the browser freezes until the loop finishes. Annoying, but there it is. One approach to getting around this is with timeouts rather than a for loop.
var lis = document.getElementsByTagName("li");
var counter = 0;
function doWork() {
  // do something here to lis[i]
  counter += 1;
  progressMonitor.innerHTML = "processing list item " + counter;
  if (counter < lis.length) {
    setTimeout(doWork, 1);
  }
};
setTimeout(doWork, 1);
so you move the bit of work you need to do into a function, and that function re-schedules itself repeatedly, using setTimeout. This time, your user interface will indeed update, and your progress monitor will show where you're up to. There are a couple of caveats with this: it'll take a bit longer, and you're no longer guaranteed to have things processed in the order you expect, but they're minor issues. For doing this in jQuery, a tiny plugin:
jQuery.eachCallback = function(arr, process, callback) {
    var cnt = 0;
    function work() {
        var item = arr[cnt];
        process.apply(item);
        callback.apply(item, [cnt]);
        cnt += 1;
        if (cnt < arr.length) {
            setTimeout(work, 1);
        }
    }
    setTimeout(work, 1);
};
jQuery.fn.eachCallback = function(process, callback) {
    var cnt = 0;
    var jq = this;
    function work() {
        var item = jq.get(cnt);
        process.apply(item);
        callback.apply(item, [cnt]);
        cnt += 1;
        if (cnt < jq.length) {
            setTimeout(work, 1);
        }
    }
    setTimeout(work, 1);
};
and now you can do
$.eachCallback(someArray, function() {
  // "this" is the array item, just like $.each
}, function(loopcount) {
  // here you get to do some UI updating
  // loopcount is how far into the loop you are
});

$("li").eachCallback(function() {
  // do something to this
}, function(loopcount) {
  // update the UI
});
Not always a useful technique, but when you need it, you need it.

This website belongs to Stuart Langridge. Contact details are available. Don't eat yellow snow. Valid HTML5, at least in theory, except for the bits that aren't because I'm that futuristic that I'm ahead of the spec, oh yes. HTML5 help from Bruce Lawson, among others. Fonts from the superb FontSquirrel. End.