<?xml version='1.0' encoding='utf-8' ?>
<!--  If you are running a bot please visit this policy page outlining rules you must respect. http://www.livejournal.com/bots/  -->
<rss version='2.0' xmlns:lj='http://www.livejournal.org/rss/lj/1.0/'>
<channel>
  <title>Chad Austin&apos;s Blog</title>
  <link>http://aegisknight.livejournal.com/</link>
  <description>Chad Austin&apos;s Blog - LiveJournal.com</description>
  <lastBuildDate>Wed, 16 Jul 2008 07:05:28 GMT</lastBuildDate>
  <generator>LiveJournal / LiveJournal.com</generator>
  <lj:journal>aegisknight</lj:journal>
  <lj:journaltype>personal</lj:journaltype>
  <image>
    <url>http://p-userpic.livejournal.com/55146667/162345</url>
    <title>Chad Austin&apos;s Blog</title>
    <link>http://aegisknight.livejournal.com/</link>
    <width>100</width>
    <height>100</height>
  </image>

<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/137762.html</guid>
  <pubDate>Wed, 16 Jul 2008 07:05:28 GMT</pubDate>
  <title>Thriving Sphere community</title>
  <link>http://aegisknight.livejournal.com/137762.html</link>
  <description>I just found out that Sphere, the RPG creation system that I started developing eleven years ago, has a thriving community!  A good 2/3 of all of the hits to aegisknight.org are to &lt;a href=&quot;http://aegisknight.org/sphere&quot;&gt;http://aegisknight.org/sphere&lt;/a&gt;, so I made sure to point people on that page to &lt;a href=&quot;http://www.spheredev.org/&quot;&gt;spheredev.org&lt;/a&gt; and the &lt;a href=&quot;http://www.spheredev.org/smforums/index.php&quot;&gt;spheredev.org forums&lt;/a&gt;.  Yeah open source!</description>
  <comments>http://aegisknight.livejournal.com/137762.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/137543.html</guid>
  <pubDate>Sun, 13 Jul 2008 21:26:09 GMT</pubDate>
  <title>Finding the shortest path between two objects in Python</title>
  <link>http://aegisknight.livejournal.com/137543.html</link>
  <description>I&apos;ve been doing a bit of memory profiling and debugging in the IMVU client lately, and we&apos;re starting to collect a nice set of tools.  Today, I&apos;m adding a function that can find the shortest path from one Python object to another.  No guarantees about performance or correctness, but it seems to work so far.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://aegisknight.org/shortest_path.html&quot;&gt;The findShortestPath function.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://aegisknight.org/shortest_path_tests.html&quot;&gt;Its tests.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update:&lt;/b&gt; Fixed some ordering bugs in longer paths and optimized it a tad.</description>
  <comments>http://aegisknight.livejournal.com/137543.html</comments>
  <category>imvu python</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/137268.html</guid>
  <pubDate>Thu, 10 Jul 2008 02:44:51 GMT</pubDate>
  <title>More Python trivia</title>
  <link>http://aegisknight.livejournal.com/137268.html</link>
  <description>&lt;p&gt;Given a set &lt;code&gt;a&lt;/code&gt;, what&apos;s the difference between:&lt;/p&gt;

&lt;pre&gt;
a |= set([1])
&lt;/pre&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;pre&gt;
a = a | set([1])
&lt;/pre&gt;

&lt;p&gt;?&lt;/p&gt;</description>
  <comments>http://aegisknight.livejournal.com/137268.html</comments>
  <category>python</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/137088.html</guid>
  <pubDate>Mon, 30 Jun 2008 03:32:02 GMT</pubDate>
  <title>New Sphere Game</title>
  <link>http://aegisknight.livejournal.com/137088.html</link>
  <description>Metallix just let me know his new game &lt;a href=&quot;http://aquatis.metallixcreations.de/&quot;&gt;Aquatis&lt;/a&gt; has been released!  Check it out!</description>
  <comments>http://aegisknight.livejournal.com/137088.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/136847.html</guid>
  <pubDate>Sat, 24 May 2008 21:20:51 GMT</pubDate>
  <title>IMVU Client&apos;s Automated Crash Reporting System: Catching Python Exceptions</title>
  <link>http://aegisknight.livejournal.com/136847.html</link>
  <description>&lt;p&gt;For years now, I have been meaning to write a series of articles on the automated crash reporting system in the IMVU client.  This first article will give a bit of background on the structure of the client and show how we handle Python exceptions.&lt;/p&gt;

&lt;p&gt;At IMVU, we generally subscribe to the &lt;a href=&quot;http://c2.com/cgi/wiki?FailFast&quot;&gt;Fail Fast&lt;/a&gt; philosophy of handling errors: when the client encounters an unexpected error, we immediately crash the program and ask the user to submit a crash report.  As part of the crash report, we send log files, stack traces, system information, and anything else that might help us debug the failure.&lt;/p&gt;

&lt;p&gt;You might wonder why we crash the program whenever anything goes wrong rather than trying to catch the error and continue running.  Counterintuitively, crashing the program forces us to act on crashes and immediately exposes bugs that might trigger unwanted behavior or lost data down the road.&lt;/p&gt;

&lt;p&gt;Now let&apos;s talk a little bit about how the client is structured.  The IMVU client is written primarily in Python, with time-critical components such as the 3D renderer written in C++.  Since the client is a cross between a normal interactive Windows program and a real-time game, the main loop looks something like this:&lt;/p&gt;

&lt;pre&gt;
def main():
	while running:
		pumpWindowsMessages() # for 1/30th of a second
		updateAnimations()
		redrawWindows()
&lt;/pre&gt;

