talideon.com

Coding for fun and prophet!

Entries for May 2008

May 5, 2008 at 11:14PM IsaacBrowser IV, release 2

[Updates since this one: Release 3]

After I uploaded the last release, I discovered IsaacBrowser III has came back to life with the addition of a directory archive download. I decided to add the same functionality to my fork, though I’m going changing it to use a different method as the current one runs out of memory far too easily.

The latest version also comes with a number of other changes, most of which are inconsequential to most users, such as removing horizontal thumbnail padding (smaller thumbnails!), various internal refactorings to remove redundancy, better 404 handling, proper PHP 4 compatibility, proper Content-Type headings for the pages, better caching for internally embedded files, changing how CSS is handled from embedding it in the page header to making it an embedded file (so it only needs to be pulled down once), and a scatter of other things.

However, from the user’s perspective, the main additions are that you can now tweak thumbnail dimensions, the name of the cache directory, and you can now allow people viewing your site to download directory archives. That said, even if you don’t want the directory download functionality, it’s worth it for all the internal change that have been made. Go on, you know it’s right!

[download; demo]

May 6, 2008 at 1:32PM Oh, Jeebus! My head’s about to explode!

Ignorant of the settings Michele had set up on the new espresso machine in work, I accidentally ended up making myself a quadruple espresso. Tasty and all as it was, I’m hurting now!

May 6, 2008 at 2:50PM EPP domain nameserver update suckage

One of the things I really dislike about EPP is the demented method its domain mapping uses for updating the nameservers associated with a domain.

Rather than just specifying the nameservers directly, it requires you to specify the ones you want to remove from the current list of nameservers associated, and the ones to add. I imagine that when this ‘feature’ was added, the excuse used was update efficiency, but considering that it requires the caller to have a pretty freshly minted list of the domain’s nameservers, which requires a fairly recent <domain:info/> call to avoid a malformed update, I really can’t see what possessed them to do things this way. That’s an extra call from a registrar’s EPP backend to the registry’s EPP server, which might fail, or might be slow, and may introduce a race condition where the fetch and update both need to be retried because somebody’s updated the nameservers in the mean time.

You have to wonder, really.

May 12, 2008 at 11:57AM Ideas from odd places: a reasonable RPC envelope protocol, and asynchronous calls

Warning: This post is an extended geeky joke. If you’re not a geek, you definitely won’t get the point. If you are a geek, you still probably won’t get the point, but believe me, you’re better for it. [smile]

Sometimes something you do as a joke can yield interesting results.

About a year or so ago, I was feeling bored and decided to see how much of a diet I could put XML-RPC on as you do. Though I managed to slim it down quite a bit and produce a RELAX NG schema for the resulting format, it was all just a diversion and I only did it for fun. I let all of this sit on my harddrive festering away for ages until I stumbled across it again and decided to fiddle with the idea of a HTTP-derived joke protocol designed specifically for tunnelling the likes of XML-RPC.

SRGP, a HTTP pod-person for RPC

I called the protocol SRGP, or Simplified RPC Gateway Protocol, and it was intended to be a pod-person equivalent of HTTP. It looked like HTTP, behaved a lot like HTTP, but there’s something wrong about it.

It had a Winer-esque specification that didn’t go to much effort to rigourously outline the workings of the protocol, and made lots of undocumented assumptions, all of which were meant to lead to subtly incompatible client and server implementations. Because it’s a pod-person protocol, it could be implemented on top of a HTTP server with little effort.

It was too late for April Fool’s Day, so I never published the spec, but the process of writing it did lead me down an interesting path.

An overview of SRGP

SRGP looks like HTTP, however it’s subtly different, though not quite incompatible. The semantics of GET and POST change slightly. A GET request on an endpoint returns a service document or IDL description for that endpoint describing what it exposes. A POST request is used for sending a payload representing a remote procedure call to the endpoint.

Requests and responses are of the same form as their HTTP equivalents, but the number of headers SRGP has been trimmed down. It still understands Content-Type, Accept-Encoding, Content-Encoding, User-Agent, Server, Connection, Host, Location, Content-Length, Retry-After, and Date. These headers mean more or less the same thing as their HTTP equivalents.

It also introduces two header elements of its own: Reference and Token, but I’ll get to those in a tick.

