Showing posts with label blogdoc. Show all posts
Showing posts with label blogdoc. Show all posts

Sunday, November 02, 2008

Ubuntu backup tip: save your package selections, including third-party, for later recovery

Today I wrote a little Python script which will pull a list of all of your installed packages, and then group them by Canonical-supplied, third-party PPA supplied, and manually installed .deb files.  This makes it a snap to automatically restore your package list if your system takes a dump, and even quickly put back your PPAs and manual debs as you go.

It requires python-twisted and python-apt installed.  Here is the script: dpkg-origins.

Recommended usage: Add this command to a script inside /etc/cron.daily:
  dpkg-origins | mail -s "Package selections for `hostname -s` as of `date`" yourname@youremail.com

When calamity strikes and you need to restore, you can pipe this file directly:

  cat selections.txt | sudo dpkg --set-selections && apt-get -u dselect-upgrade

This will begin installing all of the Canonical-supplied packages in selections.txt.  All other packages are commented out, but the file itself contains instructions for restoring your PPA's; then you uncomment some more packages and run the above again.  Finally, at the very top of the file, you will find a commented-out list of the packages (and versions) which were installed directly from a .deb file; manually download and install those.

Sunday, August 31, 2008

Gnome/Firefox tip: Pandora as a Separate Application

The problem: Pandora is more like a desktop application than a website. It should stay open when you close your browser. If you do web development, or your browser tends to crash a lot, or you just like to close Firefox sometimes, you probably don't want to lose your tunes. Figure out a way to keep Pandora open when the rest of your web browsing session is gone.

This is a walkthrough for Gnome/Compiz users for isolating Pandora from the rest of Firefox cleanly. Much of this will be applicable to you if you don't use Compiz, and even if you don't use Gnome.

This assumes Firefox 3.0.1/Linux, I can't vouch for command line options on any other version.

When I refer to your Default Firefox Icon, I am talking about whatever means you usually use to launch Firefox, whether it's a toolbar icon, a menu icon, Alt+F2 "firefox", etc.

Create a Clean Pandora Profile

Start by shutting down all instances of firefox completely. Then bring up a Terminal window and type:
firefox -no-remote -ProfileManager
You'll get the small Profile Manager window. Create a new profile, named 'pandora'. You may, if you wish, clean it up. I did all of the following:
  • moved all the controls to the menu bar line at the very top (right-click a blank space and "customize...").
  • hid all the toolbars.
  • edit > preferences and changed the homepage to http://pandora.com/