&lt;p&gt;This structure assumes that no exceptions bubble into or out of the main loop.  Let&apos;s imagine that updateAnimations() has a bug and occasionally raises an uncaught exception.  If running the client with a standard command-line python invocation, the program would print the exception and stack trace to the console window and exit.  That&apos;s all great, but our users don&apos;t launch our client by invoking python from the command line: we use py2exe to build a standalone executable that users ultimately run.  With an unmodified py2exe application, uncaught exceptions are printed to sys.stderr (as above), except there is no console window to display the error.  Thus, the py2exe bootstrap code registers a handler so that errors are logged to a file, and when the program shuts down, a dialog box shows something like &quot;An error has been logged.  Please see IMVUClient.exe.log.&quot;&lt;/p&gt;

&lt;p&gt;From a crash reporting standpoint, this is not good enough.  We can&apos;t be asking our users to manually hunt down some log files on their hard drives and mail them to us.  It&apos;s just too much work - they will simply stop using our product.  (Unfortunately, most of the software out there asks users to do exactly this!)  We need a way for the client to automatically handle errors and prompt the users to submit the reports back to us.  So let&apos;s rejigger main() a bit:&lt;/p&gt;

&lt;pre&gt;
def mainLoop():
	while running:
		pumpWindowsMessages()
		updateAnimations()
		redrawWindows()

def main():
	try:
		mainLoop()
	except:
		error_information = sys.exc_info()
		if OK == askUserForPermission():
			submitError(error_information)
&lt;/pre&gt;

&lt;p&gt;This time, if a bug in updateAnimations() raises an exception, the top-level try: except: clause catches the error and handles it intelligently.  In our current implementation, we post the error report to a Bugzilla instance, where we have built custom tools to analyze and prioritize the failures in the field.&lt;/p&gt;

&lt;p&gt;This is the main gist of how the IMVU client automatically reports failures.  The next post in this series will cover automatic detection of errors in our C++ libraries.&lt;/p&gt;</description>
  <comments>http://aegisknight.livejournal.com/136847.html</comments>
  <category>imvu</category>
  <category>python</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/136472.html</guid>
  <pubDate>Thu, 22 May 2008 05:46:41 GMT</pubDate>
  <title>Open sourced our pstats viewer!</title>
  <link>http://aegisknight.livejournal.com/136472.html</link>
  <description>IMVU has benefited greatly from open source software.  Large components of our client and website are built on top of various open source projects.  In fact, you can see the list of software on our &lt;a href=&quot;http://imvu.com/technology&quot;&gt;technology page&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Well, I&apos;m happy to announce that we&apos;re beginning to contribute source code back!  We have created a &lt;a href=&quot;http://sourceforge.net/projects/imvu&quot;&gt;project&lt;/a&gt; on SourceForge and released our first standalone tool: &lt;a href=&quot;https://imvu.svn.sourceforge.net/svnroot/imvu/imvu_open_source/tools/pstats_viewer.py&quot;&gt;pstats_viewer.py&lt;/a&gt;.  pstats_viewer is a tool for browsing the data stored in a .pstats file generated by the Python profiler.</description>
  <comments>http://aegisknight.livejournal.com/136472.html</comments>
  <category>imvu</category>
  <category>python</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/136368.html</guid>
  <pubDate>Sat, 17 May 2008 19:16:43 GMT</pubDate>
  <title>Blogs and blogs and blogs</title>
  <link>http://aegisknight.livejournal.com/136368.html</link>
  <description>I&apos;ve been asked several times which blogs I actively read, so I wrote a little program to automatically synchronize my RSS subscriptions with my web site.  You can see the list &lt;a href=&quot;http://aegisknight.org/blogroll&quot;&gt;here&lt;/a&gt;.</description>
  <comments>http://aegisknight.livejournal.com/136368.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/135971.html</guid>
  <pubDate>Thu, 15 May 2008 08:48:27 GMT</pubDate>
  <title>Tasks and Futures and Concurrency in the IMVU Client</title>
  <link>http://aegisknight.livejournal.com/135971.html</link>
  <description>&lt;p&gt;The IMVU client has a platform for developing asynchronous or long-running operations that we call the task system.  The following describes why we built it.  (Chris finally convinced me to post this information to the web.)&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Most programs that we write can be split into two categories.  There are short-lived programs like &lt;i&gt;gzip&lt;/i&gt; that take command line arguments and files and inputs and output to other files or stdout.&lt;/p&gt;

&lt;p&gt;There are also longer-lived programs that run indefinitely, handling events that happen to it.  Think about &lt;i&gt;MySQL&lt;/i&gt; or &lt;i&gt;memcached&lt;/i&gt;.  It takes inputs from the network, time, and files; doing work and spitting output back onto the network or the file system.&lt;/p&gt;

&lt;p&gt;The operating system kernel is one of those event-driven, long-lived programs.  It schedules the execution of subsidiary short- and long-lived programs.&lt;/p&gt;

&lt;p&gt;We can certainly map from one type another.  Take &lt;i&gt;PHP&lt;/i&gt; for example.  It is running within the context of a long-running server process, but the programming model for a single page is a lot closer to &lt;i&gt;gzip&lt;/i&gt;.  The program takes inputs (session data, request variables) and outputs HTML.&lt;/p&gt;

&lt;p&gt;You can also think of a web service as a set of computers all running a bunch of short-lived processes that combine into one large program.  PHP is the CPU, memcached is memory, the database is the file system, etc.&lt;/p&gt;

&lt;p&gt;Desktop applications, including the IMVU client and other &lt;a href=&quot;http://en.wikipedia.org/wiki/Rich_Internet_application&quot;&gt;&quot;rich internet clients&quot;&lt;/a&gt; are structured a lot like servers:  they are long-lived programs that respond to events from the keyboard, mouse, time, the windowing system, network requests...  and interact with its users via graphics, sound, files, and the network.&lt;/p&gt;