The response status codes are trimmed down. Their semantics are close to the HTTP originals:

200 OK
202 Accepted
301 Moved Permanently
400 Bad Request
403 Forbidden
404 Bad Endpoint
405 Bad Request Type
500 Internal Server Error
503 Service Unavailable
505 Protocol Version Not Supported

200 implies that the request was successfully dispatched, but not necessarily that it was successfully processed. After all, it can contain a fault response. The headers accompanying it are Content-Type and Date, and if they were provided with the request, Reference and Token will be returned with the response too. The response might also include the Content-Encoding, Server, and Connection responses.

202 implies that the request has been accepted but not yet processed, and the body may contain a plaintext explanation of why. Other than that, it’s just like a 200 response, but will always be accompanied by a Reference header so that the request can be retried at a later point. If none accompanied the request, the server generates its own. The response also contains a Retry-After header so the client knows the polling interval to use to retry the request to check if it’s been processed.

The point behind the Reference header is to guarantee call idempotency. Lack of the header implies that the client doesn’t particularly care if the is serviced one or many times, just as long as it get serviced. The presence of the Reference header in a request is the client’s way of saying that it wants the call to be serviced once and one only, and that any number of calls with the same reference should be treated as a single call. To this extent, it’s a bit like a HTTP client including an ETag in a POST request. The server should maintain reference affinity with the client that generated the reference in the first place, so that so clients can generate the same reference without the collision causing security and other problems. How affinity might work, I’m not sure.

Considering I’ve covered Reference, I’d might as well talk about Token. If Cookie is the state management equivalent of somebody with half their brain removed, Token is the equivalent of just leaving behind the brain stem. This header, when used in a request, can contain one of two things: ‘new’, or ‘v=”token”’, where token is a string that acts as a session identifier. Token: new is the client’s way of saying they want to start a new session. The server responds with Token: v=”token, and the client sends that back with each request that’s meant to be part of the session. Session state is unique to individual endpoints, not to the host. A session times out after 30 minutes without a request from the client, but other than that, it’s up to the server to decide how it manages session state. References are not tied to sessions, nor the reverse. It’s nicely stupid. Token is a whoopee cushion and a server or client may not even implement it.

301, 400, 403, 404, 405, 500, 503, and 505 all do what you might guess from familiarity with HTTP. 503 is notable though, so I think it’s worth mentioning here. It means that the gateway server or endpoint is temporarily down, so the client should try again later. The body can contain a plaintext explanation of why it’s down, and the response will include a Retry-After header so the client knows what amount of time to retry the original request after.

The interesting diversion

The existence of SRGP is rather silly, though not entirely impractical. An implementation of it would be simpler than an implementation of HTTP, and it’s somewhat more useful than tunnelling RPC over POST.

It does include at least two interesting and connected ideas: the Reference header and the repurposing of the 202 response code. The existence of these makes it possible for a server to process calls asynchronously, which is something tunnelling RPC over POST doesn’t let you do.

But now that calls can be processed asynchronously by the server, what about the client? To get a response, it needs to poll the server for the response. POST can be used, but it’s not a great fit, we’ll introduce a new verb, POLL, which is to POST in SRGP as HEAD is to GET in HTTP. The difference is that POLL is always accompanied by a Reference header, and never has a body. This allows the client to poll for an asynchronous response without the overhead of a possibly expensive to calculate or large request body. Other than that, it’s behaviour is identical to POST.

It’s sometimes better for both the server and client to instead have the server notify the client that the request’s been processed. With that in mind, I decided to introduce one more verb and a header: NOTIFY and Notify respectively.

A POST that includes a Notify header must always include a Reference header. The presence of this header states that if the call is processed asynchronously, the server will send a NOTIFY request to the endpoint specified by the Notify header containing the response. The server indicates its understanding of the Notify header by repeating the Notify header in the 202 response headers verbatim. Neither clients nor servers can expect their opposite to implement Notify support and should fall back on polling if unimplemented.

When the server has processed the asynchronous request, it sends a NOTIFY request to the endpoint specified in the original request. The request contains the same headers and content as a polled 200 response would. The only valid 2xx code a notification endpoint can respond with is 200. 503 and 301 responses are processed as you’d expect. A 400 response to valid request, or a 403, 404, 405, 505 response should be handled by an angry phonecall or email to the entity who made the original request to fix their software. A 500 response should be handled by a polite phonecall or email asking them to fix their software.