Now quit from that firefox window. Once again, run:
firefox -no-remote -ProfileManager
Firefox makes your latest profile the default in all situations. To fix that (you don't want Pandora as your default browser): Select 'default' (or your original profile) from the window that just appeared, and let firefox open. Now your Default Firefox Icon won't open Pandora.

Make a Launcher

Right click on your Gnome menu panel, and "Add to Panel..." Select Custom Application Launcher. Enter the following:
  • Type: Application
  • Name: Pandora
  • Command: firefox -no-remote -P pandora
  • Comment: Opens in a separate Firefox process
(Only the Type and Command must match what I show above.)

Leave that window open for now. Re-launch Firefox using your Default Firefox Icon.

Let's get a pretty icon for the launcher you're about to make. In Firefox, visit:
http://pandora.com/favicon.ico and save the file as ~/.mozilla/firefox/*.pandora/pandora.ico

Now, go back to your New Launcher window (you didn't close it, right? :) and click on the springy-looking icon to change the icon. Choose the icon file you just saved as your icon. Now finish/OK until you are out of the New Launcher window and you have a new launcher.

Bonus Compiz Step: Place Windows Support

The Compiz "Place Windows" plugin lets you put windows on a particular workspace automatically. I want Pandora windows to automatically be placed on desktop 8, and other Firefox windows to automatically be placed on desktop 3. This was the trickiest part of the entire process.

This isn't an explanation of how to use Place Windows (find that elsewhere). This is an explanation of why using Place Windows for the tricked-out Pandora profile is difficult, and how you can fix it.

Problem: Compiz requires you to identify the windows you want to place by window Class, Name, Title, Type, or a few other things. For all firefox windows, Class, Name, Type and so on are the same; so if you want some firefox windows to go one place and some to go another, well, you can't do that with Place Windows unless you can differentiate them by title. But firefox always starts with the same title: "Mozilla Firefox". Compiz only looks at the initial title when it tries to place a window.

Solution: A plugin called MozFox. This is a version of FireSomething, that plugin that lets you randomly name your browser "Fireslug", etc. Bring up your Pandora profile using your new launcher icon, and visit the link above. You will have to create an account and log in there because this is currently an "experimental" plugin.

Install the plugin, and let the Pandora profile restart. When it comes back up, Tools > Addons > MozFox > Preferences. You will have to manually delete everything in the three lists. (You can select multiple to delete at once, then right-click and "delete". This is a pain in the butt.)

Add to each list: "Pandora", "Fire", "fox".

Now in the Place Windows plugin, set it up to match "title=Pandora.*". (You must have Regex Matching on.) Close out of that window, and restart Pandora. When it comes back up, Compiz will automatically position it where you told it to go!


Saturday, March 22, 2008

iPod video for Linux

Google will tell you that you can convert video to iPod format in Linux using only VLC. When lots of people tell you something on the Internet, and post howtos, this is often a good sign that lots of people on the Internet are idiots. This is one of those times. None of the howtos worked; I was getting either a corrupt file or no audio. I am clearly not the only one, because every single howto I found that explained how to use VLC to transcode video, and there are quite a few, ALSO had commenters saying "um, the audio doesn't work".

I finally discovered a solution, though, so it's sharing time. Note that this is probably unnecessary if you just want to watch video podcasts, because gpodder works fine for that. For non-podcast video, you almost always need to do a conversion.

(Using Ubuntu, version HammurabiHardy Heron.)

For this meal, you will need these ingredients (aptitude install ..):

avidemux
avidemux-cli
vlc
faad
faac
(every gstreamer plugins package you can find in main or in universe / multiverse)
gtkpod-aac

The avidemux-cli package is optional; handy if you want to convert a whole directory full of files. Note that you are going to install vlc, because it is one of the few players that will reliably play back these files. You want it so you can test your output files before installing them on your ipod, but it may be considered optional.
  1. Open up the avidemux GUI, and open the file you want to convert.
  2. Select menu item Auto > IPOD
  3. Confirm that video is being encoded with XVID4. Optionally change the video bitrate to 1024 through the Configure button.
  4. Audio encoding will still show "Copy". Change it to "AAC". Confirm through the configure button that the bitrate is 128.
  5. Confirm that Format is MP4.
  6. Save. Encoding will begin.
Optional: Batch Conversion. When done, you may save this as a script with "File > Save Project As ..." which allows you to do this from the command line. However, I have already done the work for you. To run it from the command line, get the shell script and .js file from http://wiki.goonmill.org/AvidemuxScript. If you save the shell script as "mp4", you can run it in a directory of AVI files, as

mp4 *.avi

Test the converted file in VLC, making sure video isn't crappy and audio exists.

Now copy it to the iPod. You need gtkpod-aac installed for this, or you will get errors saying "compile gtkpod together with yadda yadda". The package gtkpod-aac contains a binary that is already so compiled. You can simply plug in the iPod, choose your model from the supported list in gtkpod, find the iPod in the tree at the left, pick "video inbox", and Add File there to add your file. Save to copy to the ipod, and Eject the ipod.

Sunday, October 14, 2007

Vim: Run the current buffer as Python code

This uses the handy preview window feature of Vim. Flagging a window as a preview window is useful because you can use pclose! to get rid of it, meaning you can reuse that vim real estate over and over for commands that produce output, and the output has to go somewhere.

fu! DoRunPyBuffer2()
pclose! " force preview window closed
setlocal ft=python

" copy the buffer into a new window, then run that buffer through python
sil %y a | below new | sil put a | sil %!python -
" indicate the output window as the current previewwindow
setlocal previewwindow ro nomodifiable nomodified

" back into the original window
winc p
endfu

command! RunPyBuffer call DoRunPyBuffer2()
map <Leader>p :RunPyBuffer<CR>


<Backslash>+p is mapped to run the current buffer through a Python interpreter. The output appears in a new window below the current one.

The code to do this with another interpreted language, such as bash, is almost identical and left as an exercise for the reader.

Saturday, October 13, 2007

Thumbnails from SWF video

I don't know if this'll ever be useful to anyone, but it was information I needed, and it was extremely hard to come by.

Here's how you extract a thumbnail from an video stored in an swf file. I'll leave the apt-get install dependencies as an exercise to the reader.

#!/bin/bash
SWF="$1"
AVI="${SWF/swf/avi}"
JPG="${SWF/.swf/%d.jpg}"
flasm -x "${SWF}"
mencoder -endpos 5 "${SWF}" -o /dev/null -nosound -ovc xvid -xvidencopts pass=1:turbo

mencoder -endpos 5 "${SWF}" -o "${AVI}" -nosound -ovc xvid -xvidencopts pass=2:bitrate=1600

ffmpeg -i "${AVI}" -an -ss 00:00:03 -t 00:00:01 -r 1 -y -s 120x90 "${JPG}"


BTW, this also converts the SWF to an AVI along the way, so if that's useful to you, great. (Modify the endpos and get rid of -nosound if you really wanted the AVI.)

Sunday, August 19, 2007

Expunging a problem file from Mercurial repo

Mercurial is almost the perfect version control system: fast, lean, distributed, easily extensible and reliable. It works by copying an entire repository, compressed, every time a clone needs to be made. This is much more efficient than it may sound, and the time it takes to transfer a Mercurial repo is quite comparable to the time it takes to checkout a Subversion repository.

The only time the system breaks down is if you lose your head and commit a large compressed database to the repository. Since I'm still learning how everything in Mercurial works, I did this accidentally, and made several commits before I realized my repo was ballooning out of control due to this one file with only 7 commits.

Here's how you recover.
  1. Make sure you fix this before anyone clones your repository from upstream. The following procedure renders your repository unusable to anyone working on their own clone of it; they will have to clone a copy of the results and start fresh.
  2. Get a list of all the commits that changed your file.
    $ hg log -M -r0:tip --template "{rev} {files}\n" goonmill/srd35.db.gz
    11 goonmill/srd35.db.gz
    52 goonmill/srd35.db.gz
    59 goonmill/srd35.db.gz
    99 goonmill/alter3.sql goonmill/srd35.db.gz
    115 goonmill/srd35.db.gz
    160 goonmill/srd35.db.gz srd35.odb
    189 goonmill/srd35.db.gz
  3. Export the entire repository as patches; use hg export to extract each one to a separate file.
    $ mkdir ../Goonmill-revs; hg export -g -o ../Goonmill-revs/%r:%n-of-%N $(hg log -M --template "{rev} ")
    (Hundreds of patch files created)
  4. Delete the ones that you found in step 2. The reason I had you print {files} in that step was so that you could check to see if any other files would be affected by the new hole in the history. For example, my commit 99 above changes another file. Instead of deleting that revision, I will edit the patch so only the patch to the sql script is committed.
  5. "hg init" a new repository and use "hg import Goonmill-revs/*" to recreate.
You now have a new repository with the same revision history as before, but with your one problem file not contained in the repo history. Next time, commit that database uncompressed, because Mercurial does efficient binary diffs that don't work very well on compressed files but work great on uncompressed structured binary files like a database.

Saturday, March 03, 2007

VMware Upgrades Blogdoc

Regarding my last post, I wanted to make note of a few issues I had, in blogdoc format.

VMware has a cross-platform software package which helps guests function a little better. It's required to get certain things, such as hgfs (filesystem for sharing files between guest and host) and cut/paste support between guest and host working.

Kernel upgrades, and therefore distro upgrades, always cause problems with this stuff. Here's some earned wisdom on these upgrades. If you're planning to upgrade a VMware Ubuntu guest, I recommend printing it out, because you won't be able to Google for these answers if you wait until after you've upgraded ;-).

Networking Missing?

The first thing you should do after such an upgrade is "sudo modprobe pcnet32" if it isn't already installed. This driver is required for basic (non-vmxnet-accelerated) networking and you'll be pretty frustrated without it. If I understand things correctly, vmware-install.pl puts pcnet32 into /etc/modules.conf for you so it'll get loaded at the right time; but if something is wrong with your vmware-tools installation, it may not be present and you have to load it manually. (It comes with your kernel, you don't need vmware tools at all to use it.)