&lt;p&gt;The key notion here is that these are &lt;b&gt;abstractions&lt;/b&gt; on top of an enormous state machine known as your processors and memory.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Now let&apos;s talk a little bit about concepts and modeling.  When you come into a code base, say, our client, you expect to see structures that look and behave like concepts you know: avatars, chat windows, buddy list, inventory, products from the catalog, etc.  This is one reason OOP is so powerful - it&apos;s an effective method for describing concepts and the way they interact.&lt;/p&gt;

&lt;p&gt;Now let&apos;s talk about a simpler concept.  One so ingrained you probably don&apos;t even think about it.  Functions (in the imperative sense).  Functions are an abstraction on top of the state machine that is your processor.  We&apos;re all very familiar with functions: &quot;This operation takes X and Y as inputs, does steps A, B, and C, and returns Z.&quot;  Even if you throw in a few branches and loops and call some other functions, we&apos;re good at following the logic.  For example:&lt;/p&gt;

&lt;pre&gt;
def _readNumber(self):
    isfloat = False
    result = self._next()
    peek = self._peek()
    while peek is not None and (peek.isdigit() or peek == &quot;.&quot;):
        isfloat = isfloat or peek == &quot;.&quot;
        result = result + self._next()
        peek = self._peek()
    try:
        if isfloat:
            return float(result)
        else:
            return int(result)
    except ValueError:
        raise ReadException, &quot;Not a valid JSON number: &apos;%s&apos;&quot; % result
&lt;/pre&gt;

&lt;p&gt;
This a fairly elaborate function, but we have no problem following it.  Still, it&apos;s an abstraction on top of the processor and memory: it&apos;s compiled by Python into bytecode, executed by the interpreter loop, which is written in C, which is compiled to x86 assembly, which is being converted by the processor into micro ops...  The point here is that we can build our own abstractions if they make us more effective at doing our jobs.
&lt;/p&gt;

&lt;p&gt;
There is a concept in the IMVU client that may be unfamiliar to some of you: long-running processes.  These are like functions, except that many can be running at the same time, and they can be blocked, waiting for results from the network or disk.  Or perhaps just waiting for some time to elapse.  Pseudocode for one of these processes might look like this:
&lt;/p&gt;

&lt;pre&gt;
def loadAvatar(chatId, userId):
    avatarInfo = getAvatarInfo(userId)
    if failure, show dialog box and stop loading
    products = getCurrentOutfit(chatId, userId)
    for p in products:
        loadProduct(p)
    loadAvatarIntoScene(products) # this might take a while
&lt;/pre&gt;

&lt;p&gt;
In fact, these processes can be fairly easily implemented on top of threads, and many people do it that way, to varying degrees of success.
&lt;/p&gt;

&lt;p&gt;
However, threads have several disadvantages:
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each thread (on CPython/Windows) requires 1 MB of memory for the stack (plus any kernel objects), limiting you to at most 2000.&lt;/li&gt;
&lt;li&gt;Because threads are scheduled by the operating system, they&apos;re prone to nondeterminism and race conditions.  In our case, threads are a large source of intermittent test failures and real bugs.&lt;/li&gt;
&lt;li&gt;Threads in CPython don&apos;t actually buy you any concurrency because of the global interpreter lock, so you might as well just run everything on the main thread.&lt;/li&gt;
&lt;li&gt;Threads are GC roots and must be explicitly cleaned up.&lt;/li&gt;
&lt;li&gt;You have to be extremely careful when interacting with the windowing system or any third-party system from threads.  Many APIs are not thread safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another way to manage these types of long-running operations is by building an explicit state machine and periodically pumping it, when it will check and update its state if possible.  This is generally how interactive games work, because they can get away with using up all of the CPU and having a fixed frame rate.  Much of the code in the IMVU client uses this model, but we&apos;ve found it 1) hard to understand the interactions between states and 2) inefficient.  Most of the time you&apos;re _not_ updating the state of a particular object, so you don&apos;t need to check every single frame.  For example, some of our most avid users have thousands of items in their inventory, and our old inventory UI walks that entire list 30 times per second, asking “does this one have any events to process?  does this one have any events to process?  does this one have any events to process?”  This takes up to 10% of the CPU on the user&apos;s computer without a 3D window open!  Finally, this approach generally makes the application feel sluggish, because the latency between an event (say, a mouse click) being sent to the client and it being processed depends on the rate at which the application is pumped, rather than having the client immediately handle messages as they come in.&lt;/p&gt;

&lt;p&gt;A third technique you might call &lt;a href=&quot;http://en.wikipedia.org/wiki/Continuation_passing_style&quot;&gt;continuation-passing style&lt;/a&gt; or &quot;onComplete callbacks&quot;.  Basically, any function that can be asynchronous takes an onComplete callback function that it runs (with the result or error) when the operation is completed.  The problem with this style is that hearkens back to the days of goto with spaghetti callback loops and non-linear control flow.  Moreover, if you accidentally forget to call an onComplete, or you call it twice, you&apos;ve just introduced a very hard-to-find bug.  We have had both of these types of bugs multiple times in our client.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;We did not find any of the above approaches particularly compelling, so we built the task system on top of Python&apos;s bidirectional generators.  First, I&apos;ll enumerate the advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tasks are a lightweight resource (sorta) – you can start as many as you&apos;d like.&lt;/li&gt;
&lt;li&gt;Tasks look like normal functions with &apos;yield&apos; statements sprinkled in.&lt;/li&gt;
&lt;li&gt;There&apos;s no chance you&apos;ll forget to call onComplete or call it twice.&lt;/li&gt;
&lt;li&gt;Exceptions propagate automatically -- with stack traces!&lt;/li&gt;
&lt;li&gt;Tasks can be implicitly cancelled by releasing the reference to their result, so you won&apos;t leak them as is so easy with threads.&lt;/li&gt;
&lt;li&gt;All tasks run interleaved on the main thread, so you don&apos;t have to worry about most kinds of race conditions or calling into external libraries.&lt;/li&gt;
&lt;li&gt;Tasks run as part of the Windows event loop, which means that popping up modal dialog boxes or modal menus does not block them from executing, keeping the application responsive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here&apos;s what the hypothetical example from above would look like as a task:&lt;/p&gt;

