Nov 16

IRC done right with ii

IRC is a guilty pleasure. Logically, I know I've never gotten any value out of it. It's largely vacuous chat. Hanging out on ##c on freenode, any reasonable lurkers like myself are mesmerized by the comedy of people who are a) students trying to get homework "help" or b) embittered veterans who enjoy helping people like Nurse Ratchet enjoys helping her patients.

Nonetheless, I can't stay away. I guess I like the activity clicking down my screen every few seconds - I feel important and connected (don't think about that too hard).

ii is the only IRC client that has made sense to me. Rather than try to wrap the protocol into a custom interface with a custom scripting setup, ii gives you the protocol as a filesystem, and lets you define your own interface & pluggability through standard tools or shell capabilities. And at < 500 lines of dependency-free code, it's light as a feather.

So, how to use ii effectively? You guessed it, I have a shell function! The function sends the output of one channel to the console, formatted my way (= no presence messages or timestamps). screen is used to set up multiple windows.

Sending a message requires echo'ing to a flat file, but since I'm a lurker it's fine with me if it's hard to accidentally spam a channel when I think a different window is focused (a sadly common thing in a tiling window manager).

Here's the shell function from my .bashrc

# Usage: jn \#\#c  -- or -- jn \#\#c irc.freenode.net
# First form defaults to irc.freenode.net

function jn {

        IRCNAME=aurous

        # init
        IRC_HOST=irc.freenode.net
        if [ $2 ]; then
                IRC_HOST=$2
        fi

        SERVER_ROOT=~/irc/$IRC_HOST
        CHANNEL_ROOT=$SERVER_ROOT/$1

        # Truncate old irc log to last few lines
        if [ -f $CHANNEL_ROOT/out ]; then
                tail -n 1000 $CHANNEL_ROOT/out > $CHANNEL_ROOT/outnew
                rm $CHANNEL_ROOT/out
                mv $CHANNEL_ROOT/outnew $CHANNEL_ROOT/out
        fi

        # Start ii for this server if not running
        #if   ! ( ii procs   get command-line                      started with same server
        if( [ ! "`pgrep ii | xargs -I xxx echo /proc/xxx/cmdline | xargs -I xxx grep $IRC_HOST xxx`" ] ); then
                ii -n $IRCNAME -s $IRC_HOST &
                sleep 1
        fi

        # Join the channel
        echo "/j $1" > $SERVER_ROOT/in
        sleep 1

        # Check that we're connected to the channel
        if [ -f $CHANNEL_ROOT/out ]; then
                # tail
                tail -f $CHANNEL_ROOT/out | 
                while read ln; do 
                        #        strip presence lns   strip date/time stamp           let grep hilite nicks
                        echo $ln | grep -v "\-\!\-" | sed -e 's/^.................//' | grep "<\(.*\)>"
                done
        else
                echo Failure, couldnt read $CHANNEL_ROOT/out, probably no such channel
        fi
}


Posted by Erik | Permanent link

Nov 09

Handing over the keys

A couple of years ago I was nuts about DotNetNuke. I followed the releases, learned all the little corners of it, and even wrote a to-do app for sale. Now, everything's changed. I don't even have Windows readily accessible, let alone the development tools that make hacking on DotNetNuke fun. This is fine with me - after tasting Django, I know now how elegant web framworks can be.

I've been committed to DotNetNuke, though, since I did a DNN brochure site for a friend who makes and sells high-end purses. I've hung onto some crappy shared DNN hosting, and secretly hoped that Meg wouldn't ask for anything too complicated. Raw deal for Meg and me both.

So, my weekend project was to move her site from DNN into Django. This has several advantages:

  • No database (with Django, you add the DB when you need it)
  • Much simpler maintenance.
  • An interesting technology
  • Sensible URL's
  • A wget will pull a working static site

Wget it?

Let me elaborate on that last point. Meg and I are considering "handing the keys" of the site over to another guy, for reasons entirely unrelated to technology. How do you do that?