Network Comes Up but Wrong IPs?

Kernel (or maybe it's just distro) upgrades always seem to re-enumerate the network devices, if your guest has more than one. In plain English, your adapter "eth0" will become "eth1" and vice-versa. This applies to you if, for example, you use both bridged networking to get onto the Internet from the guest and a private host-only network. If you only have one network adapter in your guest, this probably isn't a problem for you.

To solve this, you may have to update /etc/network/interfaces and /etc/dhcp3/dhclient.conf and adjust for the renumbering of your adapters.

vmware-install.pl Didn't Work

You have to re-run this script every time you upgrade the kernel. You will probably also have to run it if you upgrade the vmware application itself. You will certainly have to run it if you upgrade your distro. The fastest way to run it is "sudo ./vmware-install.pl defaults", which doesn't ask you any questions.

The two most common problems I've had here are:
  1. Script won't even attempt to compile because it can't find your Linux source. Just run "sudo apt-get install linux-headers-$(uname -r)" first, and then "sudo ln -sf /usr/src/linux-headers-$(uname-r) /usr/src/linux". Re-run the script, and it will find the Linux sources this time. This step has to be done on every kernel upgrade, for reasons that will be obvious to anyone who has read this far.
  2. Some kind of compile error. The most recent one I had was that the 2.6.20 kernel wasn't yet tested with the vmxnet source, and a "wrong number of arguments" bug cropped up. I found these instructions to patch this problem. (I'll add this to the bottom, in diff -u format, in case the forum post drops off somehow.)
I want to make note here of a community-maintained patch which makes vmware work on any known kernel version. This is apparently known as the "any-any-update" patch.
Resolution Woes

If you have a supported default resolution for your guest, you should be fine with "./vmware-install.pl defaults", which I believe will read your old config file and plug in the same resolution value you specified the first time you installed the tools. However, I have a funky monitor resolution: 1280x768. This is not one of the choices the script gives you, so I always have to manually tweak. Fortunately, it seems to be enough to just copy a 1280x800 in xorg.conf to 1280x768 and then change the default resolution in your "Screen"/"Display"/"24"-bit depth section to "Modes" "1280x768".

Mouse

To get a mousewheel to work, I changed protocol "ps/2" to protocol "imps/2".

The 2.6.19/2.6.20+ vmxnet Patch

--- vmxnet.c.orig 2007-03-01 14:04:27.000000000 -0800
+++ vmxnet.c 2007-03-01 13:57:29.000000000 -0800
@@ -1055,7 +1055,11 @@
vmxnet_netpoll(struct net_device *dev)
{
disable_irq(dev->irq);
+#if LINUX_VERSION_CODE <>irq, dev, NULL);
+#else
+ vmxnet_interrupt(dev->irq, dev);
+#endif
enable_irq(dev->irq);
}
#endif /* VMW_HAVE_POLL_CONTROLLER */

Tuesday, May 02, 2006

Quickie screen tip: always running screen

It's easy to set screen up so it automatically resumes your last session as soon as you log in. If you don't like to manually type 'screen -<args>' after logging in, and you don't like screen sessions getting stranded by a killed terminal, try the following in your shell's user rc/profile:

if ! echo $STY | cut -d. -f1 | xargs ps -p 2> /dev/null | grep -i screen; then
exec screen -RR
fi


Credit to danieldg on Freenode IRC for this approach.

Monday, April 03, 2006

Nastier Than a Bear Holding a Shark

I have just finished setting up a Windows XP Pro box as a Subversion server.
This is not a fun way to spend an evening. Before you read this let me say
the following things as loudly as possible:

WINDOWS IS NOT A RECOMMENDED PLATFORM FOR A SUBVERSION SERVER.

USE LINUX OR, what the hell, OS X would probably be okay too.

I will talk a little about why Windows is so bad later on. However, Windows
is what we had, and we needed a repo up ASAP, so I made do. Fortunately
Subversion keeps a database, and we will be able to copy that database to a
non-toy computer in the future. In the meantime, here's how to make it
happen:

Install SSHd

  1. Install Cygwin. Make sure the OpenSSH [1] package is checked. You
    probably want to add a few other things. My list of extras usually
    includes zsh, zip, unzip, gcc-mingw, patch, and
    openssh.
  1. Create local user accounts (or domain user accounts) for all the people
    you want to have making commits. In Windows XP pro, the default
    is accounts with no password. This is dumb. Set passwords on all
    the accounts. You can't log into SSH if you don't have either a password
    on the account or a public/private authorized_key pair on the account.
    Make these accounts local administrators. If you don't think you can do
    that, then don't even bother with the rest of this solution. You want Unix
    instead.
  2. Launch the Cygwin prompt at least once. Run:
    mkpasswd -l > /etc/passwd; mkgroup -l > /etc/group(In a Windows NT Domain/ADS environment, you probably want -l -D.)
  3. Add C:\cygwin\bin to your global PATH environment variable.
    Yeah, just do it. It's in My Computer > Proprties > Advanced >Environment Variables.
  4. Then run ssh-host-config. You probably want to answer 'yes' to
    everything.
  5. net start sshd or start the service from the services applet.