&lt;pre&gt;
@task
def loadAvatar(chatId, userId):
    try:
        avatarInfo = yield getAvatarInfo(userId)
    except Exception:
        showErrorDialog()
        return

    products = yield getCurrentOutfit(chatId, userId)
    for p in products:
        try:
            yield loadProduct(p)
        except Exception:
            pass # ignore products that fail to load?
    yield loadAvatarIntoScene(products)
&lt;/pre&gt;

&lt;p&gt;
At every &apos;yield&apos;, other tasks get a chance to run, and if that task does not return immediately, then the task is paused until the asynchronous call is done.  getAvatarInfo, for example, will contact the server in order to fetch the avatar name.  Tasks can yield on other task calls, suspending their execution until the subsidiary call returns a value or raises an exception.  (Sound familiar?)
&lt;/p&gt;

&lt;p&gt;
Tasks can start other tasks, with the admittedly strange “yield Start(task)” syntax which gives back a Future object.  Think of &lt;a href=&quot;http://en.wikipedia.org/wiki/Future_(programming)&quot;&gt;futures&lt;/a&gt; as values that may not have materialized yet.  You can later wait for the result of a future by yielding on it.
&lt;/p&gt;

&lt;pre&gt;
@task
def printFuture(future):
    # this will throw an exception if the future
    # contains an error
    result = yield future
    print result

@task
def printAvatarInfo():
    avatarInfo = yield Start(getAvatarInfo(userId))
    print &apos;started fetching avatarInfo&apos;
    yield printFuture(avatarInfo)
&lt;/pre&gt;