It's certainly not right to just give the new developer, a stranger, your DNN credentials and say "Have fun" (and anyway, this wouldn't relieve me of my DNN hosting). It's slightly less cruel to do this with a Django site. Alternatively, I may convert the Django extract into PHP, the lowest common denominator. I'm starting to think that the humane thing to do is to zip up a static web site, and let them set up the templating in their favorite technology (and hosted elsewhere).

Which brings me to a flaw in DotNetNuke: You can't do this. You can't wget a site and expect to navigate through the returned content and have the same experience as navigating in the original site. DNN uses redirects and has a complicated URL scheme.

Django, which makes URL design a first-class business process, makes your site easy to wget.

The lesson

There's a moral in here, and I'm still trying to put my finger on it. Candidates:

  • Don't use convoluted web frameworks with required databases and crusty, redirecting URL schemes.
  • Use only the most popular technologies.
  • Design for transfer - use any technology that makes wget'ing the site into raw HTML easy.

Unfortunately, the world of web development is so diverse in terms of both framework selection and the talent/experience pool, that it's very difficult to make a site that's easy to transfer to a new guy with an unknown skill set.

Have you had an experience like this? What did you do to resolve it? What lesson did you draw from it?


Posted by Erik | Permanent link

Nov 03

The latest web development stack

I've been using a new web development stack

    For web technologies:
  • Django - A nice framework. After some work, I've learned how to leverage URL's and templating, without the cruft of models, and without the admin module.
  • jQuery - dang, this is a time-saver. Just learn it.
    And for editing:
  • Vim - Omni-complete is perfect for web development - it closes my HTML tags and intelligently suggests CSS keys and appropriate values. Also, I've learned how to use the :mksession to preserve an editing layout.
  • ies4linux under Wine to test IE6 concurrently with Firefox.
  • dwm - I fought with Awesome 3 long enough to jump ship. dwm does everything I want, and nothing more.

Posted by Erik | Permanent link

Oct 26

On simple APIs

I wrote about my effort to learn sockets programming properly. For two weeks, every day, I wrote a simple socket server in C, from memory as much as possible.

It paid off this last week at work, when I finished a draft of a proxy server that sits between an existing server and a Flash client. A proxy is both a server and a client, so all my new skills were used, albeit in C# not C. The nice thing about sockets is that the API is fairly standard for all technologies - Unix and Windows have similar C API's, and all higher level languages simply wrap around them.

I'm so glad I learned the C first, since the standard API is lean. There are about 6 - 8 functions to learn to use.

Had I attacked this issue starting with C#, I'm certain I would have failed. The "user-friendly" wrappers contain a lot of distracting material. Furthermore, the MSDN documentation for class members always includes a program that does eight things, instead of being distilled down to the one thing I want to do.

So, here's to the distillation of problems down to their simplest atoms. I'll take bind(), accept(), and send() over SocketInformation, SocketInformationOptions, SocketFlags, and SocketOptionName any day.


Posted by Erik | Permanent link

Oct 19

Maker Faire 2008

How much fun is the Maker Faire? My 5-year-old had as much fun as I did, making it a perfect father-daughter geek outing.

I missed the Arduino talk, which is doubly-sad since I really need a kick in the pants to take mine out of the box and use it.


Robot carnage


Tesla coils play the "Dr. Who" theme. Inhaling the ozone in the top row, my kiddo said "It smells sweet, Daddy." Good girl.


Saying hi to R2-D2


Decorating a Day-of-the-Dead head


A self-amplifying art-bike adorned with music box players


The "Austin Bike Zoo" had its full menagerie


The rattlesnake bike


Posted by Erik | Permanent link

Oct 12

Writing portable apps under Gentoo

For a previous C project for work, I developed a Windows app on Linux. The clunky workflow:

  • Write the entire app in Linux, being careful to use only the C Standard Library.
  • Move the source onto a Windows box.
  • Using Eclipse,CDT, and MinGW, coerce a working build.
  • If cross-platform changes were needed, make the edits twice.