Test SSH

I suggest logging in as one of your users using just ssh, and make sure you
see some kind of shell.

Install Subversion and Make svnserve Your Bitch

  1. Install Subversion.
  1. Create a repository somwhere, using svnadmin. Now, there're a couple
    of things that need to be true about this repository.

    • It needs to be owned by the Administrators group, including
      subdirectories/folders.
    • It needs to be writable by them, including subdirs/folders.

    Both of these things are accessed through the Security Tab, and then the
    Advanced button, when you right-click on the folder. If you don't have a
    security tab, go into Tools>Folder Options>View> and uncheck "Simple
    File Sharing".

    You may be scratching your head and thinking "But all your users are
    administrators.. why can't they write to the folder regardless?" You, my
    friend, understand Unix. Unfortunately, you don't understand Windows,
    wherein permissions activate and deactivate apparently at random.

  2. If you've ever configured svn+ssh access to a repository on Unix, you know
    about the umask problem as described in the box at the bottom of this
    svnbook page. There's a different problem on Windows, but it's solved
    the same way, with a very similar shell script.

In Cygwin's sshd, you get a very limited set of directories in your PATH.
It does not match the list in your global environment; it is apparently
compiled into sshd. There are a couple of different ways to modify it, but
they aren't global and/or they don't apply when using ssh to tunnel svn.
The solution is this shell script:

#!/bin/sh
cd /cygdrive/c/Program\ Files/Subversion/bin
# change the following line so that -r points to where you are
# keeping your subversion repositories. This keeps your URLs shorter.
./svnserve -r C:/SUBVERSION "$@"

You need to name this script svnserve with no extension, put this into
your C:\WINDOWS directory, and, using Cygwin,
chmod a+x /cygdrive/c/WINDOWS/svnserve.
What you are doing is routing around the broken PATH and directing svn to
run the binary from its own directory. Note also the argument -rC:/SUBVERSION. That's a FORWARD SLASH. This argument is optional, but
it's extremely handy. Use it when you want shorter URLs.
For example, if you did:

mkdir C:/REPOS
cd C:/REPOS
svnadmin create Foostuffs
If you want your url to be svn+ssh://foostuffs.com/Foostuffs, then use
-r C:/REPOS.
  1. Make svnserve.exe in C:\Program Files\Subversion\bin executable by
    everyone. It isn't world executable by default.

Test svn

Try svn co svn+ssh://foostuffs.com/Foostuffs and see what happens. If
you're trying this on a Windows client, you will want to find somebody else's document
on setting up svn+ssh client access from Windows.

Why Windows Sucks

Well, we've already covered the problems with the broken PATH on Windows,
which forces us to use the C:\WINDOWS\svnserve kludge, and the requirement
that your users be local Administrators. In addition to that, this
configuration is just plain flaky and slow. Performance is sluggish or
sometimes just stops altogether, for reasons from the computer going to sleep
(watch out for that) to having the wrong "Optimize for background apps"
setting, to just weird behavior in general. SSH is also slow under Cygwin,
which makes SVN slow.

Tomorrow I'll be setting up Bugzilla on the same box, assuming this pile of
cards hasn't fallen over by then.