&lt;p&gt;
You can use futures to start several tasks in parallel and then wait on all of them, making efficient use of parallel resources.  Some examples with real code might help.  Unfortunately you can&apos;t run them, as we have not (yet) open sourced the task system.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://aegisknight.org/download/get_several_avatars_serial.py&quot;&gt;get_several_avatars_serial.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://aegisknight.org/download/get_several_avatars_parallel.py&quot;&gt;get_several_avatars_parallel.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://aegisknight.org/download/producer_consumer.py&quot;&gt;producer_consumer.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://aegisknight.org/download/queue.py&quot;&gt;queue.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://aegisknight.org/download/threadtask.py&quot;&gt;threadtask.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://aegisknight.org/download/taskutil.py&quot;&gt;taskutil.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Today, much of the client&apos;s code uses threads, polling, and onCompletes, but we&apos;re slowly converting things to tasks, usually to fix bugs.  I hope this means that the quality of the client from a user&apos;s perspective will continue to improve and engineers will be able to work in the code more easily.
&lt;/p&gt;</description>
  <comments>http://aegisknight.livejournal.com/135971.html</comments>
  <category>imvu python</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/135934.html</guid>
  <pubDate>Sun, 27 Apr 2008 20:31:18 GMT</pubDate>
  <title>Twitter</title>
  <link>http://aegisknight.livejournal.com/135934.html</link>
  <description>After several abortive attempts, I&apos;ve finally started using Twitter.  &lt;a href=&quot;http://twitter.com/chadaustin&quot;&gt;Tweet tweet&lt;/a&gt;.</description>
  <comments>http://aegisknight.livejournal.com/135934.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/135629.html</guid>
  <pubDate>Sat, 26 Apr 2008 03:54:45 GMT</pubDate>
  <title>A Wiki For Reviewing Third-Party Libraries</title>
  <link>http://aegisknight.livejournal.com/135629.html</link>
  <description>In &lt;a href=&quot;http://www.spiteful.com/2008/04/25/dev-diligence-dont-invest-in-the-wrong-code/&quot;&gt;Don&apos;t Invest in the Wrong Code&lt;/a&gt;, Tom Kleinpeter has created a wiki for &lt;a href=&quot;http://www.spiteful.com/dd/wishlist&quot;&gt;pitfalls in third-party libraries&lt;/a&gt;.  Sort of a consumer reports for software engineers.  I think this is a great idea, and would have helped us a lot at IMVU.  Please contribute!  Let&apos;s see this thing get critical mass!</description>
  <comments>http://aegisknight.livejournal.com/135629.html</comments>
  <category>imvu</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/135401.html</guid>
  <pubDate>Sun, 20 Apr 2008 01:25:50 GMT</pubDate>
  <title>More Updates (and testing facebook rss integration)</title>
  <link>http://aegisknight.livejournal.com/135401.html</link>
  <description>I replaced my lame and outdated &apos;about me&apos; section with links to Facebook, LinkedIn, and Last.fm.&lt;br /&gt;&lt;br /&gt;This morning, at 6:00 a.m., I was woken by a text message from &lt;a href=&quot;http://www.reinot.com/&quot;&gt;Andres&lt;/a&gt;, asking if I was in the Minneapolis airport.  He heard my name in an announcement.  I guess there are more Chad Austins in the world than I&apos;d like to think.</description>
  <comments>http://aegisknight.livejournal.com/135401.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/134998.html</guid>
  <pubDate>Sat, 19 Apr 2008 12:54:06 GMT</pubDate>
  <title>Site Updates</title>
  <link>http://aegisknight.livejournal.com/134998.html</link>
  <description>&lt;a href=&quot;http://aegisknight.org/&quot;&gt;aegisknight.org&lt;/a&gt;&apos;s been pretty stale in recent years, so I changed the &apos;links&apos; link to point to my &lt;a href=&quot;http://del.icio.us/chadaustin&quot;&gt;del.icio.us&lt;/a&gt; and &apos;pics&apos; to Laura&apos;s &lt;a href=&quot;http://picasaweb.google.com/lbaustin/&quot;&gt;Picasa account&lt;/a&gt;.</description>
  <comments>http://aegisknight.livejournal.com/134998.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/134754.html</guid>
  <pubDate>Thu, 03 Apr 2008 12:34:07 GMT</pubDate>
  <title>Tasks, object lifetimes, and implicit cancellation</title>
  <link>http://aegisknight.livejournal.com/134754.html</link>
  <description>Background:  The IMVU client has a system that we call the &apos;task system&apos; which works a lot like lightweight threads or &lt;a href=&quot;http://en.wikipedia.org/wiki/Coroutine&quot;&gt;coroutines&lt;/a&gt;.  You can schedule up some work which gives you back a &lt;a href=&quot;http://en.wikipedia.org/wiki/Future_(programming)&quot;&gt;Future&lt;/a&gt; object on which you can either wait or request the actual result (if it has materialized, that is).&lt;br /&gt;&lt;br /&gt;I just read Joe Duffy&apos;s &lt;a href=&quot;http://www.bluebytesoftware.com/blog/2008/02/18/IDisposableFinalizationAndConcurrency.aspx&quot;&gt;IDisposable, finalization, and concurrency&lt;/a&gt;.  The parallels between TPL and IMVU&apos;s task system surprised me.&lt;br /&gt;&lt;br /&gt;When we built the task system, there were two ways to cancel execution of a task: explicit and implicit.  You could either call future.cancel() or just release your reference to the future and wait for it to be collected by the GC.  In either case, the scheduler would notice and stop running your task.  I&apos;ve often wondered if support for implicit task cancellation was a good design.&lt;br /&gt;&lt;br /&gt;After reading Joe&apos;s article, I&apos;m convinced.  If you believe that resource utilization is also a resource, and that implicit resource disposal (garbage collection) is the right default, then implicit cancellation is the only sane default.  In fact, we recently removed support for explicit cancellation, because we haven&apos;t even &apos;really&apos; used it yet.  (Some of Dusty&apos;s code used it, but he said it was fine to take it out.)&lt;br /&gt;&lt;br /&gt;We may have to implement explicit cancellation again someday, but now I&apos;m happy to wait until there is a compelling use case.</description>
  <comments>http://aegisknight.livejournal.com/134754.html</comments>
  <category>imvu</category>
  <category>python</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/134427.html</guid>
  <pubDate>Mon, 09 Apr 2007 06:42:51 GMT</pubDate>
  <title>Why are old, but popular, games so hard to find?</title>
  <link>http://aegisknight.livejournal.com/134427.html</link>
  <description>Back in 2002, when I was in college, I was walking down the street from one of my classes, and a friend randomly walked up to me and handed me a video game.  It had a shiny box, so I accepted it, but for whatever reason did not even try playing until recently.  The game was &lt;a href=&quot;http://www.microsoft.com/games/ageofmythology/&quot;&gt;Age of Mythology&lt;/a&gt;, and as I found out in the last few weeks, it is awesome.  It&apos;s so much fun, that I want to recommend it to all of my friends.  And there&apos;s where the problem lies.  As far as I can tell, you simply can&apos;t buy it anymore.  I have looked at all of the major online retailers, but all of them either say temporarily out of stock or unavailable.  It&apos;s not like age of mythology is some rare game that you have to look especially hard for -- it won several awards in 2002 including &quot;game of the year&quot;.  And now it&apos;s just gone.&lt;br /&gt;&lt;br /&gt;Earlier this year I had the same problem with We Love Katamari.  After trying to buy it from six or seven places that claimed they had it -- each time it turned out they had miscounted their inventory -- Amazon got a new shipment and I managed to snag a copy before it went out of stock again.  Once again, this isn&apos;t some obscure PlayStation RPG, this is a recent and well-received game.&lt;br /&gt;&lt;br /&gt;So what&apos;s the deal?  Will this trend continue?  Am I going to have to purchase every game that I think I might want to play in the future?  You would think that digital distribution would solve this problem, and maybe it will eventually, but right now I just want to give somebody some dollars in exchange for a couple plastic discs.  Is that too much to ask?&lt;br /&gt;&lt;br /&gt;(This entry voice-recognized, courtesy of Dragon NaturallySpeaking.  Apologies for any weird wordos.)</description>
  <comments>http://aegisknight.livejournal.com/134427.html</comments>
  <category>games</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/134288.html</guid>
  <pubDate>Mon, 19 Feb 2007 07:56:46 GMT</pubDate>
  <title>Hawaiian Honeymoon</title>
  <link>http://aegisknight.livejournal.com/134288.html</link>
  <description>Over the past six months, Laura and I have written an account of our honeymoon in Hawaii.  Pictures included.  You can read (or just look at the pictures) &lt;a href=&quot;http://iagirl1984.livejournal.com/28183.html&quot;&gt;over here&lt;/a&gt;.</description>
  <comments>http://aegisknight.livejournal.com/134288.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/134136.html</guid>
  <pubDate>Tue, 13 Feb 2007 07:24:50 GMT</pubDate>
  <title>Consumer Electronics</title>
  <link>http://aegisknight.livejournal.com/134136.html</link>
  <description>Laura and I got a Wii on Sunday morning.  (Barely.  Luckily, Target got a shipment of 88.)  And digital cable today.  We already had: DVD player, VCR, TV-out (from my computer), PlayStation 2, N64, and a GameCube (which the Wii will replace soon).  So... I&apos;m looking for an 8-way A/V switch.  Definitely needs composite video, s-video a nice-to-have.  Manual switches are better.  We don&apos;t need a full-fledged receiver yet, at least until we get an HDTV.&lt;br /&gt;&lt;br /&gt;I can&apos;t find anything.  Any recommendations?</description>
  <comments>http://aegisknight.livejournal.com/134136.html</comments>
  <category>games</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/133748.html</guid>
  <pubDate>Sun, 11 Feb 2007 01:10:00 GMT</pubDate>
  <title>Open Source Development Tip #1</title>
  <link>http://aegisknight.livejournal.com/133748.html</link>
  <description>&lt;p&gt;