So, what’s interesting about this?

With very little effort, we’ve managed to add a pretty flexible and, more to the point, useful asynchronous call mechanism on top of SRGP.

Background: my XML-RPC reworking

Back to the XML-RPC reworking. It’s been done many times before, but about a year ago I decided to create a schema that mapped almost one-to-one onto that of XML-RPC, but without the bloat. I did this for fun and certainly never expected that anybody would ever seriously consider implementing anything to process it.

Here’s the sample response to a metaWeblog.getPost call:

<?xml version="1.0"?>
<methodResponse>
  <params>
    <param>
      <value><struct>
    <member>
      <name>categories</name>
      <value>
        <array>
          <data>
            <value>Michegas</value>
            <value>Mind Bombs</value>
            <value>Rest &amp; Relaxation</value>
            <value>Two-Way-Web</value>
            </data>
          </array>
        </value>
      </member>
    <member>
      <name>dateCreated</name>
      <value>
        <dateTime.iso8601>20030729T10:59:48</dateTime.iso8601>
        </value>
      </member>
    <member>
      <name>description</name>
      <value>Blogger Ed Cone of Greensboro talks about the several
        intersections he overlooks.&amp;nbsp; That is: junctions
        of the public and the personal (which every blogger faces)
        and more particularly the contrasting voices of a
        newspaper columnist and a blogger (he is both) and the
        opportunities for a local conversation in a global medium.</value>
      </member>
    <member>
      <name>enclosure</name>
      <value>
        <struct>
          <member>
            <name>length</name>
            <value>
              <i4>11421281</i4>
              </value>
            </member>
          <member>
            <name>type</name>
            <value>audio/mpeg</value>
            </member>
          <member>
            <name>url</name>
            <value>http://media.skybuilders.com/lydon/cone.mp3</value>
            </member>
          </struct>
        </value>
      </member>
    <member>
      <name>link</name>
      <value>http://blogs.law.harvard.edu/lydon/2003/07/18#a187</value>
      </member>
    <member>
      <name>permaLink</name>
      <value>http://radio.weblogs.com/0001015/2003/07/29.html#a1829</value>
      </member>
    <member>
      <name>postid</name>
      <value>
        <i4>1829</i4>
        </value>
      </member>
    <member>
      <name>title</name>
      <value>Chris Lydon interview with Ed Cone</value>
      </member>
    <member>
      <name>userid</name>
      <value>
        <i4>1015</i4>
        </value>
      </member>
    </struct></value>
      </param>
    </params>
  </methodResponse>

The more I look at an XML-RPC request or response, the more it looks to me like an elaborate practical joke.

Here’s that same response in my trimmed down schema:

<?xml version="1.0"?>
<response>
  <map>
    <array key="categories">
      <string>Michegas</string>
      <string>Mind Bombs</string>
      <string>Rest &amp; Relaxation</string>
      <string>Two-Way-Web</string>
    </array>
    <date key="dateAdded">2003-07-29T10:59:48-05:00</date>
    <string key="description">
      Blogger Ed Cone of Greensboro talks about the several
      intersections he overlooks.&amp;nbsp; That is: junctions
      of the public and the personal (which every blogger faces)
      and more particularly the contrasting voices of a
      newspaper columnist and a blogger (he is both) and the
      opportunities for a local conversation in a global medium.
    </string>
    <map key="enclosure">
      <int key="length">11421281</int>
      <string key="type">audio/mpeg</string>
      <string key="url">
        http://media.skybuilders.com/lydon/cone.mp3
      </string>
    </map>
    <string key="link">
      http://blogs.law.harvard.edu/lydon/2003/07/18#a187
    </string>
    <string key="permaLink">
      http://radio.weblogs.com/0001015/2003/07/29.html#a1829
    </string>
    <string key="postid">1829</string>
    <string key="title">Chris Lydon interview with Ed Cone</string>
    <string key="userid">1015</string>
  </map>
</response>

There’s no way anybody can convince me that’s not better than the original.