[1](Why am I not using conch? Conch doesn't work on Windows. If
you disagree, feel free to send me instructions or patches and I
will try them. Really. I don't like Cygwin sshd.)

Thursday, March 09, 2006

Quickie setuptools

Here's a one-liner shell script to install PJE's setuptools, for Googling goodness. Nearly everyone, I hope, has wget. (There's also a version that uses only python by importing urllib2. I like this one.)

wget -O - http://peak.telecommunity.com/dist/ez_setup.py | python -

If you're on Windows, you probably then want to do ..

cd c:\python24\scripts
ren easy_install-script.py easy_install.py
exemaker easy_install.py


Edit:
Look for "easy_install.exe" in the C:\python24\scripts directory if you're on Windows. You probably want to copy it to somewhere in your binary PATH.

Wednesday, February 22, 2006

Down with the Man

Ringtones are outrageously expensive. My stepdaughter wanted to put some custom ones on her Razr which you might not even be able to download, and I happen to be decent with Audacity. I wanted to put a note on here so the next visitor to google.com would do better than I did.

Make sure your MP3 file is encoded at a 22kHz sample rate.

If you don't do that, you will get the message "File type not recognized" from your Motorola Razr phone.

You will see a lot of stupid nonsense about making an mp3 file and then renaming it to .MID. I didn't do that, I just figured out where the project sample rate button was in Audacity. (Hint: It's in the bottom left corner.) Then export as an MP3, transfer to the phone using one of many methods, and off you go.

Monday, November 28, 2005

Stupid Nevow Tricks

Actually, this stupid Nevow trick is pretty damn clever.

So, you want to write a db-backed app with a web frontend. You plan to have, let's say, 60k users registered and you want them to be able to send one another a message via a webform by entering first and last real name as the recipient.

This could be a real pain. In the old days you'd need a separate form just to look up the user in the database first, then you can start the message sending bit. Not any more.

Check out this demo. This uses Divmod's Axiom as the database backend, and Nevow's new LivePage API called "athena" to drive the frontend. I'm very impressed with the power and performance of the Axiom database so far, and it holds up beautifully in this example. LivePage works wonderfully with it for a very compelling combination.

What does it do?


The demo database contains a single table with 10k names in it. (Locally I experimented with a 60k name database before trimming the fat for the checkin; performance did not suffer one iota.) The names are randomly generated from US Census data. When you start the demo and point your web browser at it (see the README.txt) you'll see a field. Type a name into this field; if part of what you typed has a database hit, you'll see the possible completions:



.. until you get a unique match, at which point it fills in your text field for you:


You can, of course, also click a name in the menu.

What's neat?


A lot of the interesting part is in JavaScript, so make sure you look at typeahead.html. In particular, selectRange handles selecting the untyped portion of the text field, and complete handles filling in the field and the options in the select element.

Nevow


The Nevow stuff is pretty straightforward. You create a LivePage instance as your page. Then you create elements capable of talking to the server as LiveFragments, and stuff them into the page. Here's the whole LiveFragment for the input field. This renders the input box and has a single method to handle a callRemote from JavaScript.
class TypeAheadFieldFragment(athena.LiveFragment):
docFactory = loaders.stan(T.input(type="text", id="typehere", **athena.liveFragmentID))
allowedMethods = { 'loadCompletion' : True }

def loadCompletion(self, typed):
assert type(typed) is unicode
if typed == u'':
return None
q = Person.name.like(typed, u'%')
matches = theStore.count(Person, q)
if matches <= 10:
return [p.name for p in theStore.query(Person, q)]
else:
return None

The matching JavaScript is nice and simple:
var node = $('typehere');
var typed = node.value;
var d = Nevow.Athena.refByDOM(node).callRemote('loadCompletion', typed);
d.addCallback(complete);


(Again, be sure to read the rest of the JavaScript on the page.)

Axiom


Axiom is damn bloody simple to use. The demo doesn't really show off much in the way of Axiom features, but it does show off how simple your ORM code can be. A couple of things stood out for me.

One was that Axiom's latency is very very low indeed. At the place where I do the query I treat it as non-blocking, and for me on my one laptop it had better be. In fact, if this demo works at all it has to be non-blocking; a person typing would quickly get ahead of the database otherwise. The speed of Axiom at returning a list of 10 names from a list of 10k names is impressively subsecond. Now, I'll be the first to acknowledge that what works nice and fast for me on my laptop might not be so fast when you've got 10k live users banging away on their keyboards, but it establishes that this low latency is at least possible to achieve. I'll leave the macho scaling stuff to others.

The other was that Axiom's batch performance is pretty darn good. I left in the batchInsert function in namedb.py. The function I used to to import the original 60k names took about 7 seconds to run using Store's transact method. Be sure to use .transact() if you're going to be manipulating a bunch of rows at once though; before someone (I think JP?) pointed out that Axiom had transactions, I was trying to do the 60k inserts by creating 60k Items at one per second. It's worth highlighting that the transact method is so simple to use that as soon as I knew about it I was using it with no explanation whatsoever. Simplicity is beauty.

MochiKit


Another surprise winner here was MochiKit. I've heard so many good things about it, but until now only used it indirectly through LivePage. Even if I didn't already know who wrote it, it would have been obvious that this JS library was written by an artful Python hacker. I made use of MochiKit.DOM for constructing the items in the select widget, but I was particularly impressed with MochiKit's "bookmarklet debugging". Getting complex scripts to work in JavaScript is nothing short of horrifying for me, but having the log messaging and viewing facilities in such a nice format cut in half the time it would have taken me to get the JS right.

Tuesday, June 14, 2005

Vellum is a Gnome Canvas Application


I have successfully made the switch from PyGame to Gnome Canvas in Vellum. While it has been by no means seamless, most of my problems were due to misunderstanding how it works, and general unfamiliarity with graphic programming in general and making drawing tools in particular.

Vellum maps now have scrollbars, which I never managed to implement in PyGame because it doesn't have a scrolling concept. It's not really meant for use with a toolkit, so this shouldn't be surprising. With Gnome Canvas, the scrollbars were the source of most of my problems. I was using GTK's Viewport widget, and dropping my Canvas in that to get scrollbars. In short, when it's in a Viewport, Canvas isn't double-buffered and it doesn't get the advantage of its infinite-width scrolling areas. In fact, resizing large scroll areas inside a Viewport is VERY slow. It was only when I realized that Canvas has native support for moving scrollbars around that I threw out the Viewport widget and got the performance that Canvas promises. Now the app is very fast, drawing is double-buffered and graphic operations take place near instantly.

After adding the working scrollbars I gave Vellum two new tools.
  1. There's a Pan tool, selectable by clicking on the hand icon in the toolbar. Pan allows you to drag the map around freely without having to reach for the scrollbars. This gives you finer control over what portion of the map you're looking at. I like Pan tools.
  2. There's a Magnify tool. This works like the magnify tool in Gimp. Click the Zoom button, then click somewhere in the canvas and drag. You get a rectangle which shows you what the inscribed area will be. When you release, the Canvas zooms in and centers on the area you inscribed with your rectangle. This tool will be one of the ones I use the most, as it allows a user to focus on particular aspects of a map or combat.
I will be adding keyboard shortcuts for all these tools in due time. For now I'm going to be taking pride in the fact that I've added a new application area to my repertoire of things I know how to program: Graphic editing applications.

This also represents another minor milestone for Vellum. The GUI actually does something useful. Granted, it's basically a crippled image viewer at this point, but look at it in context. There's now a way for a GM to share a map with his clients: create a .map file and put it in the server. The clients can connect and get that map, which happens automatically after they type in the right address. And everyone can scroll around or zoom to parts of the map that are interesting, which gives them a shared frame of reference for IRC-based games. It's certainly not earth-shattering, but it's something to dogfood.

I promised in my last post to provide more details on how I got Gnome Canvas to compile on Windows. There's really no special magic. If you want to see how it's done:

svn co svn://svn.berlios.de/vellum/sandbox/corydodt/canvas

There's a shell script in there to build libgnomecanvas and another one to build gnomecanvas-bindings. Ignore the libgnomecanvas one, as the binaries are checked into that directory as well (no special magic is required to build the C library runtime, it turns out). README.txt will fill in the rest. You just run the shell script, which invokes the code generator (I know, I didn't write it), compiles the two C files and links them.

