<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8624721007426248377</id><updated>2012-02-16T20:53:52.323Z</updated><category term='linux'/><category term='rest'/><category term='couchdb'/><category term='openid'/><category term='music'/><category term='funny'/><category term='go'/><category term='erlang'/><category term='python'/><category term='aikido'/><category term='development'/><title type='text'>Random Acts of Senseless Blogging</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://matt.goodall.me/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>19</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-3470986062162724450</id><published>2009-11-15T23:27:00.007Z</published><updated>2010-07-20T08:21:55.425+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='go'/><title type='text'>100,000 tasklets: Erlang and Go</title><content type='html'>&lt;p&gt;Purely out of interest, and to see how &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; and &lt;a href="http://golang.org/"&gt;Google's Go&lt;/a&gt; compare on my puny laptop (a Dell Mini 10v), I wrote an Erlang version of the example application on &lt;a href="http://dalkescientific.com/writings/diary/archive/2009/11/15/100000_tasklets.html"&gt;100,000 tasklets: Stackless and Go&lt;/a&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Basically, it creates a chain of 100,000 microthreads (tasklet, goroutine, process ... take your pick), sends a value in one end and waits for the result at the other end. The number is incremented by each microthread it passes through.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;The code comes in two parts. Firstly, a chain.erl module:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;-module(chain).&lt;br /&gt;-export([run/1]).&lt;br /&gt;&lt;br /&gt;run(Num) -&gt;&lt;br /&gt;Tail = chain(Num, self()),&lt;br /&gt;Tail ! 0,&lt;br /&gt;receive Result -&gt; Result end.&lt;br /&gt;&lt;br /&gt;chain(0, Tail) -&gt;&lt;br /&gt;Tail;&lt;br /&gt;&lt;br /&gt;chain(Num, Tail) -&gt;&lt;br /&gt;chain(Num-1, spawn(fun() -&gt; f(Tail) end)).&lt;br /&gt;&lt;br /&gt;f(Tail) -&gt;&lt;br /&gt;receive&lt;br /&gt;Num -&gt; Tail ! Num+1&lt;br /&gt;end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;And secondly a simple escript to start it off (mostly to make it easy to run under time):&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;#!/usr/bin/env escript&lt;br /&gt;%%! +P 1000000 -smp disable&lt;br /&gt;-export([main/1]).&lt;br /&gt;&lt;br /&gt;main([]) -&gt;&lt;br /&gt;Result = chain:run(100000),&lt;br /&gt;io:format("~p~n", [Result]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;And the run times:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;$ time ./chain &lt;br /&gt;100000&lt;br /&gt;&lt;br /&gt;real 0m1.520s&lt;br /&gt;user 0m1.012s&lt;br /&gt;sys 0m0.468s&lt;br /&gt;$ time ./go-chain &lt;br /&gt;100000&lt;br /&gt;&lt;br /&gt;real 0m3.371s&lt;br /&gt;user 0m1.672s&lt;br /&gt;sys 0m1.000s&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;A couple of things to point out/mention:&lt;/p&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The Erlang code is just beautiful (well, maybe not the escript so much ;-)). To me, it's more readable than either the Python or Go versions.&lt;/li&gt;&lt;li&gt;I turned SMP off. Yes, it's an optimisation but then the tasks are running in series so it's never going to help.&lt;/li&gt;&lt;li&gt;The Go version was compiled and linked using 8g and 8l.&lt;/li&gt;&lt;li&gt;The Go version didn't always complete and sometimes took a *very* long time ... just not when running under time for some reason.&lt;/li&gt;&lt;li&gt;Go seemed to use about 3x as much memory.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p&gt;What does this prove? Absolutely nothing! Firstly, it's an unrealistic application. Also, Go is really quite new and I'm sure performance and memory use will improve in the coming months.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;So why did I do this? Simply, because I really enjoy playing with Erlang and Go is interesting and a hot topic. (In my opinion anything with concurrency built into the runtime is onto a good thing ... I sure wish we didn't have to resort to Twisted, Stackless, greenlets or generator hacks in Python.)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-3470986062162724450?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/3470986062162724450/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=3470986062162724450' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/3470986062162724450'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/3470986062162724450'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/11/100000-tasklets-erlang-and-go.html' title='100,000 tasklets: Erlang and Go'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-788080844565319921</id><published>2009-10-22T11:53:00.003+01:00</published><updated>2009-10-22T12:32:54.868+01:00</updated><title type='text'>CouchDB and XML</title><content type='html'>&lt;div&gt;Everyone knows by now that &lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt; is a &lt;a href="http://en.wikipedia.org/wiki/Json"&gt;JSON&lt;/a&gt; document store, right? What's not quite so obvious is that it handles &lt;a href="http://en.wikipedia.org/wiki/XML"&gt;XML&lt;/a&gt; very nicely too via &lt;a href="http://en.wikipedia.org/wiki/ECMAScript_for_XML"&gt;E4X&lt;/a&gt;. Strictly speaking, it's the &lt;a href="http://www.mozilla.org/js/spidermonkey/"&gt;Mozilla Spidermonkey&lt;/a&gt; &lt;a href="http://en.wikipedia.org/wiki/Javascript"&gt;JavaScript&lt;/a&gt; engine that provides the E4X support but that's CouchDB's default view server.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm calling out to a 3rd party &lt;a href="http://en.wikipedia.org/wiki/SOAP"&gt;SOAP&lt;/a&gt; service and the result is returned as an XML string (don't blame me for that, it's what &lt;i&gt;their&lt;/i&gt; &lt;a href="http://en.wikipedia.org/wiki/Web_Services_Description_Language"&gt;WSDL&lt;/a&gt; file says). I've been tossing that result into a CouchDB doc - it's so easy, you might as well - in case I needed to refer to it later.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That time has come as I now need to be a bit more selective about how I handle the result. So, I fired up CouchDB's Futon and wrote a view that emitted bits of the JSON together with the interesting parts of the XML blob. Now I have a simple view of the data without all the XML "noise". Very nice :).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What's best about this is that using &lt;a href="http://www.cmlenz.net/archives/2008/03/couchdb-xml-and-e4x"&gt;CouchDB, XML and E4X&lt;/a&gt; together is trivial and E4X is compact and quick to learn.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-788080844565319921?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/788080844565319921/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=788080844565319921' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/788080844565319921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/788080844565319921'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/10/couchdb-and-xml.html' title='CouchDB and XML'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-7662523359535414761</id><published>2009-09-30T22:48:00.005+01:00</published><updated>2009-11-12T10:40:05.080Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='couchdb'/><title type='text'>Build CouchDB on Ubuntu 9.10 (Karmic Koala)</title><content type='html'>&lt;div&gt;Ubuntu 9.10 includes CouchDB in the standard desktop installation ... perhaps that's all you need?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Personally, I like to track CouchDB trunk but building on the latest Ubuntu is not quite as easy as it used to be. The following is mostly from memory so may not be quite right ...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The list of packages in the CouchDB README to compile from source does not work (at least, not for me). Instead, ensure your apt sources are up to date and install the following packages:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;$ sudo apt-get install build-essential erlang-nox erlang-dev libicu-dev xulrunner-dev libcurl4-openssl-dev&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, configure CouchDB to use xulrunner's headers and libraries, using a custom LD_RUN_PATH:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;$ LD_RUN_PATH=/usr/lib/xulrunner-1.9.1.5 ./configure --with-js-lib=/usr/lib/xulrunner-devel-1.9.1.5/lib/ --with-js-include=/usr/lib/xulrunner-devel-1.9.1.5/include&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And then &lt;i&gt;make&lt;/i&gt; with the same LD_RUN_PATH:&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;$ LD_RUN_PATH=/usr/lib/xulrunner-1.9.1.5 make&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Finally, &lt;i&gt;make install&lt;/i&gt; CouchDB as usual and you're ok to start the CouchDB server.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Note that if Ubuntu's standard CouchDB package is also installed you may need to avoid your install clashing with it. Edit your install's &lt;i&gt;etc/couchdb/local.ini&lt;/i&gt; and set &lt;i&gt;port&lt;/i&gt; in the &lt;i&gt;httpd&lt;/i&gt; section to something different. I use 15984 but it doesn't really matter as long as it's unique.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-7662523359535414761?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/7662523359535414761/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=7662523359535414761' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/7662523359535414761'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/7662523359535414761'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/09/build-couchdb-on-ubuntu-910-karmic.html' title='Build CouchDB on Ubuntu 9.10 (Karmic Koala)'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-576298937022844590</id><published>2009-09-18T15:26:00.004+01:00</published><updated>2009-09-19T13:44:00.620+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Tornado ... first thoughts</title><content type='html'>It's quite amusing to see the furore that's surrounded &lt;a href="http://www.tornadoweb.org/"&gt;Tornado&lt;/a&gt;'s release, especially how it compares to &lt;a href="http://twistedmatrix.com/trac/"&gt;Twisted&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I like Twisted a lot although I'm far from a fan boy. Deferreds are not ideal but without proper coroutines (&lt;a href="http://www.stackless.com/"&gt;stackless&lt;/a&gt;, &lt;a href="http://pypi.python.org/pypi/greenlet"&gt;greenlet&lt;/a&gt;, etc) or message dispatch (see &lt;a href="http://erlang.org/"&gt;Erlang&lt;/a&gt;) in the core language they're a reasonable way to &lt;a href="http://twistedmatrix.com/projects/core/documentation/howto/async.html"&gt;model async processes&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Anyway, a colleague asked me about my opinion on Tornado the day it was released. I'd only looked at it very briefly by then (scanned the docs and scanned the code) but I'm never short on opinion ;-).&lt;br /&gt;&lt;br /&gt;So, mostly for posterity (there are far more in-depth posts elsewhere), below is my response almost verbatim. I'm sure to be wrong about some points, and I have no problem admitting that, but I've since seen comments that confirm at least some.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Only had a quick look at Tornado yesterday; I intend to look more&lt;br /&gt;closely sometime. However, a few thoughts did spring to mind ...&lt;br /&gt;&lt;br /&gt;Simple web page looks nice and simple ... as it should.&lt;br /&gt;&lt;br /&gt;Can't get too excited about the url dispatch and request handler.&lt;br /&gt;Nothing new to see, afaict.&lt;br /&gt;&lt;br /&gt;Performance is quite impressive but I was surprised that 4 cores&lt;br /&gt;wasn't even close to 4x the performance of a single core. Probably&lt;br /&gt;something else limiting it but not very clear from the chart.&lt;br /&gt;&lt;br /&gt;No Twisted performance comparison? I suspect Tornado is a little&lt;br /&gt;faster but then it's much more focussed on what it's trying to&lt;br /&gt;support.&lt;br /&gt;&lt;br /&gt;Tornado's a callback-style framework. However, they don't seem to&lt;br /&gt;bother handling errors (at least not in any consistent way). I think&lt;br /&gt;that's a really bad idea. Sure, Twisted's Deferred is not ideal but I&lt;br /&gt;think it's necessary. Sure, they could add an errback to every&lt;br /&gt;function too ... but they don't seem to have bothered yet so they're&lt;br /&gt;going to end up with an inconsistent way of reporting errors at best.&lt;br /&gt;(The iostream module, for instance, appears to silently dump errors).&lt;br /&gt;&lt;br /&gt;Their database module sucks big time. As far as I can see it blocks.&lt;br /&gt;What good is that in an async framework!? It's MySQL only. Also ...&lt;br /&gt;autocommit(True) ... argh!!!!&lt;br /&gt;&lt;br /&gt;There's absolutely nothing in the docs about how to call those pesky&lt;br /&gt;blocking libraries, you know like the entire Python stdlib or MySQL,&lt;br /&gt;and there's no threading support afaict.&lt;br /&gt;&lt;br /&gt;template library ... hohum.&lt;br /&gt;&lt;br /&gt;locale support ... "Loads translations from CSV files in a directory"&lt;br /&gt;... enough said.&lt;br /&gt;&lt;br /&gt;OK, too many negatives so far ...&lt;br /&gt;&lt;br /&gt;auth module looks very cool. i'd love to be able to support all those&lt;br /&gt;out-of-the-box.&lt;br /&gt;&lt;br /&gt;web "UI modules". looks interesting, need to take a closer look.&lt;br /&gt;&lt;br /&gt;XSRF. nice, and now I know how to handle it without sessions ... use a&lt;br /&gt;cookie instead, duh!&lt;br /&gt;&lt;br /&gt;static file serving ... i like the automatic far-future expires + file&lt;br /&gt;version. i've been meaning to write something to do that for a while&lt;br /&gt;now only I was going to use the file's timestamp instead of the&lt;br /&gt;content hash (much better).&lt;br /&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-576298937022844590?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/576298937022844590/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=576298937022844590' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/576298937022844590'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/576298937022844590'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/09/torndao-first-thoughts.html' title='Tornado ... first thoughts'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-9174369493706319995</id><published>2009-03-10T13:00:00.003Z</published><updated>2009-03-10T13:19:56.663Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='music'/><title type='text'>Ten Tracks - 10 music tracks for £1</title><content type='html'>Looks like &lt;a href="http://www.tentracks.co.uk/"&gt;Ten Tracks&lt;/a&gt; only launched late last year ... but wow, what a fantastic site for music lovers.&lt;br /&gt;&lt;br /&gt;Basically, there are a bunch of channels, each channel publishes 10 tracks per month (almost, it's early days yet) and we, the music buying public, get to buy all 10 tracks for just £1.&lt;br /&gt;&lt;br /&gt;Thank you &lt;a href="http://www.toob.org.uk/"&gt;Toob&lt;/a&gt; for telling me about the site and also for allowing Ten Tracks to distribute one of your tracks in &lt;a href="http://www.tentracks.co.uk/channel/open-ear/open-ear-february-09"&gt;this month's Open Ear channel&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-9174369493706319995?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/9174369493706319995/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=9174369493706319995' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/9174369493706319995'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/9174369493706319995'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/03/ten-tracks-10-music-tracks-for-1.html' title='Ten Tracks - 10 music tracks for £1'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-1925828509845571198</id><published>2009-02-13T20:29:00.004Z</published><updated>2009-02-13T20:42:12.056Z</updated><title type='text'>World of Goo on Linux ... hurray!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2dboy.com/blog/wp-content/uploads/2009/01/goolinux.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 480px; height: 228px;" src="http://2dboy.com/blog/wp-content/uploads/2009/01/goolinux.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://2dboy.com/"&gt;2D Boy&lt;/a&gt; have just released the &lt;a href="http://2dboy.com/2009/02/12/world-of-goo-linux-version-is-ready/"&gt;Linux version of World of Goo&lt;/a&gt;. As if that wasn't good enough, there are even .deb packages available.&lt;br /&gt;&lt;br /&gt;A wonderful game just got a bit more wonderfuller (and yes, that is a real word ;-)).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-1925828509845571198?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/1925828509845571198/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=1925828509845571198' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/1925828509845571198'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/1925828509845571198'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/02/world-of-goo-on-linux-hurray.html' title='World of Goo on Linux ... hurray!'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-4014759663997829136</id><published>2009-01-07T00:36:00.017Z</published><updated>2009-01-07T13:34:01.726Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='rest'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>restish resources ... from the ground up</title><content type='html'>I've been working on a &lt;a href="http://wsgi.org/wsgi/"&gt;WSGI&lt;/a&gt; web framework called &lt;a href="http://ish.io/projects/show/restish"&gt;restish&lt;/a&gt;. Yes, another one ... sorry! Only, this one's got a serious preference for trying to stay close to HTTP and the way of the web, and therefore encourages &lt;a href="http://en.wikipedia.org/wiki/Representational_State_Transfer"&gt;REST&lt;/a&gt; principles.&lt;br /&gt;&lt;br /&gt;restish is really simple and, in my opinion, a pleasure to use. It's also extremely light-weight compared to some other web frameworks, mostly because it doesn't actually attempt to do that much :).&lt;br /&gt;&lt;br /&gt;One feature that will hopefully be of interest is that there is no reliance on threads. There's no thread local use anywhere. In fact, they're banned, I despise the things! That allows a restish app to run happily inside a threaded &lt;a href="http://pythonpaste.org/deploy/"&gt;Paste Deploy&lt;/a&gt; server or a &lt;a href="http://pypi.python.org/pypi/Spawning"&gt;Spawning&lt;/a&gt; server in &lt;a href="http://codespeak.net/py/dist/greenlet.html"&gt;greenlet&lt;/a&gt; mode, i.e. with threads switched off), or potentially any other web server. Basically, *you* choose how you want to deploy it. If you decide to use a threaded model then fine, but why should the web framework dictate to you form the start?&lt;br /&gt;&lt;br /&gt;Anyway enough, let's see some code. I thought I'd try to demonstrate the basic idea of restish resources. (I do intend to move all this to the &lt;a href="http://ish.io/embedded/restish/"&gt;restish documentation&lt;/a&gt; at some point but that will take longer than an informal blog post.)&lt;br /&gt;&lt;br /&gt;Oh, all the following bits of code *should* run. If you want to try them out, the 'application' is actually a WSGI application. Running under &lt;a href="http://pypi.python.org/pypi/Spawning"&gt;Spawning&lt;/a&gt; is as easy as:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;$ spawn module_name.application&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The simplest resource imaginable&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;from restish import app, http&lt;br /&gt;    &lt;br /&gt;def hello_world(request):&lt;br /&gt;    return http.ok([('Content-Type', 'text/plain')], 'Hello, world!')&lt;br /&gt;    &lt;br /&gt;application = app.RestishApp(hello_world)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;OK, so there's this thing called a RestishApp. It's just a WSGI application that kicks of the request handling process. Nothing too interesting there. When it's created it's passed the root resource for the site.&lt;br /&gt;&lt;br /&gt;A resource, at its simplest, is something callable that takes a http.Request instance as its only arg and returns a http.Response instance. You can build a http.Response yourself but the http module provides some response factories to simplify application code and save a bit of typing.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The Resource class&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;from restish import app, http, resource&lt;br /&gt;    &lt;br /&gt;class HelloWorld(resource.Resource):&lt;br /&gt;    def __call__(self, request):&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], 'Hello, world!')&lt;br /&gt;    &lt;br /&gt;root_resource = HelloWorld()&lt;br /&gt;application = app.RestishApp(root_resource)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;You weren't really expecting anything interesting so soon were you? ;-)&lt;br /&gt;&lt;br /&gt;Most of the time using a function as a resource is too limiting so restish provides a Resource class. It has some magic abilities as we'll see later but, just like the hello_world(request) function above, it's basically something callable.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Request parameters&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;from restish import app, http, resource&lt;br /&gt;    &lt;br /&gt;class Users(resource.Resource):&lt;br /&gt;    def __call__(self, request):&lt;br /&gt;        username = request.GET.get('username') or 'anonymous'&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], 'Hello, %s!'%(username,))&lt;br /&gt;    &lt;br /&gt;root_resource = Users()&lt;br /&gt;application = app.RestishApp(root_resource)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;I hope noone's impressed by that code. In fact, I'm not even going to describe it but would like to point out that passing the username as a URL segment is almost certainly a nicer way to do things. So, moving swifly on ...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Resource children&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;from restish import app, http, resource&lt;br /&gt;    &lt;br /&gt;class Users(resource.Resource):&lt;br /&gt;    &lt;br /&gt;    def __call__(self, request):&lt;br /&gt;        doc = "matt: %s" % (request.path.child('matt'),)&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], doc)&lt;br /&gt;    &lt;br /&gt;    @resource.child()&lt;br /&gt;    def matt(self, request, segments):&lt;br /&gt;        return user&lt;br /&gt;    &lt;br /&gt;def user(request):&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], 'Hello, matt!')&lt;br /&gt;    &lt;br /&gt;root_resource = Users()&lt;br /&gt;application = app.RestishApp(root_resource)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The Users resource (the root of the site) returns a document that looks like, "matt: /matt", where "/matt" is the URL of the "matt" resource. Notice how the URL for the matt resource is created? 'request.path' is a url.URL instance - a smart string that knows how to parse and manipulate URLs, e.g. by adding a child segment. http.Request has a few URL instance attributes.&lt;br /&gt;&lt;br /&gt;The 'matt' method has a @child decorator to expose it as a child resource factory. By default @child() uses the name of the decorated method as the name of the segment it matches so here it will be called to create a resource for the 'matt' child, i.e the thing at the URL '/matt'.&lt;br /&gt;&lt;br /&gt;(You can pass an explicit segment name to @child instead, e.g. @child('matt'), allowing you to call your method whatever you want.)&lt;br /&gt;&lt;br /&gt;Statically-named children are useful and quite common but dynamically-named children are more interesting.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Dynamically named resource children&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;from restish import app, http, resource&lt;br /&gt;    &lt;br /&gt;USERS = ['alice', 'matt', 'rebecca']&lt;br /&gt;    &lt;br /&gt;class Users(resource.Resource):&lt;br /&gt;    &lt;br /&gt;    def __call__(self, request):&lt;br /&gt;        doc = '\n'.join(['%s: %s' % (username, request.path.child(username)) \&lt;br /&gt;                         for username in USERS])&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], doc)&lt;br /&gt;&lt;br /&gt;    @resource.child('{username}')&lt;br /&gt;    def child_user(self, request, segments, username):&lt;br /&gt;        if username in USERS:&lt;br /&gt;            return User(username)&lt;br /&gt;    &lt;br /&gt;class User(resource.Resource):&lt;br /&gt;&lt;br /&gt;    def __init__(self, username):&lt;br /&gt;        self.username = username&lt;br /&gt;    &lt;br /&gt;    def __call__(self, request):&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], 'Hello, %s!'%(self.username,))&lt;br /&gt;    &lt;br /&gt;root_resource = Users()&lt;br /&gt;application = app.RestishApp(root_resource)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;We now have a "database" of users. OK, so it's just a list of username but you get the idea. The User resource returns a document containing a list of users each with their URL.&lt;br /&gt;&lt;br /&gt;This time, the @child decorator has been passed a segment match template. '{username}' means match a single URL segment, extract the segment and pass it to the method as the username keyword arg.&lt;br /&gt;&lt;br /&gt;The child_user method returns a User resource instance, giving it the username the resource represents, or None to signal a 404.&lt;br /&gt;&lt;br /&gt;If you think the User class is bit "heavy" then, no problem, use a partial function instead, or a lambda it you prefer:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;class Users(resource.Resource):&lt;br /&gt;    [...]&lt;br /&gt;    @resource.child('{username}')                     &lt;br /&gt;    def child_user(self, request, segments, username):&lt;br /&gt;        if username in USERS:    &lt;br /&gt;            return functools.partial(user, username)&lt;br /&gt;                              &lt;br /&gt;def user(username, request):&lt;br /&gt;    return http.ok([('Content-Type', 'text/plain')], 'Hello, %s!'%(username,))&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Request methods&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;So far, every resource would respond in exactly the same way for all HTTP methods. It doesn't differentiate between GET, POST, PUT, DELETE, etc. Let's fix that now.&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;from restish import app, http, resource&lt;br /&gt;&lt;br /&gt;USERS = ['alice', 'matt', 'rebecca']&lt;br /&gt;&lt;br /&gt;class Users(resource.Resource):&lt;br /&gt;&lt;br /&gt;    @resource.GET()&lt;br /&gt;    def text(self, request):&lt;br /&gt;        doc = '\n'.join(['%s: %s' % (username, request.path.child(username)) \&lt;br /&gt;                         for username in USERS])&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], doc)&lt;br /&gt;    &lt;br /&gt;    @resource.child('{username}')&lt;br /&gt;    def child_user(self, request, segments, username):&lt;br /&gt;        if username in USERS:&lt;br /&gt;            return User(username)&lt;br /&gt;&lt;br /&gt;class User(resource.Resource):&lt;br /&gt;    &lt;br /&gt;    def __init__(self, username):&lt;br /&gt;        self.username = username&lt;br /&gt;    &lt;br /&gt;    @resource.GET()&lt;br /&gt;    def text(self, request):&lt;br /&gt;        return http.ok([('Content-Type', 'text/plain')], 'Hello, %s!'%(self.username,))&lt;br /&gt;    &lt;br /&gt;root_resource = Users()&lt;br /&gt;application = app.RestishApp(root_resource)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The only difference here is that we've replaced the resource's __call__ method with a nicely named method decorated with @resource.GET(). Now the resources only respond to a HTTP GET; anything else returns a "405 Method Not Allowed" response.&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;$ curl -X GET http://localhost:8080/&lt;br /&gt;alice: /alice&lt;br /&gt;matt: /matt&lt;br /&gt;rebecca: /rebecca&lt;br /&gt;$ curl -X POST http://localhost:8080/&lt;br /&gt;405 Method Not Allowed&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;I've actually just sneakily introduced some content negotation too. Not only does @resource.GET() match the HTTP method but it also matches the request's "Accept" header. However, GET defaults to an "Accept" match '*/*', i.e. any content type the client asks for.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Content negotiation ... at last&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I mentioned above that decorating with @GET also performs '*/*' content negotiation. We can easily configure a resource to handle requests for different content types.&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;import simplejson&lt;br /&gt;from restish import app, http, resource&lt;br /&gt;    &lt;br /&gt;USERS = ['alice', 'matt', 'rebecca']&lt;br /&gt;    &lt;br /&gt;class Users(resource.Resource):&lt;br /&gt;    &lt;br /&gt;    @resource.GET(accept='text/plain')&lt;br /&gt;    def text(self, request):&lt;br /&gt;        doc = '\n'.join(['%s: %s' % (username, request.path.child(username)) \&lt;br /&gt;                         for username in USERS])&lt;br /&gt;        return http.ok([], doc)&lt;br /&gt;&lt;br /&gt;    @resource.GET(accept='application/json')&lt;br /&gt;    def json(self, request):&lt;br /&gt;        users = [{'username': username, 'url': request.path.child(username)} \&lt;br /&gt;                 for username in USERS]&lt;br /&gt;        return http.ok([], simplejson.dumps(users))&lt;br /&gt;&lt;br /&gt;    @resource.child('{username}')&lt;br /&gt;    def child_user(self, request, segments, username):&lt;br /&gt;        if username in USERS:&lt;br /&gt;            return User(username)&lt;br /&gt;&lt;br /&gt;class User(resource.Resource):&lt;br /&gt;    &lt;br /&gt;    def __init__(self, username):&lt;br /&gt;        self.username = username&lt;br /&gt;&lt;br /&gt;    @resource.GET(accept='text')&lt;br /&gt;    def text(self, request):&lt;br /&gt;        return http.ok([], 'Hello, %s!'%(self.username,))&lt;br /&gt;&lt;br /&gt;    @resource.GET(accept='json')&lt;br /&gt;    def json(self, request):&lt;br /&gt;        doc = simplejson.dumps({'username': self.username, 'url': request.path})&lt;br /&gt;        return http.ok([], doc)&lt;br /&gt;    &lt;br /&gt;root_resource = Users()&lt;br /&gt;application = app.RestishApp(root_resource)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;This time we have 'text' and 'json' methods, decorated with @GET(accept='text/plain') and @GET('application/json') respectively. Now we have a resource that will look at the Accept header, find the best matching method and call it. No match results in a "406 Not Acceptable" error.&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&lt;br /&gt;$ curl -H "Accept: text/plain" http://localhost:8080/&lt;br /&gt;alice: /alice&lt;br /&gt;matt: /matt&lt;br /&gt;rebecca: /rebecca&lt;br /&gt;$ curl -H "Accept: application/json" http://localhost:8080/&lt;br /&gt;[{"username": "alice", "url": "/alice"}, {"username": "matt", "url": "/matt"}, {"username": "rebecca", "url": "/rebecca"}]&lt;br /&gt;$ curl -H "Accept: text/html" http://localhost:8080/&lt;br /&gt;406 Not Acceptable&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Note that the resource no longer has to specify Content-Type headers. That's because the Accept matching process knows what it found and fills it in for you ... how kind :). (Don't worry, you can still include the Content-Type in the response headers if you want to handle it yourself.)&lt;br /&gt;&lt;br /&gt;Note also that the User resource uses shorthand in the form of @GET(accept='text') and @GET(accept='json'). They're expanded to the full MIME type on your behalf and so work just the same. Frankly, typing 'application/json' is tedious and 'application/xhtml+xml' is perverse ;-).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Well, that's all for now although there's a few other things I wanted to mention. A quick list will have to do for now:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;wildcard accept matching, e.g. 'image/*'&lt;/li&gt;&lt;br /&gt;&lt;li&gt;PUT, POST, DELETE, etc&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Content-Type header matching (basically the same as Accept matching but for data sent from the client&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Handling multiple content types with one method, e.g. @GET(accept=['html', 'xhtml'])&lt;/li&gt;&lt;br /&gt;&lt;li&gt;@child URL matching in general&lt;/li&gt;&lt;br /&gt;&lt;li&gt;@child that matches any URL&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Consuming additional URL segments during traversal&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Hope someone finds this post interesting!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-4014759663997829136?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/4014759663997829136/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=4014759663997829136' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/4014759663997829136'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/4014759663997829136'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/01/restish-resources-from-ground-up.html' title='restish resources ... from the ground up'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-5823096688793478886</id><published>2009-01-03T00:48:00.003Z</published><updated>2009-01-03T01:29:09.959Z</updated><title type='text'>The Royal Institution Christmas Lectures</title><content type='html'>I really enjoyed watching the &lt;a href="http://www.rigb.org/christmaslectures08/"&gt;Royal Institution Christmas Lectures&lt;/a&gt;, presented by &lt;a href="http://research.microsoft.com/en-us/um/people/cmbishop/"&gt;Chris Bishop&lt;/a&gt;. I don't think I've actually watched them properly since I was a kid!&lt;br /&gt;&lt;br /&gt;We sat down as a family every night to watch and it clearly sparked some interest from the kids. We've had the cover off the computer looking at its innards, we've been playing with &lt;a href="http://www.phunland.com/"&gt;Phun&lt;/a&gt; (the kids remembered it after seeing the multi-touch displays), we played with &lt;a href="http://www.gnome.org/"&gt;GNOME&lt;/a&gt;'s &lt;a href="http://en.wikipedia.org/wiki/Dasher"&gt;Dasher&lt;/a&gt;. Heck, we even touched on public key encryption.&lt;br /&gt;&lt;br /&gt;Sure, there were a couple of bits that weren't so good. In particualr Bill Gates was seriously dull and the programme on software, The Ghost in the Machine, was not exactly great (the kids still don't know what I do ;-)) but over all, fantastic.&lt;br /&gt;&lt;br /&gt;If only all TV was that interesting!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-5823096688793478886?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/5823096688793478886/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=5823096688793478886' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/5823096688793478886'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/5823096688793478886'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/01/royal-institution-christmas-lectures.html' title='The Royal Institution Christmas Lectures'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-7174193135469499177</id><published>2009-01-03T00:11:00.004Z</published><updated>2009-01-03T00:44:27.128Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='rest'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>GET and idempotence</title><content type='html'>I was reminded today of what seems to be a common misunderstanding of the idempotent requirements of a GET in a REST-ful architecture. GET doesn't mean the response must be the same each time; only that it must have no side effects, i.e. it should never cause the server's state to change.&lt;br /&gt;&lt;br /&gt;For instance, CouchDB includes a resource, /_uuids, that returns a number of server-generated UUIDs. As far as I know, it only exists to support languages without a decent UUID library. It has no effect on the server.&lt;br /&gt;&lt;br /&gt;However, CouchDB will only respond when /_uuids is POST'ed to:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ curl -X GET http://localhost:5984/_uuids?count=2&lt;br /&gt;$ curl -X POST http://localhost:5984/_uuids?count=2&lt;br /&gt;{"uuids":["d267c530c9591eeeed72f589fcef5599","f4c12de5147ec97e7a770440cc7a69f2"]}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;One suggestion on the mailing list (although not from one of CouchDB's core developers) for the use of POST is to, "comply with REST as it returns a different output each time".&lt;br /&gt;&lt;br /&gt;A GET would be just fine here. In fact, it would be more in keeping with the intended use of the HTTP methods.&lt;br /&gt;&lt;br /&gt;It's easy to come up with examples of a REST-ful resource that sends a different response every request, with probably the most obvious being some sort of time server. Consider the following URLs:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;/time&lt;/li&gt;&lt;li&gt;/time/BST&lt;/li&gt;&lt;li&gt;/time/EST&lt;/li&gt;&lt;li&gt;etc&lt;/li&gt;&lt;/ul&gt;You would GET the current time from an appropriate resource and I sure hope there will be a different response each time ;-). You could also PUT a time to one of those resource to set the time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-7174193135469499177?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/7174193135469499177/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=7174193135469499177' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/7174193135469499177'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/7174193135469499177'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2009/01/get-and-idempotence.html' title='GET and idempotence'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-8484969692067930764</id><published>2008-12-17T23:11:00.003Z</published><updated>2009-01-03T01:29:39.885Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='funny'/><title type='text'>Flow Charts (from xkcd)</title><content type='html'>&lt;a href="http://xkcd.com/"&gt;xkcd&lt;/a&gt; is so often amusing but occassionally they post something that quite literally makes me laugh out loud. Today, is one of those times ...&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://imgs.xkcd.com/comics/flow_charts.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 740px; height: 534px;" src="http://imgs.xkcd.com/comics/flow_charts.png" alt="" border="0" /&gt;&lt;/a&gt;(from &lt;a href="http://xkcd.com/518/"&gt;http://xkcd.com/518/&lt;/a&gt;)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-8484969692067930764?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/8484969692067930764/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=8484969692067930764' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8484969692067930764'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8484969692067930764'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/12/flow-charts-from-xkcd.html' title='Flow Charts (from xkcd)'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-8759109491718414603</id><published>2008-12-05T19:21:00.004Z</published><updated>2008-12-05T20:18:43.774Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>Selenium, give me back my desktop!</title><content type='html'>&lt;a href="http://selenium.seleniumhq.org/"&gt;Selenium&lt;/a&gt; is a great tool for testing web applications but, when running it locally, it does tend to take over your desktop with Firefox windows popping up constantly, leaving you twiddling your thumbs until the test suite is finished.&lt;br /&gt;&lt;br /&gt;A beautifully lightweight and simple solution on Linux is to display the Firefox windows under test in an &lt;a href="http://en.wikipedia.org/wiki/Xnest"&gt;Xnest&lt;/a&gt; window.&lt;br /&gt;&lt;br /&gt;So, here's my Selenium server startup script:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# Configure the display we'll run Xnest on and the location of the Firefox&lt;br /&gt;# binary.&lt;br /&gt;XNEST_DISPLAY=:10&lt;br /&gt;FIREFOX_BIN="/usr/lib/firefox-3.0.4/firefox"&lt;br /&gt;&lt;br /&gt;# Start Xnest and capture its pid.&lt;br /&gt;Xnest $XNEST_DISPLAY -ac &amp;&lt;br /&gt;xnest_pid=$!&lt;br /&gt;&lt;br /&gt;# From now on we want all X11 apps to start on Xnest's display.&lt;br /&gt;DISPLAY=$XNEST_DISPLAY&lt;br /&gt;&lt;br /&gt;# Let's run a nice, simple window manager on it.&lt;br /&gt;openbox-session &amp;&lt;br /&gt;&lt;br /&gt;# Run the selenium server.&lt;br /&gt;java -jar selenium-server.jar -forcedBrowserMode "*firefox3 $FIREFOX_BIN"&lt;br /&gt;&lt;br /&gt;# Close the Xnest session.&lt;br /&gt;kill $xnest_pid&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-8759109491718414603?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/8759109491718414603/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=8759109491718414603' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8759109491718414603'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8759109491718414603'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/12/selenium-give-me-back-my-desktop.html' title='Selenium, give me back my desktop!'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-4323468489903672169</id><published>2008-04-19T22:07:00.000+01:00</published><updated>2008-04-20T22:11:55.273+01:00</updated><title type='text'>Doctor Who Confidential</title><content type='html'>Pah, it's nothing to do with a, "Behind-the-scenes look at the making of Doctor Who". It exists purely to show my 4 year old that the nasty monsters aren't actually real so they can sleep at night. ;-).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-4323468489903672169?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/4323468489903672169/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=4323468489903672169' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/4323468489903672169'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/4323468489903672169'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/04/doctor-who-confidential.html' title='Doctor Who Confidential'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-5556324457439077724</id><published>2008-04-05T00:01:00.002+01:00</published><updated>2008-04-05T00:22:15.234+01:00</updated><title type='text'>Adobe (Hot) Air</title><content type='html'>Came across this fantastic statement in some &lt;a href="http://www.viddler.com/explore/Raferx2/videos/10/"&gt;Adobe marketing video&lt;/a&gt;, :&lt;br /&gt;&lt;blockquote&gt;"So, it's actually the first time that you've ever been able to double-click on an FLV file on your desktop and actually watch it. Before you've had to basically watch Flash video through the browser."&lt;/blockquote&gt;Silly me. I can't possibly have been able to play FLV files on my computer for quite some time and &lt;a href="http://ffmpeg.mplayerhq.hu/projects.html"&gt;all&lt;/a&gt; &lt;a href="http://en.wikipedia.org/wiki/Flv#FLV_players"&gt;these&lt;/a&gt; &lt;a href="http://www.google.co.uk/search?q=standalone+flv+player"&gt;projects&lt;/a&gt; must be completely deluded too.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-5556324457439077724?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/5556324457439077724/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=5556324457439077724' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/5556324457439077724'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/5556324457439077724'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/04/adobe-hot-air.html' title='Adobe (Hot) Air'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-8497848911623124574</id><published>2008-03-11T22:53:00.005Z</published><updated>2008-03-11T23:06:53.087Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='aikido'/><title type='text'>2nd Kyu, Aikido</title><content type='html'>Yay, I just got 2nd Kyu &lt;a href="http://en.wikipedia.org/wiki/Tomiki_aikido"&gt;Aikido&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;I know I made a number of mistakes (like not avoiding and getting a tanto in the stomach :-/) but I obviously did well enough.&lt;br /&gt;&lt;br /&gt;Recently I've been practicing randori-no-kata, shichi-hon-no-kuzushi, randori-no-kata-no-ura-waza and koryu-dai-san; all a long way from perfect, of course. And what did they ask me to do .... left-handed Randori-no-kata, tanto hikitategeiko and some free play. Oh well, it's all good stuff.&lt;br /&gt;&lt;br /&gt;The general comments about the grading, and I know some apply to me, was that it needed to be more flowing with more movement and "life".&lt;br /&gt;&lt;br /&gt;Comments on my personal performance was that I was getting a reasonable amount of movement in the tanto hikitategeiko, and that my kata was quite good. They obviously weren't watching the whole time ;-).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-8497848911623124574?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/8497848911623124574/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=8497848911623124574' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8497848911623124574'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8497848911623124574'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/03/2nd-kyu-aikido.html' title='2nd Kyu, Aikido'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-3341616486510218310</id><published>2008-02-27T21:00:00.006Z</published><updated>2008-02-27T22:41:59.503Z</updated><title type='text'>Virding's First Rule of Programming</title><content type='html'>"Any sufficiently complicated concurrent program in another language contains an ad hoc informally-specified bug-ridden slow implementation of half of Erlang."&lt;br /&gt;&lt;br /&gt;-- &lt;a href="http://www.erlang.org/pipermail/erlang-questions/2008-January/032224.html"&gt;Virding's First Rule of Programming&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I'm really no Erlang expert but, from what I've seen so far, there's a fair amount of truth to that statement.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-3341616486510218310?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/3341616486510218310/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=3341616486510218310' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/3341616486510218310'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/3341616486510218310'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/02/any-sufficiently-complicated-concurrent.html' title='Virding&apos;s First Rule of Programming'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-3344934371554856149</id><published>2008-02-27T01:04:00.002Z</published><updated>2008-02-27T01:16:01.794Z</updated><title type='text'>Earthquake rocks house ... a bit</title><content type='html'>Just sitting here playing around with &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; and Mnesia's database replication when I felt what can only have been an earthquake.&lt;br /&gt;&lt;br /&gt;It wasn't very big (this is the UK, after all) but it made the house shudder quite strongly.&lt;br /&gt;&lt;br /&gt;I've felt one here before. That was more like a very low rumble, quite a strange sensation, but I don't remember it making the house move to the same extent as this one.&lt;br /&gt;&lt;br /&gt;I tried to check with the &lt;a href="http://www.earthquakes.bgs.ac.uk/"&gt;British Geological Survey Seismology Home Page&lt;/a&gt; but it seems to be down at the moment. Have to try again in the morning.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-3344934371554856149?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/3344934371554856149/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=3344934371554856149' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/3344934371554856149'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/3344934371554856149'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/02/earthquake-rocks-house-bit.html' title='Earthquake rocks house ... a bit'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-8025058087329073936</id><published>2008-02-26T17:32:00.004Z</published><updated>2008-02-26T17:42:19.027Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='openid'/><title type='text'>One site account, multiple OpenID identities</title><content type='html'>If you're adding OpenID to your site then please allow your users to add as many of their OpenID identities as they want to their account.&lt;br /&gt;&lt;br /&gt;I've got a few identities I use so adding a couple to sites that are important to me means I'm not reliant on a single OpenID provider being available when I need it. It's a simple form of fault tolerance.&lt;br /&gt;&lt;br /&gt;Multiple identities can also be a way to allow an account on a particular site to be shared by multiple people, each with their own authentication credentials.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-8025058087329073936?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/8025058087329073936/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=8025058087329073936' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8025058087329073936'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8025058087329073936'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/02/one-site-account-multiple-openid.html' title='One site account, multiple OpenID identities'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-8773251827202113790</id><published>2008-02-24T16:45:00.002Z</published><updated>2008-02-26T09:02:36.064Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='music'/><category scheme='http://www.blogger.com/atom/ns#' term='funny'/><title type='text'>Get your MP3s now, last few left</title><content type='html'>Spotted this on play.com.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_9dLYE8ZcSow/R8GZ1Ktd5EI/AAAAAAAAAJE/o3lWN_HHG4E/s1600-h/preorder+mp3+download.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_9dLYE8ZcSow/R8GZ1Ktd5EI/AAAAAAAAAJE/o3lWN_HHG4E/s320/preorder+mp3+download.png" alt="" id="BLOGGER_PHOTO_ID_5170582985885213762" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Erm ... but ... why? Are they going to run out of them or something ;-) ?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-8773251827202113790?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/8773251827202113790/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=8773251827202113790' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8773251827202113790'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/8773251827202113790'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/02/get-your-mp3s-now-last-few-left.html' title='Get your MP3s now, last few left'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_9dLYE8ZcSow/R8GZ1Ktd5EI/AAAAAAAAAJE/o3lWN_HHG4E/s72-c/preorder+mp3+download.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624721007426248377.post-5850889323037300630</id><published>2008-02-24T16:00:00.003Z</published><updated>2008-02-26T17:41:58.933Z</updated><title type='text'>Perhaps this time I'll keep it up</title><content type='html'>I've kept various forms of blog over the years from personal "just to keep some notes" to a company-related journal. I've been hopeless at posting regularly to all of them and, as a result, they have withered and died.&lt;br /&gt;&lt;br /&gt;The whole corporate journal thing really doesn't appeal to me much anyway. If I'm going to post to a blog then I really don't want to have to think too hard about it. I've also always felt very uncomfortable posting anything of personal interest to it for fear of "losing" it if I ever change job.&lt;br /&gt;&lt;br /&gt;I doubt this blog will do much better (I've not even convinced myself that I want to use blogger.com yet) but it really is about time I posted about stuff I'm interested in, stuff I play with, etc.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624721007426248377-5850889323037300630?l=matt.goodall.me' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://matt.goodall.me/feeds/5850889323037300630/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8624721007426248377&amp;postID=5850889323037300630' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/5850889323037300630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624721007426248377/posts/default/5850889323037300630'/><link rel='alternate' type='text/html' href='http://matt.goodall.me/2008/02/perhaps-this-time-ill-keep-it-up.html' title='Perhaps this time I&apos;ll keep it up'/><author><name>Matt Goodall</name><uri>https://profiles.google.com/113886470075752902605</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-ebtqlvB7kEo/AAAAAAAAAAI/AAAAAAAAA6Y/OnOD6-IpzHE/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