As you can see, it didn’t take an awful lot of effort on my part to strip out all the junk to give something a lot clearer and more compact than what Dave Winer came up with. All it took was removing the redundant <value/>, <params/>, <param/>, and <data/> tags; not being afraid of attributes and using them as the were intended, which allowed me to get rid of the <member/>, <methodName/>, <member>, and <name/> elements, and trim down <fault/> considerably; I trimmed down the names, getting rid of the redundant <i4/> and changing <datetime.iso8601/> to <date/> because it’s more obvious and doesn’t include a pointless description of its encoding, <double/> to <float/> as that’s more familiar to the average person, <base64/> to <binary/> because that describes what it’s for rather than how it’s encoded, and <struct/> to <map/> because struct has the implication of ordering, whereas map doesn’t.

Here’s the RELAX-NG compact schema:

default namespace = "http://talideon.com/projects/schemas/xpc/v1/"
start = xpcCall | xpcResponse | xpcFault

xpcMethodName = xsd:string {
  pattern = "[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*"
}

xpcCall = element call {
  attribute method { xpcMethodName },
  xpcType*
}

xpcResponse = element response {
  xpcType?
}

xpcFault = element fault {
  attribute code { xsd:integer },
  text
}

xpcType =
  element nil     { empty } |
  element int     { xsd:integer } |
  element boolean { xsd:boolean } |
  element string  { text } |
  element float   { xsd:double } |
  element date    { xsd:dateTime } |
  element binary  { xsd:base64Binary } |
  element array   { xpcType* } |
  element map     { xpcMType* }

xpcKey = attribute key { text }
xpcMType =
  element nil     { xpcKey, empty } |
  element int     { xpcKey, xsd:integer } |
  element boolean { xpcKey, xsd:boolean } |
  element string  { xpcKey, text } |
  element float   { xpcKey, xsd:double } |
  element date    { xpcKey, xsd:dateTime } |
  element binary  { xpcKey, xsd:base64Binary } |
  element array   { xpcKey, xpcType* } |
  element map     { xpcKey, xpcMType* }

The type system’s extended slightly to include <nil/>, which represents the lack of a value. This is already a common extension. Method names now have a canonical form similar to that of method name in all the major languages though this is something I’m not terribly attached to. Because it has a schema, it’s rigourously defined, so a spec for this RPC system would never have required the Q&A section that accompanies the XML-RPC spec. Oh, and dates now require a timezone.

Update: For reference’s sake, there’s already a reworking of XML-RPC called XPC, which goes even further by combining all the scalar types together, so the mapping is one way. I don’t mind annotating the types, but I wanted to minimise the waste markup. I considered batch calls, but just never bothered.

May 13, 2008 at 1:19PM Why you should wait in the cinema to watch the Iron Man credits

Seriously, could this possibly get any more awesome? I don’t think so!

[via Rick O’Shea, via an IM from Elly]

May 13, 2008 at 1:40PM Iron and Wine - Boy with a Coin

And on the topic of things iron...

May 13, 2008 at 1:55PM tkdiff patches: BZR support and improved documentation formatting

Two quick hacks to tkdiff 4.1.4.

The first patch is basic Bazaar NG support. I’ve grown so accustomed to using tkdiff for viewing file diffs with subversion that I miss it when I’m using Bazaar NG. This patch adds it, but there’s still an edge case when you’re dealing with new files, not that it should matter terribly.

The second patch improves the documentation formatting, prettying it up a bit. Not an important patch, but still it’s worth committing, I think.

May 15, 2008 at 1:34PM Quick hacks: Python netstring reader

I’m writing a proof-of-concept server for a less jokey version of the SRGP. The protocol is more or less SRGP, but it doesn’t use MIME-style headers each request is made up of a series of netstrings. The header and body sections are terminated with empty netstrings, i.e., 0:,.

The proof-of-concept server is being written in Python. Here’s the netstring reader generator function, in case it might be of use to somebody else out there:

class MalformedNetstringError:
    pass

def netstring_reader(f):
    while True:
        n = ""
        while True:
            c = f.read(1)
            if c == '':
                return
            if c == ':':
                break
            if len(n) > 10:
                raise MalformedNetstringError
            if c == '0' and n == '':
                # We can't allow leading zeros.
                if f.read(1) != ':':
                    raise MalformedNetstringError
                n = c
                break
            n += c
        n = int(n, 10)
        payload = f.read(n)
        if len(payload) < n:
            return
        if f.read(1) != ',':
            raise MalformedNetstringError
        yield payload