Edit: Original post said I switched from "PyGTK", not from "PyGame" which is what I meant.

Saturday, November 13, 2004

Making XML Suck Less

XML sucks. It's axiomatic these days, except to those corporate code-jockeys who happily implement everything their pointy-haired bosses tell them is buzzword compliant. (Many, I'm sure, do it to keep their job; just as many, I feel, probably do it because they believe their own bullshit.)

One of the corrollaries of "XML Sucks" is "the great tools are what make XML suck less." And the pro-XML faction firmly believe that XML is what makes it possible to implement those great tools. I hold to another point of view. XML Sucks, the tools for processing XML suck, and XML makes it harder to implement those tools. But it is possible to write tools that don't suck, and that would make XML suck less.

There are a few bright points. Frameworks like Nevow make XML generation both elegant and idiot-proof. Tools like uTidylib and BeautifulSoup make it possible to clean up other peoples' garbage. And some of the standards for XML are actually useful. CSS is simply an awesome way to change the appearance of a rendered document. Relax-NG provides a way to define a valid XML format without the awfulness of a DTD or the brain-numbing pedantry of a schema, and even has a compact form. (An aside: the word "compact" gets applied to a lot of XML-related technologies. It's actually code for "you don't have to use that awful XML crap to write this, you can use a more sensible syntax instead." Examples: XSLTXT, Relax-NG compact.) And then there's XPath which provides a concise way to get a set of nodes from a document, and is extensible.

Extending XPath

I want to use XPath on a project I'm working on. (If it goes anywhere, I'll blog about that too.) To use XPath, you generally have to provide some extension functions. This is because XPath's set of core functions, while sensible, can't do some basic operations. That other standards support. For example, you can interact with a CSS stylesheet by multiclassing nodes, like this:
<div class="important">1. You should turn your car on before attempting to drive it.</div>
<div class="dangerous">2. If you drop a lit match in the gas tank, bad things will happen.</div>
<div class="important dangerous">3. Don't drink and drive.</div>
<div class="dangerous important">4. Use tire chains when driving on an icy road.</div>
<div class="not-important">5. Your glove compartment can be used to store maps.</div>

This applies both the important and dangerous styles to the node last node, whatever that means. In CSS, it probably specifies an appearance, but other applications acting on the same file may also want to get those classes. XPath can't select a node that uses classes in this way. Consider how we might select the nodes with class 'important'.
//*[@class='important'] # only matches (1), not (3) or (4)
//*[starts-with("important", @class)] # only matches (1) and (3)
//*[contains("important", @class)] # matches (1), (3) and (4) .. oops, and (5) too.

After trying libxml2 and having it crash the Python interpreter on me (this is the second time I've given it a try; there won't be a third) I installed pyxml 0.8.3. First thing I had to do was figure out how to implement an extension function for using regular expressions, such that:
//*[func("\bimportant\b", @class)]
returns the nodes I want, by regular expression selection.

First I learned that namespaces in XPath are, quite naturally, XML namespaces. Therefore, to call func I needed to define a namespace, analogous a Python module, for it to live in. I chose an arbitrary URL at a hypothetical developer.berlios.de website for the hypothetical project I'm working on: "http://bypath.berlios.de/2004/11/bypath". This string is itself the namespace; when you want to refer to the namespace, you use an alias, called a prefix.

Then I wrote the first version of my extension function:
def simpleSearchRe(ctx, expr, input):

return re.search(expr, input) is not None

Some things to note. simpleSearchRe takes three arguments, not two. The first argument is the XPath evaluation context object, which pyxml always passes to extension functions. simpleSearchRe returns True or False, not a string or a node or a nodelist or something else; so it can be used to filter nodes in an XPath expression in exactly the manner I demonstrated above.

Once you have an extension function and a namespace, making the actual XPath binding is simplicity itself. The code:
xpath.g_extFunctions.update({...})
The dict you pass maps a (namespace, function-name) tuple to the actual function. Example:
xpath.g_extFunctions.update({("http://bypath.berlios.de/2004/11/bypath", "simple-search-re"): simpleSearchRe})

Unfortunately the above implementation of simpleSearchRe is too naïve for my desired use. For example, this works:

//*[by:simple-search-re('\bimportant\b', string(@class))]

but not my example. This returns a RuntimeError:
//*[by:simple-search-re('\bimportant\b', @class)]

Since developers familiar with XPath will expect both to work, I had to dig deep to find out what was happening. Finally I learned that @class becomes a list of nodes with one item, and my function tries to treat it like a string. There's a big gotcha: a bug in pyxml makes it appear that my code isn't even being called, because simpleSearchRe does not appear in the traceback call stack anywhere. In fact what's happening is my code is raising a simple TypeError, and pyxml then eats that error and issues its own, in a different part of the code.

This revised version worked:
def simpleSearchRe(ctx, expr, input):

# convert a node, nodelist or a string to a string
input = xpath.Conversions.StringValue(input)
return re.search(expr, input) is not None

The full module, demonstrating how one extends pyxml's xpath with new functions:
from xml.dom import minidom

from xml import xpath
import re

# define a namespace
BYPATH_NAMESPACE = 'http://bypath.berlios.de/2004/11/bypath'

