Less Talk, More Do Christopher Finke is a software engineer. He is available for birthday parties and bar mitzvahs.

Posts tagged with 'Programming'

Making Add-on/User Communication Less Annoying

Saturday, February 27th, 2010

When a new user downloads TwitterBar, there are a number of things I want them to know or questions I want to ask them. So what is the best method to communicate with an add-on user?

The solution I've been using for a while is to pop up a dialog like this:

There are several problems with this approach, all of which I decided to ignore when I implemented it:

  • It steals the user's focus.
  • It's annoying.
  • The user might click cancel without reading it just to get rid of it.
  • It's annoying.
  • The user might immediately (but accidentally) click elsewhere, hiding the dialog behind another window, never to be seen again.
  • It's annoying.
  • It's extra code and work to pop up a special dialog like this.
  • It's annoying.

Back when there was only one dialog, I decided that these were acceptable faults. However, since then, I've come up with a few more questions I want to ask users, so now instead of one annoying dialog, there are three or four annoying dialogs - a new one appearing each time you restart Firefox.

Predictably (or so it should have been), users don't like to be assaulted with new dialogs each time they start their browser. Most likely, they're starting their browser for some purpose other than using my add-on, so my add-on shouldn't steal their attention. As one user so elegantly put it,

"I really love the TwitterBar, but after the most recent TwitterBar update, I noticed I kept getting these annoying as hell pop-ups from TwitterBar about TwitterBar. After the third one (while I was in the middle of doing something and became distracted with this pop-up dialog box TwitterBar tip of the day), I uninstalled it. If you want to keep your clients, don't constantly tap them on the shoulder."

I had already been working on redesigning these add-on/user interactions when I got that email, so the user's message reinforced what I had suspected: I was alienating my userbase.

Here's the new scheme I've settled on for now:

It's a notification bar, much like the one that appears when Firefox blocks a popup. It has these positive qualities:

  • It doesn't steal focus or interrupt the user.
  • It's not in-your-face, so it's less likely (I assume) to be dismissed without thought.
  • It can't be lost behind another window.
  • The amount of code to implement it is less, and it's more in tune with the browser interface.
  • It's not as annoying.

I'd love your feedback on this change. Is it enough? Should I stop bothering users altogether and just let them discover their way around the add-on? I'm open to all ideas.