netstring_reader() takes a file object and returns a generator that returns each netstring extracted from the file object. If the stream is malformed, a MalformedNetstringError is raised.

Currently the diagnostics are far from as good as they ought to be. It should state whether it’s a malformed length or a missing terminating comma, and the offset into the stream where the error occurred. There’s a few places where it can be made more robust, but expect that in later releases.

Here’s an example of it in use.

f = open("sample.dat", "r")
try:
    for i in netstring_reader(f):
        print i
finally:
    f.close()

May 19, 2008 at 10:19PM On the incomprehensibility of the Lisbon Treaty

I wrote a piece for Irish Election on the strawman argument that the the Lisbon Treaty is unreadable. I thought I’d reproduce it here:

I’m one of those people who hasn’t quite decided which way they’re going to vote on in the referendum. This is because I haven’t got around to reading all of the text I’m voting on yet. However, there’s one incredibly stupid argument the no camp is bandying about right now. It’s an argument that does an incredible amount of disservice to everybody. That argument is:

The Lisbon Treaty is an incomprehensible document, which is NOT readable in a linear way. It was NOT meant to be read by anyone, just to be blindly accepted. It is against basic common sense and against the sense of responsibility to sign any treaty or contract without fully knowing its contents and understanding its consequences.

So writes howardh (who I’m presuming is the Howard Holby who wrote this Indymedia article, which reads like he thinks the EU is one big conspiracy) of the The Lisbon Treaty Blog. I’ve heard this thrown about a lot, and I have to dispel it. Here’s an example he gives:

GENERAL PROVISIONS

64] Article 61 shall be replaced by the following Chapter 1 and Articles 61 to 61 I. Article 61 shall also replace the current Article 29 of the Treaty on European Union, Article 61 D shall replace Article 36 thereof, Article 61 E shall replace Article 64(1) of the Treaty establishing the European Community and the current Article 33 of the Treaty on European Union,

Yup, completely unreadable to the average person. Frankly, I can see how people would have difficultly reading this. However, to assume that this is what you’re meant to read is to confuse the map with the territory.

I’m a software developer, and the text of the Lisbon Treaty is what we call a diff. Diffs are useful and describe in succinct terms the changes between two different version of the same piece of software. It’s not surprising that the legal profession and the software development profession both came up with the same idea: we both deal with large reams of text, we both need to be able to trace the change in those reams of text, and need to be able to distribute those change in a manner that shows the changes explicitly. However, the diffs themselves are rarely read in isolation. So it is with the Treaty.

When you’re voting on the Lisbon Treaty, you’re voting on a whole bunch of changes to existing treaties to consolidate them into a single body of law: if you want to know what you’re really voting for, it’s this consolidated body of law you need to read. And guess what, there have been copies of the consolidated treaties floating around for ages.

For instance, if you type “lisbon treaty consolidated” into Google, you get Peadar O Broin’s consolidated text, which has been around since January 16th of this year. Alternatively, Libertas, if you’re suspicious that O Broin might have some pro-treaty bias, also have an excellent consolidated text with annotations. Read and download one or the other. If you want a good overview, head to the Wikipedia page on the treaty, but don’t forget to read the treaty text too.

Contrary what some in the No camp say, the consolidated treaties, though running into over 300 pages in total, is actually pretty clear. Best as I can tell, there’s no great, if any, effort at obfuscation. So please, ignore the scaremongering from both the Yes and No camps, read the consolidated treaty text, and make your own mind up.

And if anybody brings up the incomprehensibility argument, print out the consolidated treaty and whack them across the back of the head with it. You’ll be doing them and the country a service.

May 19, 2008 at 11:30PM IsaacBrowser IV, release 3

I think this should be the last one for a while.

I haven’t made any major interface changes but there are a few important internal ones, namely:

So, download away! Any feedback is much appreciated.

[download; demo]

Update (June 17th): I’m throwing around a few ideas, if anybody wants to contribute.

May 20, 2008 at 12:06AM Annoying problem with the RSS element in Bloglines