You can imagine this workflow is unsustainable for projects larger than my little console app. Fortunately, I'm now set up to do the whole shebang on my Gentoo box, using MinGW and Wine. Tonight, I've compiled and run a Windows-specific app completely from Linux.

Gentoo makes this setup easy, although it's probably a bigger production than it needs to be. I simply followed Gentoo's MinGW HOWTO to the letter, and had no issues.

By big production, I mean that instead of providing a quick-and-dirty Windows-specific solution, they've gone after the entire problem of cross-compiling for any architecture, with a single, enormous "CrossDev platform". One day, I'm sure I'll see the benefit of this framework. Among other things, it's meant to simplify the cross-compilation of dependencies (say, a UI library).

I haven't yet accomplished a full Autotools build that can compile a Linux or Windows app based on a configure script flag. It's high on my list, to be sure.

I'm completely stoked at the idea of compiling native apps for all major platforms. I don't want to be religious about portability. I just know that some programming problems aren't that hard if you plan the solution from the start.

Any cross-compiling devs out there? What experiences have you had?


Posted by Erik | Permanent link

Oct 05

Cruft and the joy of a New Install

I rebuild my machine once a year, with a fresh OS installation. I did this as a Windows user, and now under Linux too. Some reasons for this:

  • I like the performance of stripped down machine, and the "new car smell" of a clean box is a cheap thrill.
  • It's the only way I've found to solve the cruft problem.
  • Installing Gentoo is a great way to learn about OS bootstrapping, and kernel configuration.
  • I can put off real work for many hours.

To facilitate this vanity, I've got several partitions:

  • / on 10G
  • /usr on 10G
  • /home on 15G
  • 15G - temp or spare
  • 50G for data - code projects, movies, etc.

Installing a new Gentoo on the spare partition means that I can transiton asynchronously, with my old install still runnable until I'm comfortable "flipping the switch".

The problem of Cruft

No OS has solved the problem of Cruft buildup, and I'm not sure it's solvable. Cruft takes several forms:

  1. Package-managed software installed but no longer used.
  2. Package-managed software with side-by-side upgrades. Think of the JVM - I have to upgrade to 1.6, but now 1.4 and 1.5 are still on the disk.
  3. Software installed outside of the package-management system.
  4. User data (/home, /usr/src, etc)

Old software

Yes, software can be uninstalled, but is it really? If my package manager installs 10 library dependencies to a program, and then I uninstall the program, the 10 libraries remain. Gentoo provides a "dependency cleansing" command, but it carries enough warnings that I'm loathe to use it, and rightly so. What if it detects that a library isn't used, but really it's used by something I installed outside of the package manager? I've broken my software.

A new install removes all this uncertainty, and I get the latest version of everything too.

User data

Sure, I could go through every file in /home and evaluate its worth. But I won't. Treat that data like the stuff in your garage - box it up (an archive/reference partition), and after a year or so of not looking at it, haul it to the curb.

Feeling fresh

I haven't rebuilt my wife's Windows box for five years. Logging in, I'm haunted by a slow performance, a nearly full disk, and every programmer's dread - my old code! (If it doesn't hurt to look at your own five-year-old code, then you've stopped learning). Computing on that machine isn't, and couldn't be, enjoyable.

The final solution

Thanks for letting me rationalize the hours I spend re-inatalling my OS periodically. I look forward, in a month or so, to having a completely new machine, with nothing on it except what I use today. Remember to always keep a spare partition for just such an occasion.

I always get funny looks when I confess this compulsion. Are there others like me out there, who get off on a clean install? Please leave a commont, I'd like to hear your experiences.


Posted by Erik | Permanent link

Sep 28

Hashtables, damn

I've been working on a hashtable implementation in C because, well, everyone should do it once, right? And in principle, it should be easy, right? I've spent more than a few hours getting this far, and more than a few to go.