(If you'd like to try a version of TwitterBar with this new notification method, you can download it here. Although, if you've already seen the old dialog-style version of these notifications, you won't see the new-style ones anyway.)

Uploading form data and files with JavaScript (Mozilla)

Saturday, January 30th, 2010

One problem I stumble across occasionally in writing Firefox extensions is properly uploading form data that includes a file - that is, assembling the POST request in JavaScript while still maintaining the sanctity of any file or string data. You can't just do this:

var request = "--boundary\r\n some text\r\n--boundary" + fileBytes + "\r\n--boundary--";

I had to spend a bit of time getting this just right in order to allow ScribeFire to upload media to Posterous, so I'm posting below the final solution at which I arrived; it was cobbled together from a dozen different examples I found around the Web (none of them solving the full problem), then lovingly massaged into the elegant function you see before you. With this function, you can pass in an array of fields and files, and the request will be crafted and returned to you, ready for upload.

Instructions for use are in the comment block at the top of the function.

function createPostRequest(args) {
  /**
   * Generates a POST request body for uploading.
   *
   * args is an associative array of the form fields.
   *
   * Example:
   * var args = { "field1": "abc", "field2" : "def", "fileField" :
   *              { "file": theFile, "headers" : [ "X-Fake-Header: foo" ] } };
   *
   * theFile is an nsILocalFile; the headers param for the file field is optional.
   *
   * This function returns an array like this:
   * { "requestBody" : uploadStream, "boundary" : BOUNDARY }
   *
   * To upload:
   *
   * var postRequest = createPostRequest(args);
   * var req = new XMLHttpRequest();
   * req.open("POST", ...);
   * req.setRequestHeader("Content-Type","multipart/form-data; boundary="+postRequest.boundary);
   * req.setRequestHeader("Content-Length", (postRequest.requestBody.available()));
   * req.send(postRequest.requestBody);
   */

  function stringToStream(str) {
    function encodeToUtf8(oStr) {
      var utfStr = oStr;
      var uConv = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
      uConv.charset = "UTF-8";
      utfStr = uConv.ConvertFromUnicode(oStr);

      return utfStr;
    }

    str = encodeToUtf8(str);

    var stream = Components.classes["@mozilla.org/io/string-input-stream;1"]
                   .createInstance(Components.interfaces.nsIStringInputStream);
    stream.setData(str, str.length);

    return stream;
  }

  function fileToStream(file) {
    var fpLocal  = Components.classes['@mozilla.org/file/local;1']
                     .createInstance(Components.interfaces.nsILocalFile);
    fpLocal.initWithFile(file);

    var finStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                      .createInstance(Components.interfaces.nsIFileInputStream);
    finStream.init(fpLocal, 1, 0, false);

    var bufStream = Components.classes["@mozilla.org/network/buffered-input-stream;1"]
                      .createInstance(Components.interfaces.nsIBufferedInputStream);
    bufStream.init(finStream, 9000000);

    return bufStream;
  }

  var mimeSvc = Components.classes["@mozilla.org/mime;1"]
                  .getService(Components.interfaces.nsIMIMEService);
  const BOUNDARY = "---------------------------32191240128944"; 

  var streams = [];

  for (var i in args) {
    var buffer = "--" + BOUNDARY + "\r\n";
    buffer += "Content-Disposition: form-data; name=\"" + i + "\"";
    streams.push(stringToStream(buffer));

    if (typeof args[i] == "object") {
      buffer = "; filename=\"" + args[i].file.leafName + "\"";

      if ("headers" in args[i]) {
        if (args[i].headers.length > 0) {
          for (var q = 0; q < args[i].headers.length; q++){
            buffer += "\r\n" + args[i].headers[q];
          }
        }
      }

      var theMimeType = mimeSvc.getTypeFromFile(args[i].file);

      buffer += "\r\nContent-Type: " + theMimeType;
      buffer += "\r\n\r\n";

      streams.push(stringToStream(buffer));

      streams.push(fileToStream(args[i].file));
    }
    else {
      buffer = "\r\n\r\n";
      buffer += args[i];
      buffer += "\r\n";
      streams.push(stringToStream(buffer));
    }
  }

  var buffer = "--" + BOUNDARY + "--\r\n";
  streams.push(stringToStream(buffer));

  var uploadStream = Components.classes["@mozilla.org/io/multiplex-input-stream;1"]
                       .createInstance(Components.interfaces.nsIMultiplexInputStream);

  for (var i = 0; i < streams.length; i++) {
    uploadStream.appendStream(streams[i]);
  }

  return { "requestBody" : uploadStream, "boundary": BOUNDARY };
}

Wolfram Alpha

Saturday, May 16th, 2009

Wolfram|Alpha, the new computational knowledge engine, seems like it has a lot of potential. But for some reason, I'm not so sure about its mathematical abilities:

Loading screen shows: NaN%


For more information on Wolfram Alpha, here's Mahalo's Wolfram Alpha page:

Retweeting in Python

Thursday, March 19th, 2009

My friend Eliot has been running a retweet bot named @SanMo for some time that is designed to serve Twitter users in the Santa Monica, California area by allowing them to broadcast tweets to other Santa Monica-area users without explicitly friending them. He wrote up the full details of the service and the script behind it on his blog, and after reading about it, I wanted to start a similar service for Twitterers in my immediate vicinity.

After grabbing a copy of the code running @SanMo, (it's the same Perl script created to power @lotd, available here), I quickly decided that I would rather write my own, for three reasons:

  • Strike 1: It was written in Perl.
  • Strike 2: It didn't implement a feature that links the retweet to the original tweet.
  • Strike 3: It required MySQL - a bit much for simple bot.
  • Bonus Strike 4: It was written in Perl.

I sought out to write a lightweight script that would accomplish the same end goal (republishing tweets directed at a given account), and I'm happy to say that it's finished: retweet.py has been running smoothly for the past few weeks behind the Twitter account SWMetro, a service for Twitter users in the southwest metro area of the Twin Cities.

It's 40 lines of Python (if you omit blank lines and comments), and you can grab a copy of it from here. It uses SQLite for storage (which you should already have installed if you have Python installed), and it utilizes the great python-twitter library from Dewitt Clinton of Google. (Make sure you get the latest version for trunk; you'll need simplejson as well, as python-twitter requires it.)

Just download the script and replace "username" and "password" at the top with your account credentials. (You can manage multiple accounts by adding another username/password pair to the ACCOUNTS variable.) Change the DB_PATH variable to point to the directory where you'll keep your SQLite databases, and then add this to your crontab:

*/2 * * * * python /full/path/to/retweet.py

The script will run every other minute, republishing any tweets that start with "@username", where "username" is the value you gave the USER variable. The only thing it stores in the SQLite database are the status id's of the tweets it republishes, and it links each retweet to the original message being republished. If the new message is longer than 140 characters, it chops words off of the end, replacing them with "..." until it's under the 140 character limit.

If you're using this script to replace an existing retweet bot, you can supply it with the status id of the last message it re-published so that you don't end up republishing a bunch of old tweets. To do that, just run it once like this:

$ python retweet.py 12345

where 12345 is the status id of the last message your existing bot published.

Feel free to download retweet.py and use it for your own purposes. All I ask is that if you make an improvement (or start up a new service with it), take a minute and mention it in the comments below.

Update: Some people have had to use the full path to Python (version 2.5 or greater) in their crontab to get retweet.py working properly.

Update: Retweet.py has been updated to ensure it keeps working as intended after Twitter started categorizing all tweets that contain a username as replies, not just ones that start with the username. Grab the updated version here.

Want to make some money?

Thursday, March 12th, 2009

Can you write Facebook or iPhone Apps? Do you like money? If so, have I got a deal for you:

We're looking for some help building out our iPhone and Facebook applications for Mahalo Answers. If you know of any great developers who have done a really solid application for either platform (or who are looking to make a name for themselves doing one), please have them email me at jason@mahalo.com and cc mark@mahalo.com. -- Jason Calacanis, CEO Mahalo.com

Tell them Finke sent you; readers of this blog are known 'round the world to be persons of solid character and intellectual fortitude, so you should definitely identify yourself as such.

I invented Facebook

Tuesday, March 3rd, 2009

Ok, well not really. But I like to think that I *could* have.

The summer after my senior year of high school (that's 2002), I came up with an idea for a website while delivering pizzas, and I had this exact conversation with my then-girlfriend (now my wife):

Me: "Wouldn't it be cool if there was a website where you could connect with people from high school, and see what they're up to, since you won't be seeing them in person much anymore?"
Her: "That sounds kind of dumb."
Me: "But then later on, when we all have jobs, we would have this ready-made network of people we know who are in all different fields. Think of the potential!" (Obviously, I invented LinkedIn as well.)
Her: "Sure, whatever."

Well, maybe she didn't respond exactly like that, but she definitely wasn't as excited as I was about the concept of creating this social site where you could network with people from your school. I thought it was a neat idea though, so I started working on it.

About four months later, I had finished the first version of the site:

  • It had profiles that you could fill out.
  • You could upload a profile picture.
  • You could comment on other people's profiles.
  • It automatically showed you people from your class that had signed up already.
  • It even had a feature called the "Rumor Mill," where you could post information about classmates who hadn't yet signed up. (In retrospect, this feature was poorly named and probably encouraged libel. Live and learn.)
  • It had search functions: by name, year, city, state, etc.

(Are you seeing the similarities here to a much larger site that would be launched a few years later?)

Now I just needed users. I sent out mass IMs and e-mails to people from my class and the classes a few years ahead of and behind me announcing the site, and then I waited for the inevitable flood of users and praise. However, given my grassroots approach, usage was predictably low. Maybe a hundred people signed up before I abandoned it for more worthwhile pursuits. I had to personally e-mail my parents a second time to prod them to sign up. My idea was obviously just ahead of its time.

Looking back upon this project, I realize that I made two crucial mistakes: the site was specific to my high school, and all of the names of the alumni who could possibly sign up were hard-coded in the database. (I was able to convince the school secretary to send me a spreadsheet of all current and former students.)

If you didn't catch the implication there, here it is: I limited adoption of my site right off the bat to a single school, and I went through the trouble of manually creating user accounts for every possible user - a waste of time for a project with limited appeal. (Additionally, it prevented any students who attended the school after 2002 from signing up.)

The site is long gone now (except for a poorly styled copy of the front page courtesy of the Internet Archive), and the code that powered it has since been lost. But I learned a valuable lesson from the experience:

Think big.

When you're starting a project, don't just plan on your friends using it, plan on EVERY SINGLE PERSON IN THE WORLD wanting to try it out. If I had only planned for wider adoption, I could have created a Facebook-esque site 2 years before Zuckerberg got the idea from ConnectU. (Theoretically, of course. I'm not claiming that I was the first to write a social networking sites for classmates, but obviously, there was space in that niche for another competitor.)

I took 5-Hour Energy, and I feel great!

Monday, December 29th, 2008

Typing up a storm

For anyone who isn't in the industry, this is what computer programming really looks like.

Sprites and the new Mahalo homepage

Sunday, October 12th, 2008

On Friday, we at Mahalo launched a revamped homepage and site-wide redesign, the result of months of technical and editorial development. I'd like to talk specifically about one new technical aspect of the homepage.


The old Mahalo homepage had, at any given moment, between 8 and 10 content images. (By "content images," I'm referring to images that are not design elements, like backgrounds or logos.)

Screenshot of old Mahalo homepage

The new Mahalo homepage, which packs much more data into the same space, has at any given moment, between 75 and 80 content images. (This isn't obvious from a screenshot due to the number of tabbed sections; for example, in the top left section, each of the five tabs contains 10 items, each of which usually contains an image.)

Screenshot of the new Mahalo homepage

Displaying each of these images requires a separate request to the server, and according to some, each request can add about 0.2 seconds to the load time of the entire page; for 80 images, that's 16 seconds of extra load time - unacceptable. Mahalo's architecture is faster than the average site's, but the general principle still holds true: more requests means longer load time and more stress on the servers.

To solve this problem, I was given the following tasks:

  1. Reduce the number of requests made for images when a user views the Mahalo homepage.
  2. Do so without affecting the productivity of the Guides, who upload the images and write the content for the homepage.

The solution for #1 is fairly obvious and is in widespread usage: CSS sprites. Using sprites means that all of the images for a given page are combined into a single image file, and then the browser displays sections of the master image in different parts of the page using CSS. If you dig around in the source of Mahalo's index.html, you'll see a reference to an image like this:

Several images stitched together into one image file

This image combines all of the separate images from the homepage into one, allowing the browser to download them all with a single request. Additionally, the total byte size of this single image is typically the same or smaller than the combined byte sizes of the images it contains, so it's a win-win situation.

By setting the background-image CSS property of the images to this master image, we can then display only a section of the image by using CSS declarations like these:

#i-1 { background-position: -0px -230px; width: 149px; height: 115px; }
#i-2 { background-position: -0px -345px; width: 149px; height: 115px; }
#i-3 { background-position: -0px -460px; width: 149px; height: 115px; }
[...]

So problem #1: solved. Now, how to achieve this effect automatically, without affecting Guide productivity? Why, Python, of course!

Using a little Python and a touch of magic, we can retrieve the HTML for a given page, enumerate the images, retrieve the images, combine them into a single image, modify the HTML to strip references to the original images, generate the CSS necessary to implement sprites for the images, and save the HTML back out to the server. This allows our Guides to modify the homepage however they like, view their changes on a server that doesn't use the sprites, and then sit back and enjoy as their changes are automatically published to the Mahalo production servers.


I don't know of any other site of scale that is generating dynamic sprites, but the system we've set up has so far worked flawlessly. However, if you're going to implement a similar system, there are a few caveats to keep in mind:

1. You need to be smart about how you stitch together your master image. Some poor planning (or none at all) can lead to a massive single image with tons of empty space, which will simply bloat the file size. Efficient use of space is key here.

2. By using sprites, any time a single image on the page changes, the user must re-download all of the other images, since they're all contained in the same master image. Avoiding this problem might mean waiting for a certain number of images on the page to change before generating new sprites and HTML, or it might mean generating two or three master images for different sections of the page, so that master images for each section of the page can change independently of each other.

Have any of you ever implemented a similar system? What do you think of our implementation?

What was your first program?

Thursday, August 21st, 2008

The first computer program I ever wrote was written in GW-BASIC. I was 8 or 9, and my dad had gotten me a book from the library that included the BASIC code for simple text-based games; after typing them into our IBM and playing around with them for a few hours, I decided to use what I had learned to write my own program, and it went something like this:

10 INPUT "WHAT IS YOUR NAME?", N$
20 IF N$ = "CHRIS" THEN PRINT "YOU ARE COOL"
30 IF N$ = "AARON" THEN PRINT "YOU ARE DUMB"
40 END

I thought it was pretty cool; my brother Aaron probably disagreed. I recall having plans to use this program to not only tell me that I was cool, but to store all sorts of secret information that it would only print out if I ran the program, since no one else would think to type in my name. (I suppose this would also count as the first password I ever chose on a computer: my name. Way to go, 8-year-old self.)

Anyway, there's not much more to this story, but I was wondering:

If you're a programmer, what was the first program you wrote? What was the first thing you made a computer do that it wasn't already programmed to do?

Finding unused entities in your Firefox extensions

Tuesday, April 22nd, 2008

If you've maintained a Firefox extension for any amount of time, you know that you can accumulate unused entities as you change the UI or add/remove features. They just pile up in your .dtd and .properties files, taking up space. Here's a bash script that will list out any entities or entries in .properties files in your extension that is no longer being used so that you can prune them out.

Usage: $ ./unused-entities.sh path/to/locale-directory/ path/to/content-directory/


#!/bin/bash

echo "Unused entities:"

for dtdfile in `ls $1*.dtd`
do
	awk '/<!ENTITY/ {print $2}' < $dtdfile | while read line
	do
		search=`grep -R "${line}" "$2"`
		if [ "$search" == "" ]
		then
			echo "${line}";
		fi
	done;
done;

echo ""
echo "Unused properties:"

for propfile in `ls $1*.properties`
do
	awk -F "=" '{if (!($2 == "")) { print $1 }}' < $propfile | while read line
	do
		search=`grep -R "${line}" "$2"`
		if [ "$search" == "" ]
		then
			echo "${line}";
		fi
	done;
done;