Gah! I’m at the end of my tether with this Bloglines bug. I haven’t got around to ditching my RSS generator in favour of Atom yet, but this is pushing me towards doing so sooner than I’d imagined. I’ve two perfectly valid RSS feeds, but Bloglines treats the <guid/> elements in both incorrectly. I posted a bug report to them. Here’s what it said:

The RSS <guid/> element is handled incorrectly when its isPermaLink attribute is assigned the value “true”.

What should be happening is that the title of each entry displayed should link to the URL given in this element. However, the entry title link for each entry is, instead, empty.

For an example of a feed that is valid, but rendered incorrectly, see http://talideon.com/weblog/syndication.cfm.

It appears the <guid/> element is being ignored in favour of the contents of the <link/> element, at least going off of this other feed: http://talideon.com/linklog/syndication.cfm. However, this feed is also being rendered slightly incorrectly as the contents of its <guid/> elements is also being ignored when it should be rendered as a permalink to the original item in addition to the linked item.

Chances of it being acted upon any time soon? Meh. I can’t see fixing this being a high priority given that it’s a problem with RSS.

May 26, 2008 at 6:22PM Converting a quoted-printable string to plaintext

This was sitting around doing nothing, and I thought it was worth posting up.

function from_quoted_printable($s) {
    $result = preg_replace("/=\n/m", '', $s);
    $result = preg_replace('/=([0-9A-F]{2})/e', "pack('H2', '\\1')", $result);
    return $result;
}

May 29, 2008 at 3:48PM Thoughts on bugtracking

At Blacknight, we use our own custom bugtracking software, GrassSnake. It’s something I wrote over the course of a few days on my own time and which I’ve been working on whenever I’ve had a free moment, which, in fairness, isn’t all that often.

It’s worked pretty well for us so far, but the design is beginning to hit a few limitations. Before I get to that, it’s best if I explain its current design.

GrassSnake contains a list of projects, and each project has issues contained within it, and there’s a one-to-many relationship between projects and issues. Each issue has a series of messages attached to it and a watch list. The watch list is a list associating issues and users who wish to be informed when an issue is updated. How they get informed is the responsibility of whatever notification plugins are installed, with the current sole notification method being a simple mailer plugin. Privileged users are known as developers and can be assigned as the lead on a project or have issues assigned to them. The main purpose of a lead is that they’re the person to whom issues for a particular project are assigned to initially and it’s their responsibility to triage new issues for those projects and assign them to the appropriate developer.

This seems like a perfectly reasonable way of running things, and it was until a few months back, but it’s becoming difficult to manage without resorting to adding a lot of administrative cruft to the application. For instance, there’s reassigning issues from one project to another, merging issues together, marking issues as duplicates, and so on. These were being managed with command-line tools, but with the company network being locked down further, partly as a consequence of the Debian OpenSSL debacle and a certain system administrator’s intransigence (*cough*), that’s not feasible anymore.

So, what to do?

For a start, I want to get rid of projects. The basic idea is sound, but it doesn’t really need to be explicit.

In its place, I want to add tagging or some other form of categorization. My thinking is that projects just need to be a specific class of category or tag. If I go down this route, the tag for, say, BlogPing-related tickets would be @blogping, for AFK-related tickets, @afk, and so on: the @ prefix marks the tag as a project tag. Issues too would become a special kind of tag, where the tag is a number prefixed by a hash symbol, so #42 would be a tag for issue 42, whatever that might be. This would allow issues to be associated with one-another. A variant on issue tags would be duplicate issue tags, which would be used to mark an issue as a duplicate of another, so something like dup:#42 would mark an issue as a duplicate of issue 42.

I also want to get rid of the idea of a developer. It should be sufficient for somebody to be watching an issue and optionally take responsibility for that issue.

To compensate for all the stuff being taken out, some more stuff’s going to have to be put back in. The main thing to be added will be autowatching, where when an issue matches a set of criteria (that is, a specific set of tags), they’re automagically put on the watchlist for that issue and possibly take responsibility for that issue.

The issue workflow GrassSnake currently has is far too complex. The current states are:

Nine different states. This list grew organically over time and, I think, should never have reached the size if did. It’s desperately in need of simplification.