# define a function capable of coercing its arguments to strings and
# operating on them
def simpleSearchRe(ctx, expr, input):
# convert a node or a string to a string
input = xpath.Conversions.StringValue(input)
return re.search(expr, input) is not None

# add the function to the global list of extension functions, bound to
# an xpath name in an xml namespace
xpath.g_extFunctions.update({(BYPATH_NAMESPACE, 'simple-search-re'):
simpleSearchRe})

# test doc
doc = minidom.parseString('<y class="aa bb"><x class="b a c"/></y>')

# create a context which knows about our namespace
ctx = xpath.CreateContext(doc)
ctx.setNamespaces({'by':BYPATH_NAMESPACE})

xeval = lambda expr: xpath.Evaluate(expr, context=ctx)

# tests
print xeval('//*')
print xeval(r'//*[by:simple-search-re("\ba\b", string(@class))]')
print xeval(r'//*[by:simple-search-re("\ba\b", @class)]')

Sunday, November 07, 2004

Experiments in Writing an Offline Blog

I did a very strange thing yesterday. I wrote a blog. At least, it might look like a blog if you allowed certain liberties, such as the fact that most blogs don't force you to upload a static HTML file in order to publish a post. I will try to explain why I did this, but I'm going to tell you up front that the explanation will not be satisfying and largely amounts to "because I could". I will also explain how I did it, and that will be more interesting if you have a problem in the same category as mine.

The Problem

As the author of PBP, I have to keep the front page updated with news items. Until now I've been doing this by editing an HTML file by had. Well, I hate editing XML by hand, and while HTML at least is supported by editors, they never get the markup quite right. The editor I'm using right now to generate this blog entry is doing ugly things like using font style attributes and making guesses about where my line breaks should go. For prose on my blog, that's acceptable, but I expect a certain precise format for my "professional" projects.

Frustrated with the errors I was constantly making getting the news file just right, I decided to make news entries something much simpler to type. I've been doing a lot of successful experimenting with YAML lately, and although I had previously decided it was more appropriate for "data" formats than "document" formats (whatever you think that means) I figured news entries were brief enough and structured enough that YAML might be appropriate.

I also needed a way to render the YAML to HTML. Well, I already had this, in Nevow. Of course, you can't run your own webserver on BerliOS, and Nevow isn't really appropriate for CGI yet, and in any case I have no need yet for this level of control. I just needed a way to generate the static HTML file that I was at the time generating by hand.

When I was done I wanted a tool I could run on a YAML file, which would plug my news items into a template and give me an HTML document suitable for posting on the web. Then in the future, when I want to edit my news items, I only have to edit a YAML file. As YAML is much more readable and writeable than XML and need contain only the dynamic part of the page (the news items) instead of all of it, this would less onerous a chore. As a bonus, if I made a formatting mistake, I should get a parse error instead of hiding the problem behind a web browser's sorta-error-correcting rendering engine.

The Implementation: An "Offline Blog"

The implementation for this is available in PBP's svn repository, which you can check out with the command
svn co svn://svn.berlios.de/pbp/trunk pbp
I will refer to parts of this by their location relative to the root of the working copy, so (for example) /doc/pbp.xhtml refers to svn://svn.berlios.de/pbp/trunk/doc/pbp in the repository and pbp/doc/pbp.xhtml in the working copy.

Step one was getting Nevow to render a static HTML file. Fortunately this is easy. There are two ways to do it. One is the newly-added Page.renderSynchronously method, which returns a rendered HTML file. The other is to start and stop a reactor and use plain old callbacks to finish rendering. The former method is fine if you are sure you'll never need to execute anything asynchronously while publishing the file (therefore needing real deferred handling), but I figured I would go ahead and use the reactor in case I ever made a GUI out of this.

First I designed a template: /doc/pbp.xhtml. I won't go into the details of Nevow templates, and this one is completely unremarkable. Only two interesting things will be plugged in: the latest released version, which appears in the template as <n:slot name='latest' />, and the news items, which appear as <n:invisible render='news' />.

Then I needed some data to put into the template. I had to learn up a bit on YAML, but I came up with /doc/news.yml with very little trouble. (Tip: To use >-style folding, you need to indent everything in the block, even paragraph separators. A paragraph separator is actually a line containing only n blank spaces between two paragraphs, where n is the number of spaces the preceding paragraph is indented.) The YAML document contains two items, "release:" which contains the version of the most recent release, and "news:" which is a sequence of all the news items, ever. Each news item in turn is a map of three items, "date:", "title:", and "c:" which stands for content. The important thing to see is how simple it is to add new items to this file, and how extremely readable the file is. And since it's done with YAML, I didn't have to write my own parser for the format, as PyYaml already exists.