A couple years ago, when I was heavily involved in several open source projects, I accumulated a set of thoughts that might be interesting to someone starting their own open source project.  I had grandiose dreams of writing a series of articles along these lines, but as you might expect, real life and a lack of ambition nixed that.  Now, I&apos;ve forgotten even what I was interested in saying, so let&apos;s try a different approach.  Whenever I have a thought that I think somebody might possibly care about, I will write it as a tip.  If the set of tips grows sufficiently large, and enough people care, I may combine them into something more organization.  So here we go.  Open Source Development Tip #1:
&lt;/p&gt;

&lt;p&gt;
Subscribe to a &lt;a href=&quot;http://www.google.com/alerts&quot;&gt;Google Alert&lt;/a&gt; with your project&apos;s name and watch users &quot;in the wild&quot;.  Mailing lists are fine and all, but many people won&apos;t bother sending their problems or even first impressions to you or the project mailing lists, even if they&apos;ll write in other forums or blogs.  Google Alerts will give you a broader perspective.
&lt;/p&gt;

&lt;p&gt;
Prerequisites:
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Release your software.  (This, of course, requires a sufficient feature set to convince yourself that somebody might possibly want to use it.)&lt;/li&gt;
&lt;li&gt;Give your software a sufficiently google-able name.  (Audiere, iPodExtract, yes.  Corona, Sphere, no.)&lt;/li&gt;
&lt;/ol&gt;</description>
  <comments>http://aegisknight.livejournal.com/133748.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/133217.html</guid>
  <pubDate>Sun, 28 Jan 2007 01:51:38 GMT</pubDate>
  <title>Windows is looking pretty good today</title>
  <link>http://aegisknight.livejournal.com/133217.html</link>
  <description>Microsoft released a &lt;a href=&quot;http://go.microsoft.com/fwlink/?LinkID=75078&quot;&gt;new theme&lt;/a&gt; for Windows XP, in the style of the Zune.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://www.flickr.com/photos/chadaustin/371344173/&quot;&gt;&lt;img alt=&quot;My desktop&quot; src=&quot;http://farm1.static.flickr.com/165/371344173_9724189280.jpg?v=0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://aegisknight.org/~aegis/images/zunedows.png&quot;&gt;It looks pretty good.&lt;/a&gt;</description>
  <comments>http://aegisknight.livejournal.com/133217.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/132891.html</guid>
  <pubDate>Sun, 28 Jan 2007 00:32:09 GMT</pubDate>
  <title>Quality With A Name</title>
  <link>http://aegisknight.livejournal.com/132891.html</link>
  <description>As IMVU and its engineering team have grown, we&apos;ve encountered an increasing amount of overhead while implementing features that affect the original code for the client and web site (you may even call some of it prototype code).  You know, this effort even goes back to when I started.  Most of my early days at IMVU involved fixing bugs, adding test coverage, and making small changes to the product.  There were a lot of things about the code that I didn&apos;t particularly like, but I chalked that up to the first time I&apos;ve ever had to deal intimately with a large code base that I did not write.  That doesn&apos;t mean I didn&apos;t try to improve the things that I found; in fact, it&apos;s been an excellent exercise in articulating engineering practices I&apos;ve internalized so deeply that I take them for granted.&lt;br /&gt;&lt;br /&gt;At IMVU, we practice test driven development, which means that we always* add a unit test exposing a bug in the system before fixing the bug.  In practice, this means we should never see the same bug twice.  Some areas of the code have seen more love than others, of course.&lt;br /&gt;&lt;br /&gt;Recently, &lt;a href=&quot;http://thespeedbump.livejournal.com/&quot;&gt;Andy&lt;/a&gt; has been trying to implement a tiny new feature in the buddy window.  The kind of change that should only take 15 minutes to write the tests and maybe another 15 to implement.  (And we wouldn&apos;t have to worry about other features breaking, or other changes breaking this feature, and all the other TDD goodness.)  That is, if we have good tests in that area of the code.  But we don&apos;t, so it&apos;s not that easy.  So he gets to be the lucky engineer, adding tests to this module, which is involving a &lt;i&gt;lot&lt;/i&gt; of refactoring.  The code&apos;s style has a lot of internal consistency, and there definitely is a design, but adding tests around it has proven to be a huge time loss.  So we&apos;ve been trying to articulate why the code is such a pain.  But James Shore &lt;a href=&quot;http://www.jamesshore.com/Articles/Quality-With-a-Name.html&quot;&gt;explains it exactly&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&quot;...the &lt;i&gt;goodness&lt;/i&gt; of a design is inversely proportional to the cost of change.&quot;&lt;br /&gt;&lt;br /&gt;And there we go.  That sums it up.&lt;br /&gt;&lt;br /&gt;* Ideally.  We&apos;re always improving.</description>
  <comments>http://aegisknight.livejournal.com/132891.html</comments>
  <category>imvu</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/132804.html</guid>
  <pubDate>Sun, 21 Jan 2007 07:00:00 GMT</pubDate>
  <title>My father, continued</title>
  <link>http://aegisknight.livejournal.com/132804.html</link>
  <description>&lt;p&gt;
Laura and I just returned from San Diego, where my father is in the cardiac care unit of UCSD Medical Center.  On Wednesday, he was at a leadership conference, and out of nowhere, he collapsed to the floor.  Somebody noticed his heart had stopped, so they started CPR, but CPR doesn&apos;t do a whole lot for the condition my dad experienced.  Luckily, the hotel he was in happened to have an &lt;a href=&quot;http://en.wikipedia.org/wiki/Automated_external_defibrillator&quot;&gt;automated external defibrillator (AED)&lt;/a&gt; on hand, which they placed on his chest, and after two jolts, started his heart again and brought him back alive.  Within hours, he was basically fine again.
&lt;/p&gt;