Obviously, Untriaged can be collapsed into Open now that the idea of a lead developer gone. Suspended was always a bit of a sop for the developer to be lazy and was a bad idea. The same goes for Design Decision Needed, which started life as a not-so-subtle way to get people to work on the design side of things properly, but never worked. Works For Me, Intended Behaviour, Won’t Fix, and Not Enough Information are really better off dealt with using messages. That leaves us with three issue states that are actually useful: Open, Resolved, and Closed. This simplified system would stay an issue property; there’s no sense making it a tag. Moreover, I want to tie down the lifecycle more. From Open, the user should only be able to mark the issue as Resolved, and then, and only then, Closed. It should be possible to make Resolved or Closed issues Open again. The current system is rather fast-and-loose with the part of the lifecycle.

I’m tempted to make priorities issues, but, aside from some confusion and conflict between people on what the priorities mean, having them as distinct property of issues is useful.

Oh, and I finally have to implement fulltext search, which I’ll probably make a plugin if I can, and a bunch of other bits and pieces such as tasks, a nag plugin for people who have taken responsibility for an issue, and paging. Then there’s the need to finish the ticketing system I’d started writing to act as a customer frontend for GrassSnake.

Thoughts?

May 29, 2008 at 4:15PM Stupid shell hacks: A poor man’s MusicPD.

I use ROX, and one of the extension scripts I wrote for it was something to allow me to play arbitrary audio files in the background without needing something like MusicPD.

#!/bin/sh
#
# play-track
# by Keith Gaughan <http://talideon.com/>
#
# Think of it as the poor man's MPD. :-) Specifically, I use it for playing
# audio files with ROX. Start it with a file to play that file and without
# to stop whatever the file currently being played from playing. Starting it
# with a directory name will play all the tracks in that directory (but none
# of its subdirectories). Starting it with no arguments will cause the copy
# currently executing in the background to exit.
#
# I, Keith Gaughan, hereby put this script in the public domain. It comes
# without warranty of any kind and the author disclaims responsibility for
# any mishap that may occur due to its use. You are requested, but not
# obligated, to leave the attribution of the original author in place and
# to append your name to the attribution if you distribute altered versions
# of this script.
#

PID_FILE=$HOME/.`basename "$0"`.pid

kill_existing () {
    test -r $PID_FILE && kill `cat $PID_FILE`
}

play () {
    kill_existing
    for i in mplayer mpg321 mpg123 plaympeg; do
        if which $i >/dev/null; then
            $i "$@" 2>&1 >/dev/null &
            PID=$!
            echo $PID >$PID_FILE
            if wait $PID; then
                test -r $PID_FILE && rm $PID_FILE
                return 0
            fi
            test -r $PID_FILE && rm $PID_FILE
            return 1
        fi
    done
    return 1
}

randomise_files () {
    for i in "$@"/*; do
        # jot is *BSD specific and generates numbers, much like seq. However,
        # it's also able to generate sequences of *random* numbers.
        echo "`jot -rn 1 1 100000`~$i"
    done | sort -n | cut -f2 -d~
}

play_dir () {
    randomise_files "$@" | while read j; do
        play "$j" || exit
    done
}

if [ "$@ " = " " ]; then
    kill_existing
elif [ -d "$@" ]; then
    play_dir "$@"
else
    play "$@"
fi

The script has a few FreeBSD-isms, notably the use of jot to randomise the tracklisting. If you use bash, you can substitute $RANDOM for this.

Asides from demonstrating some nifty piping tricks, there’s an actual purpose behind me posting this up. Originally, it was only able to play individual tracks, but I extended it last night to support playing the contents of a directory. Why? Because I’ve became extraordinarily bad at getting up in the morning lately and I decided to have my laptop double as an alarm clock by setting up an at or cron job to trigger the alarm. I didn’t get to try it out properly last night, but I’m going to try it tonight and see if it helps.

Update: Well, it seems like Operation “Get The Hell Outta Bed” failed miserably. I’d set it up, but I was so anxious about the whole thing that I couldn’t get to sleep at all. Let’s see if I’ve better results on Monday.

May 29, 2008 at 10:00PM Jape: Graveyard

Richie Egan is a legend! Might be going down to Waterford to see them on June 8th, depending on how things go.

[via; on MySpace]