Some features:

  • The getHashCode function returns a char not an int. This keeps memory use low, and in my experience hash tables commonly have dozens, not thousands, of records.
  • Simple chaining is used to resolve collisions.
  • Since traversing the keys is a common thing to do in a hash table, I have an array to hold the keys.
  • For collision resolution, each value in the table is preceded by a pointer to the key in the key array.

The library includes some array helper functions for using re-sizable, null-terminated arrays. I was inspired by the dietlibc [pdf] philosophy of favoring arrays over linked lists and other more complicated data structures, and using realloc() for re-sizing the arrays. A side-effect of realloc, however, is that it may move your array to make enough room, which made life somewhat difficult for me.

I won't print the whole thing here, because:

  • I haven't implemented remove(), clear(), or free() yet.
  • I have a good, general-purpose getHashCode() function in mind, not yet implemented.
  • I don't care to be someone's homework helper. (Please e-mail me, address in the margin, and I'll be happy to send you the latest version).

Here's the hashPut function, though, just for the purpose of showing the amount of work involved.

 
void hashPut(hash * h, void * key, void * value) {

assert(key);
assert(value);
assert(h);

size_t szPtr = sizeof(void *);

if(h->keys) {
	// Couldn't find an existing equivalent key, so we add
	if(!arrSeek(h->keys, key, h->szKey, h->keyComparator)) {
		void * oldKeysArray = h->keys;
		h->keys = arrAdd(h->keys, key, h->szKey);

		// Since realloc (in arrAdd) may have moved the array,
		// we will have to correct all the pointers to the keys.
		off_t keyArrayMoved = h->keys - oldKeysArray;
		if(keyArrayMoved) {
			size_t szPtrPlusValue = szPtr + h->szValue;
			char *** oneValue = h->values;
			int i = 0;
			for(; i < 256; oneValue++, i++) {
				if(*oneValue) {
					**oneValue += keyArrayMoved;
				}
			}
		}
	}
// No existing key array, create it
} else {
	h->keys = arrNew(key, h->szKey);
	
	h->values = calloc(256 , szPtr);
}

void * keyInArray = arrSeek(h->keys, key, h->szKey, h->keyComparator);

char hashCode = h->hashFunc(key);
void ** table = h->values;
void * chain = table[(unsigned char)hashCode];

/* Chain data is stored as an array of an implied
 * struct type:
 * 	{
 * 		void * ptrToKeyInKeyArray;
 * 		char[szValue] value;
 * 	}
 *
 * Since values have variable size, the struct can't
 * be explicitly declared.  Just know that's what's going
 * on here.
 */

if(chain) {
	void * arr = chain;
	void * existing = arrSeek(arr, value, h->szValue, 
		h->valueComparator);

	// Replace an existing value in the chain
	if(existing) {
		memcpy(existing,  key, szPtr);
		memcpy(existing + szPtr, value, h->szValue);

	// A new item in the chain
	} else {

		char ptrPlusValue[szPtr + h->szValue];
		*ptrPlusValue = (int)keyInArray;
		memcpy(ptrPlusValue+szPtr, value, h->szValue);
		table[(unsigned char)hashCode] = 
			arrAdd(arr, &ptrPlusValue, szPtr + h->szValue);

	}
// No chain exists at this location in the values array
} else {

	char ptrPlusValue[szPtr + h->szValue];
	*ptrPlusValue = (int)keyInArray;
	void * copyValueHere = ptrPlusValue + szPtr;
	memcpy(copyValueHere, value, h->szValue);

	void * newChain = arrNew(&ptrPlusValue, szPtr + h->szValue);
	table[(unsigned char)hashCode] = newChain;
}

}

"A hash table? Simple!" I thought. "I could probably do it with macros," I thought.


Posted by Erik | Permanent link

Sep 21

MovieCube is unusable

I've been a fan of DVD rental kiosks for quite a while. They allow for the spontaneity that is lost with Netflix, my practice of returning the movie the next day is rewarded with cheap prices, and the retrieval/vending hardware is called the "Robot". What's not to like?

My neighborhood grocery store changed kiosk vendors from TheDVDConnection to MovieCube (a giant in the market). I walked away empty-handed from the kiosk last night following a horrible user experience!

Every single rule of interface design is broken. I'll start with the landing screen. I don't have screenshots, but the layout is like this:

,___________________________________________________
| *Category* *Buttons* *Up* *Here*                 |
|--------------------------------------------------|
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|--------------------------------------------------|
| |-------. |-------.                              |
| |__Eng__| |__Esp__|                              |
`---------------------------------------------------

With very small buttons, yet the center of the screen is completely blank!

Clicking a category, say "New Releases", results in a progress bar that lasts no less than 10 seconds!

,___________________________________________________
| *Category* *Buttons* *Up* *Here*                 |
|--------------------------------------------------|
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                  +----------+                    |
|                  |Processing|                    |
|                  | xxx----- |                    |
|                  +----------+                    |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|                                                  |
|--------------------------------------------------|
| |-------. |-------.                              |
| |__Eng__| |__Esp__|                              |
`---------------------------------------------------

Come on. YOU"RE FILLING AN ARRAY. This is repeated for every category selection, including the case where you click the button for the category you're already on. There's no possible rationale for performance this bad. If you're connecting to the network for some reason, shame on you. And not caching whatever hardcore algorithms are needed to assemble a paged list of movies, double shame.

Now the movies appear in a paged list, a nice two-column layout with thumbnails.

 ,___________________________________________________
 | *Category* *Buttons* *Up* *Here*                 |
 |--------------------------------------------------|
 |                  New Releases                    |
 |          ____               ____                 |
 |         |    |             |    |                |
 |         |    | Borat       |    | La Mujer       |
 |         |    |             |    |                |
 |         '----'             '----'                |
 |          ____               ____                 |
 |  <<<    |    |             |    |           >>>  |
 |         |    | Saw XXI     |    | Pirates!       |
 |         |    |             |    |                |
 |         '----'             '----'                |
 |          ____               ____                 |
 |         |    |             |    |                |
 |         |    | El Oso      |    | Fuego Fuego    |
 |         |    |             |    |                |
 |         '----'             '----'                |
 |                                                  |
 |--------------------------------------------------|
 | |-------. |-------.                              |
 | |__Eng__| |__Esp__|                              |
 `---------------------------------------------------

At this point, so many things are wrong:

  • Borat isn't a new release.
  • Half the movies shown are Spanish-language titles, even though I pressed the "English" button. I can appreciate that the language of the interface and the language of the movie are two different things, but Joe User doesn't understand that. Offer the user a way to filter based on movie language, and make it clear that the existing "English" and "Spanish" buttons don't do this.
  • There's no one-line description of the movie. Come on, just a sentence, it really does help.
  • The paging function ... well ...
    • There's no "Page 1 of 7" indicator. You must scroll every page to know how many "New Releases" there are.
    • The paging buttons (left and right arrows) neither disable for impossible actions, nor cycle to the other end of the list. In other words, if I'm on Page One, then either the "Page Back" button should disable, or it should cycle me to the last page of results. Nope. It just reloads page one again (incredibly I'm spared the 10-second progress bar in this case).

After all this fighting with the interface, it turns out that the selection is tiny and poor. I walk away. After years of resisting, I'm joining Netflix today.

MovieCube, don't hire the CEO's nephew to do your interface design. "Trevor, he's good with computers!"


Posted by Erik | Permanent link

Sep 14

Lifehacking the Grocery List

Here's our last grocery list:

Look like a mess? Look again. Instead of the typical list style, we've started writing the items in the geographical location in the grocery store. So, produce being the front right of the store when you walk in, lettuce is drawn in the lower left hand corner.

How does it work? Awesome. Until now, every grocery shopping trip has one moment where I realize I missed something and I have to walk across the store again to get it. Never again.


Posted by Erik | Permanent link