« August 2006 | Main | January 2007 »

September 2006 Archives

September 26, 2006

Giving XMMS the "what for" with Perl (AKA, C developers, append to enums, don't insert)

I've always wanted a way to be able to remotely control my MP3 player that runs on my linux box, through some sort of web interface or something. So I took some time off recently and thought it'd be fun to hack something together to let me do this. So the first thing I do is search the CPAN for any modules that let me control XMMS from Perl. What I found was the Xmms::Remote set of modules which just tied itself with a little XS code to a C library that is meant for sending commands to XMMS.

To make a long story short, this didn't work for me because when I used this module from my CGI script running through apache on my linux box, it was looking for the apache users instance of XMMS, not MY instance of XMMS. Ugh! So I wondered how this library was communicating with XMMS and guessed correctly that there was a Unix domain socket open. A little "netstat -a" action revealed my socket endpoint at /tmp/xmms_myusername.0. So what the library does is find your username and builds this path when it tries to connect to the socket in order to send XMMS a command. So, my CGI script, behind the scenes, was actually trying to connect to /tmp/xmms_apache.0, which of course, didn't exist.

This lead to the realization that I could use this socket to send my own command to XMMS! Sweeeeeeeeet! So I cracked open the XMMS source code and found its mechanisms for accepting commands on this socket and implemented my code for sending them to it. In this source code, I found an enumeration that went a bit like this...

enum
{
     CMD_GET_VERSION, CMD_PLAYLIST_ADD, CMD_PLAY, CMD_PAUSE, CMD_STOP,
}

What this does is maps numbers starting at 0 to each of the names in the enum. So CMD_GET_VERSION is 0, CMD_PLAYLIST_ADD is 1 and so on. So this enum maps out the commands that can be sent to the socket.

Next I needed to figured out the protocol that is used to send commands and receive data back from XMMS. This was easily found with a little more digging. There are two C structs defined for sending and receiving....

typedef struct
{
     guint16 version;
     guint16 command;
     guint32 data_length;
} ClientPktHeader;

and

typedef struct
{
     guint16 version;
     guint32 data_length;
} ServerPktHeader;

So if you want to send a command to XMMS, you just need to create a ClientPktHeader, enter the values and send it to the socket and XMMS will see it and execute the command. In order to do things like this in Perl, you need to get cozy with the pack() and unpack() functions. With pack() you can pack values into memory, or in other words, create a C struct. In order to make a ClientPktHeader I just needed to do this...


my $data = pack( 'SSI', $xmms_protocol_version, $command, $data_length );

What this does is packs a C short (16 bits) or "S", followed by another one, followed by a C int (32 bits), for a total of 8 bytes, into memory. Once this is done, you can feed pack()s output to Perls send() function to send this data to the socket. Wonderful! Connecting to the socket is straightforward if you've done any socket work. The key is that it's a Unix domain socket, not a TCP socket.

Everything worked great, and quite honestly, I couldn't believe it did. However as I went on to implement functions to send XMMS commands, I noticed that some of them seemed to work and some didn't. One even crashed XMMS! I spent hours looking at my code and I was doing everything correctly. My solution? Watch TV for a couple days and forget about it. A few days later though, I came back to it and after a few hours realized that the enum that mapped out all of the XMMS commands must have been out of sync with what XMMS was actually expecting! Ugh! So when I was trying to call CMD_GET_VOLUME, it was actually calling CMD_SET_VOLUME and was crashing because it was expecting me to send it 8 bytes of data representing the new volume of the 2 stereo channels and didn't get it. The result? Segmentation Fault!

What had happened was that when my linux distro (Gentoo) built and installed XMMS it also applied patches to the source tree and made changes to that enum. So what read

enum
{
     CMD_GET_VERSION, CMD_PLAYLIST_ADD, CMD_PLAY, CMD_PAUSE, CMD_STOP
}

was actually now

enum
{
     CMD_GET_VERSION, CMD_PLAYLIST_ADD, CMD_SOMETHING_ELSE, CMD_DIFFERENT_CMD, CMD_PLAY, CMD_PAUSE, CMD_STOP
}

The result of this is that I was sending incorrect commands to XMMS without knowing it. I finally got the correct enum out of the patched XMMS tree and everything now works great.

The moral of this story? When you're writing enums that someone could possibly get their grubby hands on and start using, please, please, please append any changes to the end of the enum instead of inserting items in the middle of it! Of course you'd probably say, "Well, you shouldn't go and do stuff like you did, it's your own fault. You should know better than to use someone elses code that could easily change!" And you'd be right I suppose, but I don't care.

At the very least, even if this project is doomed to failure by unpredictable XMMS enums, it was still a fantastic learning experience and a lot of fun to boot.

P.S. I know I could have just used C or C++ and used the XMMS library that is meant for this sort of thing, but I really wanted to implement a pure Perl solution instead of something that needed a binary to work.

P.P.S. I also know that there is a xmms-shell program that lets you control XMMS from a command line, so I can SSH into my box and control it remotely that way. This works great and I highly recommend it.

September 20, 2006

A filesystem recurser in Python too

So I'm learning Python here and there and thought I'd reimplement my directory recurser with it. Again, I'm only learning, so there may be a better way to do this in Python. Of course I could use os.walk(), but that would defeat the purpose of creating my own recurser. Why am I doing this simple task? Just cuz. :)

#!/usr/bin/env python

import os, sys

def printDir( path ):
     "Print a directory and it's files recursively"
     
     print 'Directory: ' + path
     
     for item in os.listdir( path ):
          newPath = path + '/' + item
          if os.path.isdir( newPath ):
               printDir( newPath )
          elif os.path.isfile( newPath ):
               print item
          
if len(sys.argv) > 1:
     printDir( sys.argv[1] )
else:
     printDir( '.' )

Next up...Ruby...

September 5, 2006

A shorter filesystem recurser in Perl

Shortened it up a bit...

#!/usr/bin/perl use strict; use warnings;

printdir( $ARGV[0] );

sub printdir
{
     my $dir          = $_[0] ? $_[0] : '.';
     my $handle      = ();
     
     print "Directory: $dir\n";
     
     opendir( $handle, $dir );
     my @items = sort( readdir( $handle ) );
     closedir( $handle );
     
     map
     {
          -d "$dir/$_"
          ? printdir( "$dir/$_" )
          : print "$dir/$_\n"
     } @items[2 .. $#items];
}

And people call Perl just a bunch of line noise?! Bah, sissies! :-P

About September 2006

This page contains all entries posted to Avidity Software in September 2006. They are listed from oldest to newest.

August 2006 is the previous archive.

January 2007 is the next archive.

Many more can be found on the main index page or by looking through the archives.