&lt;div style=&quot;text-align: center&quot;&gt;
&lt;img style=&quot;width: 50%&quot; src=&quot;http://upload.wikimedia.org/wikipedia/commons/9/9b/EKGI.png&quot; alt=&quot;normal heartbeat&quot; /&gt;&lt;br /&gt;
&lt;i&gt;Normal heartbeat&lt;/i&gt;
&lt;/div&gt;

&lt;div style=&quot;text-align: center&quot;&gt;
&lt;img style=&quot;width: 50%&quot; src=&quot;http://upload.wikimedia.org/wikipedia/commons/8/85/EKG_VF.jpg&quot; alt=&quot;ventricular fibrillation&quot; /&gt;&lt;br /&gt;
&lt;i&gt;Ventricular fibrillation&lt;/i&gt;
&lt;/div&gt;

&lt;p&gt;
The condition my father experienced is &lt;a href=&quot;http://en.wikipedia.org/wiki/Ventricular_fibrillation&quot;&gt;ventricular fibrillation (VF)&lt;/a&gt;, where the electrical activity of the heart causes it to lose its coordination and stop pumping blood.  This is not the same as a heart attack, which is usually a problem with the muscle or blockage.  The survival rate on VF is 4% outside of the hospital.  &lt;i&gt;Four&lt;/i&gt; percent.  Scary.  The AED saved his life.  We&apos;re so lucky that one was nearby and that we still have a father.
&lt;/p&gt;

&lt;p&gt;
So, since not many victims of cardiac arrest and VF in particular survive, my dad&apos;s a bit of a celebrity now, and various doctors and interns have come in to say hello.  His picture and story are even going to be used in publications for San Diego&apos;s &lt;a href=&quot;http://www.sdprojectheartbeat.com/&quot;&gt;Project Heart Beat&lt;/a&gt;, a program to make AEDs ubiquitous.  My dad&apos;s company, &lt;a href=&quot;http://www.terex.com/&quot;&gt;Terex&lt;/a&gt;, is going to place AEDs in every plant and office building.  They&apos;re so cheap (you can even order them on &lt;a href=&quot;http://amazon.com/s/ref=nb_ss_gw/104-7709269-1857540?url=search-alias%3Daps&amp;amp;field-keywords=aed&amp;amp;Go.x=0&amp;amp;Go.y=0&amp;amp;Go=Go&quot;&gt;amazon.com&lt;/a&gt;) and so easy to use (basically automatic) that there&apos;s no reason public places, hotels, office buildings, etc. shouldn&apos;t have them.  So yeah, we&apos;re big fans now.
&lt;/p&gt;

&lt;p&gt;
Anyway, we&apos;re blessed that my father is still with us, and I guarantee we won&apos;t take any of our family for granted anymore.  Events like this seem to have that effect.
&lt;/p&gt;

&lt;p&gt;
&lt;i&gt;Update: I guess my sister wrote an &lt;a href=&quot;http://bssnchica.livejournal.com/210974.html&quot;&gt;update&lt;/a&gt; too.&lt;/i&gt;
&lt;/p&gt;</description>
  <comments>http://aegisknight.livejournal.com/132804.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/132407.html</guid>
  <pubDate>Thu, 18 Jan 2007 09:09:55 GMT</pubDate>
  <title>My dad&apos;s in the hospital</title>
  <link>http://aegisknight.livejournal.com/132407.html</link>
  <description>My dad is in the hospital in San Diego right now.  My sister &lt;a href=&quot;http://bssnchica.livejournal.com/210867.html&quot;&gt;explains&lt;/a&gt;.  Laura is flying down tomorrow morning, and I will be joining them the day after.  Please keep our family in your thoughts.</description>
  <comments>http://aegisknight.livejournal.com/132407.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/132345.html</guid>
  <pubDate>Thu, 18 Jan 2007 09:07:59 GMT</pubDate>
  <title>Quotes</title>
  <link>http://aegisknight.livejournal.com/132345.html</link>
  <description>&lt;i&gt;Creating a lot of technical debt quickly is not exactly what &quot;rapid development&quot; is supposed to mean.&lt;/i&gt; -- dnicolet99 on Scrum mailing list&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Scrum is great for either fixed-date variable-scope, or &quot;fixed-scope&quot; (which always grows) variable-date. If you&apos;re doing fixed-date fixed-scope, I recommend waterfall or RUP, which will buy you a few months to look for a new job.&lt;/i&gt; -- Michael James</description>
  <comments>http://aegisknight.livejournal.com/132345.html</comments>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/132041.html</guid>
  <pubDate>Sat, 16 Dec 2006 22:02:27 GMT</pubDate>
  <title>Help Me Pick a Game</title>
  <link>http://aegisknight.livejournal.com/132041.html</link>
  <description>&lt;p&gt;
In the recent past, I have finished Dragon Quest 8, Bahamut Lagoon, and Far Cry, basically flushing my game queue.  So I&apos;m looking for advice on what to play next.  I&apos;ll give a list of games, but feel free to suggest others.  Optimizing for fun divided by number of hours is best.  For example, Katamari Damacy was supremely fun and short, giving it a very high enjoyment quotient.  Here are the games, in no particular order:
&lt;/p&gt;