To parse this file into native Python data types is just one call:
ydata = list(yaml.loadFile(yamlfile))[0]
yaml.loadFile returns a stream of YAML documents, and is designed for streaming data. I turn this generator into a list with list() and take the first (zero'th) item from it, as there is only one YAML document there.

To go with the template I needed a rend.Page subclass, which is news2html.Page in /doc/news2html.py. I pass ydata to the Page and the news items and release version are extracted in Page.render_news and Page.render_download.

Now there was just one problem left to solve. I wanted to be able to put links in my HTML documents, of the form <a href='...'>blah blah</a>.

A Secret Friend

I considered several possibilities for the problem of HTML linking. One was to have a sequence of links as an item at the top of the YAML document, and make references to that, but this would have significantly increased the work typing each entry, even the ones without links... and most of the entries do not have links. I chose instead to make a small concession to the Wiki way of doing things and write links like this: [I am the text around the href which is http://foo.com/bar].

To do this I needed a parser. Let it be said that I have never had any luck writing my own parsers for anything, and even extremely simple parses like this have stymied me in the past. I don't know but for some reason I suck at parsing. It can be said to be my Achilles heel, the thing I dread doing more than anything else.

Perhaps that will no longer be true. At the suggestion of deltab on IRC, I looked into an undocumented feature of Python's sre module, sre.Scanner. This provides a simple scanning interface which lets you define tokens with regular expressions--a common tactic in parsing-helper modules--and then bind them to callbacks. You create a Scanner object initializing it with a list of the tokens, in the order that they should be matched. There were only three interesting tokens for me: bracketed links, "[[" which I intended to allow as an escape for the [ character, and everything else. The code to emit <a> links is below:
def got_char(scanner, token): return token


def got_bracket(scanner, token): return '['

def got_link(scanner, token):
# strip start and end brackets
token = token[1:-1]

words = token.split()
url = words[-1]
rest = ' '.join(words[:-1])

return T.a(href=url)[rest]

tokens = [(r'\[\[', got_bracket),
(r'\[[^\]]+\]', got_link),
('.', got_char),
]
scanner = sre.Scanner(tokens)
...
scanner.scan(para)

If you look at my code in news2html.py, you'll see that I later on added scanning to fix double and single quotes, turning them into double and single “smart” quotes.

As finishing touches, I added the command line option --count to control how many news items will appear in the generated page, and allowed an additional command line parameter to control which template is used to generate the HTML page.

Next Steps

RSS! I added a --output command line option, and for my next project I'll be using news2html to generate a feed for PBP. Not that many people will notice or care, but when it's this easy why not make it available for those who do?

Thursday, November 04, 2004

apt-get install antidote

New Distro #73

OK, so I've started to play with Ubuntu. Kick-ass distribution. Installed without a hitch on a server, a desktop machine, 4 Sony laptops and one Dell laptop. And by 'installed' I mean: got the screen resolution, sound, and network cards right, first try, no human intervention. Two different kinds of wireless network cards in that group, and the server's network card is a funky dual Intel one that no other distribution has picked up yet out of the box. Knoppix couldn't even do that.

There's one small problem, and it is small (smaller than I thought it was, even): not all Debian packages are available out of the box. They only support a subset (a large subset) of Debian's recent vintage packages.

I needed winbind, first thing, and that package is not in Ubuntu. So I did what I have been told time and again never to do: I added the Debian sid archive to /etc/apt/sources.list and fired up Synaptic to install the winbind package. This went off without a hitch, but on my next upgrade I ended up upgrading hundreds of packages to sid, since sid is now slightly more recent than Ubuntu for very uninteresting reasons.

Oops

This messed up Ubuntu's nice desktop layout. Realizing I didn't want to keep it that way, I then removed the sid repositories from sources.list and then downgraded to the Ubuntu versions of a few of the desktop packages using an old apt trick:

$ sudo apt-get install gnome-system-tools=1.0.0-0ubuntu7

This was fine, got my desktop back to pretty again. I knew I still had loads of "foreign" packages installed on my system, but I resolved not to think about it too hard.

But pretty soon I needed another package. This one happened to be python-dev. The Python package structure in Debian (and therefore Ubuntu) is a rat's nest of weird, exact-version dependencies. If you try to mix repositories on Python, you will end up reinstalling hundreds of packages, even using the trick above. Wait, that's an exaggeration: in my case, the number was actually 76 packages that had to be reinstalled because I • already had Debian's sid version of Python installed and • needed to install one from Ubuntu. In order to use the apt trick above to fix this problem, I would have had to look up the most recent Ubuntu version of all 76 of those packages (using apt-cache showpkg) and then type each one on the command line. If I'm smart, maybe I could write a little Python code to help me build the list, but it would still be a monstrous pain in the ass.

I have torn my hair out on this issue many times before, when I did things like install Debian sarge and then install everything from backports.org, or try to add one sid package to a woody system and end up installing hundreds of unwanted packages. My conclusion has always been: you can't go back. You're stuck with your mixed repositories, and you'll end up upgrading everything under the sun. Hope everything keeps working right! :-)

You Can Go Home Again

Turns out that's not true. The solution turns out to be quite simple. There's a file called apt_preferences by the man page. It's in /etc/apt/preferences. It controls where you get packages from, and under what conditions, when you have multiple sources in sources.list. It makes filters by combining groups of three facts:
  • What packages the filter applies to, with "Package:"
  • Where those packages come from, with "Pin:"
  • How high a priority you want to set for the filtered packages, with "Pin-Priority:"
Basically you say, "for these packages, if they're coming from this location, install them with this much priority." You can, for example, use apt_preferences to set a priority of 0 which says "Don't install the package from here, ever." I wanted just the opposite, the most forceful priority possible. A priority > 1000 says "Install packages from this location, no matter what, even if they're less recent than the one already installed." Used like this, it forces Ubuntu to downgrade everything on your system to the version in Ubuntu:

Package: *
Pin: release a=warty
Pin-Priority: 1001

Once you've made this setting, just do an apt-get upgrade. I don't promise it'll be smooth as glass; packages aren't tested for downgrading, let alone downgrading across distributions. At worst you will have to run it a few times, and possibly manually select a few packages to remove completely to prevent conflicts, but it does work. I just used it to downgrade 466 packages from sid to warty. :-)

So then they tell me about the universe repository, which is where I should have found winbind. . .

By the Way

Don't forget to change your apt_preferences back to the way it comes out of to the box (missing). Just delete it when you're done. Otherwise, you won't get security updates, because warty/security doesn't match the Pin you specified in apt_preferences. Once you've gotten rid of the preferences file, do another apt-get upgrade and you'll re-get the most recent security updates.

Update


In addition to universe, I recently learned how to enable the multiverse repository and I feel it deserves a mention because useful things like browser plugins live there. Add this to your sources.list:
deb http://archive.ubuntu.com/ubuntu/ warty multiverse