&lt;p&gt;&lt;b&gt;The Main List&lt;/b&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Half-Life 2&lt;/li&gt;
&lt;li&gt;Zelda: Twilight Princess&lt;/li&gt;
&lt;li&gt;Final Fantasy XII&lt;/li&gt;
&lt;li&gt;Rogue Galaxy&lt;/li&gt;
&lt;li&gt;We Love Katamari&lt;/li&gt;
&lt;li&gt;Shadow of the Colossus&lt;/li&gt;
&lt;li&gt;Chrono Cross&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;b&gt;The Secondary List&lt;/b&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Age of Mythology&lt;/li&gt;
&lt;li&gt;Final Fantasy III&lt;/li&gt;
&lt;li&gt;Rudra no Hihou (Treasure of the Rudras)&lt;/li&gt;
&lt;li&gt;Devil May Cry 3&lt;/li&gt;
&lt;li&gt;Ico&lt;/li&gt;
&lt;li&gt;Psychonauts&lt;/li&gt;
&lt;li&gt;Metroid Prime&lt;/li&gt;
&lt;li&gt;Kingdom Hearts&lt;/li&gt;
&lt;li&gt;Phantom Brave&lt;/li&gt;
&lt;li&gt;God of War&lt;/li&gt;
&lt;li&gt;Lego Star Wars&lt;/li&gt;
&lt;li&gt;Xenogears&lt;/li&gt;
&lt;li&gt;Valkyrie Profile (if playing it before VP2 is important)&lt;/li&gt;
&lt;li&gt;Zelda: Majora&apos;s Mask&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Help!  (But there are always &quot;eternal games&quot;, like Guild Wars and Gran Turismo that will consume all time in the meanwhile.)
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;Update:&lt;/b&gt; Added Shadow of the Colossus, Chrono Cross, Ico, Metroid Prime, Xenogears, and Majora&apos;s Mask.
&lt;/p&gt;</description>
  <comments>http://aegisknight.livejournal.com/132041.html</comments>
  <category>games</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/131780.html</guid>
  <pubDate>Tue, 28 Nov 2006 05:43:45 GMT</pubDate>
  <title>Agile Databases</title>
  <link>http://aegisknight.livejournal.com/131780.html</link>
  <description>There is a subgroup of the agile community focused on &lt;a href=&quot;http://www.martinfowler.com/articles/evodb.html&quot;&gt;agile database development&lt;/a&gt; and &lt;a href=&quot;http://databaserefactoring.com/&quot;&gt;database refactoring&lt;/a&gt;.  Just do &lt;a href=&quot;http://www.google.com/search?q=database+refactoring&quot;&gt;a search&lt;/a&gt;.  At IMVU, we definitely do agile development of our databases and the application layer code that talks to them.  We expect most new engineers to be capable of writing a database schema and pushing it to production within a week of starting, with automated tests that let us change things without fear of breakage, of course.  We have no traditional DBAs, although one of our engineers acts as one, reviewing schemas before they&apos;re applied in production.&lt;br /&gt;&lt;br /&gt;The database refactoring resources that we have seen have been deficient in one area: they assume that you have small amounts of data, or the ability to take down your site or service so that you can apply schema changes.  (Which lock tables until the schema is applied.)  The problem that we have not seen addressed yet is how to change table structure when you have millions of customers &lt;i&gt;and&lt;/i&gt; heavy usage.  Each hour the site is down translates into lost revenue.  Yeah, there are ways to get around this, such as applying schemas to slaves and then failing the master over to the slave, but these require a fair amount of infrastructure and pain to support.&lt;br /&gt;&lt;br /&gt;But if I could choose one thing to get from &quot;the agile database community&quot;, it would be a modification to MySQL that let you add or remove indices or columns in the background, even if that particular table was under heavy use, and even if the table had degraded performance during the alter.  Maybe it could be implemented as a table with special metadata that shows how to get the data from the old table if it does not exist in the new one.  And maybe Oracle supports this already, and I just want a new feature in MySQL.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update&lt;/b&gt;:  It turns out that if you have partitioned your customers across enough databases, and have some spare capacity, you can apply schema changes across your databases in parallel, and it&apos;s not so bad.  So maybe that&apos;s how to be agile and have users.  :)</description>
  <comments>http://aegisknight.livejournal.com/131780.html</comments>
  <category>imvu</category>
  <lj:security>public</lj:security>
</item>
<item>
  <guid isPermaLink='true'>http://aegisknight.livejournal.com/131452.html</guid>
  <pubDate>Sun, 26 Nov 2006 09:10:08 GMT</pubDate>
  <title>backup-sourceforge.py</title>
  <link>http://aegisknight.livejournal.com/131452.html</link>
  <description>Today I updated my &lt;a href=&quot;http://aegisknight.org/backup-sourceforge.py&quot;&gt;backup-sourceforge.py&lt;/a&gt; script to reflect the new SourceForge backup policies, including support for Subversion repositories.  You can download it on my &lt;a href=&quot;http://aegisknight.org/scripts&quot;&gt;scripts&lt;/a&gt; page.&lt;br /&gt;&lt;br /&gt;It used to be a bash script, but the second you&apos;re using commands like &lt;code&gt;trap &quot;error&quot; ERR&lt;/code&gt; and &lt;code&gt;set -e&lt;/code&gt; and &lt;code&gt;cd &quot;`dirname \&quot;$0\&quot;`&quot;&lt;/code&gt;, it&apos;s worth rewriting in Python.  Interestingly, the Python came out surprisingly terse and clear, unlike past experiences writing shell scripts in Python.  Also, it used to call into the &lt;a href=&quot;http://sitedocs.sourceforge.net/projects/adocman/&quot;&gt;adocman&lt;/a&gt; project (some Perl scripts provided by SourceForge to make backups easier), but it was such a pain to set up CPAN and install the required Perl packages that I decided I would reimplement their &lt;code&gt;xml_export&lt;/code&gt; tool in Python and urllib2.  I managed to replace &lt;code&gt;xml_export&lt;/code&gt; with fewer than 20 lines of code.  Python really does have &quot;batteries included&quot;!</description>
  <comments>http://aegisknight.livejournal.com/131452.html</comments>
  <lj:security>public</lj:security>
</item>
</channel>
</rss>
