Tuesday, June 23, 2009

Finding Cycles in Git Commit Graphs

Git's data model is a directed acyclic graph of commits. Using .git/info/grafts one can accidentally create cycles in this graph. I wrote the following script to detect those cycles and help me determine which grafts might have caused the problem. In most cases, it's helped me narrow the problem down far enough that I can resolve the problem manually.

Thursday, April 9, 2009

Smallest Credit Card Payment Form

I really dislike filling out web forms. There are some fancy tools to avoid the hassle, but it would be even better if the forms weren't so long in the first place. While thinking about payments for online downloads, I wondered what the smallest possible credit card payment form would be. I came up with:

CC #
Street
Zip

I used PayPal's DirectPay documentation (PDF) as my guideline for what's required by a typical payment processor. I've separated the list of required fields into those for which you have to ask the customer and those that can be inferred from the customer's earlier responses.

  • Ask the Customer
    • Credit card number
    • Street part of address
    • Zip code
  • Infer from the above responses
    • Credit card type (inferred from CC number)
    • City (inferred from zip code)
    • State (inferred from zip code)

This exercise assumes that only U.S. payments are accepted, so I've left off the Country field. That could probably also be inferred from the zip code for many countries. Below are details about how to infer the bottom fields from the top ones.

Credit card type

Google Checkout was the first site that I saw implement this. Each credit card type has a unique prefix for their card numbers. The code for inferring the card type from the card number is pretty straightforward:

    /* cc_num is the credit card number */
    if ( cc_num.match(/^4(\d{12}|\d{15})$/) ) return 'visa';
    if ( cc_num.match(/^5[1-5]\d{14}$/) )     return 'mc';
    if ( cc_num.match(/^3(4|7)\d{13}$/) )     return 'amex';
    if ( cc_num.match(/^6011\d{12}$/) )       return 'discover';
    return 'unknown';

Once the customer is done typing his card number, you can display a credit card type logo to show that you've inferred the information.

City and State

In the common case, the city and state can be easily inferred from the zip code. Load the U.S. zip code list onto your server. Look up the customer's zip code in the list and send those on to the payment processor.

Unfortunately, some zip codes cover multiple cities. For instance, in some databases the zip code 82327 is associated with both Hanna, WY and Elmo, WY. I doubt that most credit card processors pay any attention to the exact city in the payment request. If that's right, just choose the first city in the list and submit that one.

If I'm wrong and the payment processor does care, you'll need a little more user interface. The list of zip codes with colliding cities should be fairly small. It should be trivial to include that information in a small JavaScript function. If your customer enters one of these zip codes, make an AJAX request to find out which cities are applicable and update your form on the fly to include a city selector. The customer can resolve the problem by choosing from the list of possible cities.

CC #
Street
Zip

Optional Fields

Most credit card forms ask for the credit card expiration date and the CVV2 number. PayPal doesn't require those two fields as long as you set your security thresholds low enough. For inexpensive digital downloads, it's probably acceptable to use those lower thresholds.

In the typical case, this tiny credit card form should only require about 18 key presses plus the length of the customer's street address. Since the form is so small it could easy be launched as a CSS overlay when a customer clicks a payment link. If the payment is rejected, show a rejection message in the overlay. If it's accepted, redirect the user to the link he just paid to access.

Wednesday, April 1, 2009

500 Usage: $h->push_header($field, $val)

After upgrading the LWP::UserAgent module to version 5.824, I started getting a bunch of errors like 500 Usage: $h->push_header($field, $val) in the error logs. SOAP::Lite seemed to be the top-level cause of the error. It turns out that I had an old version of SOAP::Lite (version 0.69). Upgrading to version 0.710.8 fixed the problems.

Thursday, March 26, 2009

Module::Install Custom Makefile Targets

I'm using Module::Install to build a Perl module. I was trying to persuade Module::Install to add a custom Makefile target so that I could leverage all the hard work it had already done. I found nothing by searching or reading the documentation. It turns out to be pretty easy. Just put something like this in your Makefile.PL.
Makefile->postamble(qq{hi:\n\techo "Hello World!"});
That creates a new "hi" target in the Makefile.

Wednesday, February 18, 2009

Generalizing Twitter

I'm using Twitter now. So far it's pretty fun. I like being notified when interesting things happen (IM, twitter, new email). It's made me wonder whether it's feasible to create one event log to rule them all.

This event log would live in the cloud. Anything could notify the cloud that something "interesting" happened. It records that event in the log. For example, my tweets would automatically notify the cloud. When I leave my desk, it'd notify the cloud. As my phone detected a new geolocation, it'd notify the cloud. I'd soup up my toaster to notify the cloud when the toast popped up, etc.

Now what if it were programmable? I'd notify the cloud with a special event containing a small program and some conditions to trigger that program. For example, to automatically send me an instant message on Google Talk when the toast is ready:

    "event"  : "mndrix's toaster popped up",
    "action" : "xmpp_send('mndrix@gmail.com', 'Your toast is done')",

(The way of specifying event patterns needs some thought). This is kind of like JoCaml's concurrency model: events matching certains patterns trigger code. In the example above, events come into the cloud and that action just sits there doing nothing. As soon as the "mndrix's toaster popped up" event arrives, the cloud notices that it matched the precondition I specified, and it triggers my code.

If the service had a way to run a webhook, you could run arbitrary code when interesting events (or sequences of events) occurred. The event log in the cloud basically becomes a switch board where events come in, they're matched against patterns and new events are generated (infinite event loops, anyone?)

I think there's potential for interesting applications once you have access to tons of events outside your own personal sphere. For instance, to create an impromptu IM meeting once everyone is available: "if my wife and brothers are available on IM and I'm not on the phone, create a chat room and invite us all". The condition is composed of the state of several people (inferred from previous events in the log). The action could be implemented with some XMPP scripting.

Wednesday, February 4, 2009

GNU screen for Phones

I'm a big fan of GNU screen. One reason is that when a bad 'net connection (or a two year old) disconnects my ssh session. I can reconnect to the server and reattach to the previous session without any lost work. I'm currently in an area with really bad cell coverage. It would be great if there were a GNU screen equivalent for cell phones. I'd call a phone number in the cloud and have it dial out to my final destination. If my cell phone dropped the call, the service would announce to the parties on the line "Michael's phone got disconnected. He'll be right back". The service would then call my cell phone. As soon as I answered, I'd be reconnected to the previous call.

The same thing could work for incoming calls. I'd provide others with my number in the cloud. When a call came in, the service would call my cell phone and patch the call through. If I lost the connection, the same scenario would play out.

Another idea is that I could call a customer service phone number (via the service). When I'm put on hold, I could dial *99 (or something) and then hang up. Once I'm off hold (detected somehow or other) the service would call me back and I'd be connected to the customer service representative.

I'm sure there are lots of other cool features that could be implemented. They key is having a single layer of indirection in the phone system between my phone and the rest of the world. Google's GrandCentral service has the potential for making this possible. It also seems like one could hack up an Asterisk application to do this.

Friday, January 16, 2009

Mac OS X Proximity Sensor

Problem and Background

I'm often annoyed by small inefficiences. Typically, these are manual tasks which a machine should be doing for me. One inefficiency that's bothered me for a while is coordinating my IM status (away vs available) with my presence in the office.

Adium is my IM client. It can detect keyboard and mouse movement and automatically set my status as away after a couple minutes of inactivity. Unfortunately, it can't tell when I'm working on the whiteboard across the room. In that case, I might not have typed for a while, but I'm still available.

I tried using software like HomeZone or MarcoPolo to make my cell phone a proximity sensor. I set them up to detect the presence of my phone's bluetooth connection. When it sensed the bluetooth connection disappear, it set me as away. When the connection reappeared, it set my status to available. Unfortunately, bluetooth is too strong a signal for my house's floorplan. When I'm in the dining room eating lunch or in the bathroom taking a shower, the bluetooth signal from my phone is still strong in my office. I have to walk all the way across the house before MarcoPolo tells Adium that I'm away.

I searched for USB proximity sensors, but couldn't find anything decent. Everything was too expensive or too complicated to set up.

Solution

For a project I'm working on, I needed a small webcam. I bought a $40 Logitech QuickCam E3500 at Walmart. The box only claims compatibility with Windows, but it works fine with my Mac. This morning, I realized that it could become a motion detector and solve my IM status problem. Here's what I did (after plugging in the camera):

  1. Download, install and run Periscope (50 hour free trial, then $25 to purchase)
  2. Under the "Capture" tab, click the boxes next to "Camera sees movement" and "watch the entire camera"
  3. In the upper-left corner of the live image, adjust the slider so that it points at the second box from the left. This makes the camera quite sensitive to movement. You might get better behavior with a different setting.
  4. In Periscopes preferences, under General, uncheck the box "Flash camera and play sound when taking a photo". Under Review, make sure that the photos folder is set to "~/Pictures/Periscope"
At this point, Periscope is capturing images of me every time I move a little bit. The next step is to convert those images into motion events that can trigger Adium.
  1. Download the file proximity.pl (part of a Github project)
  2. Open the Terminal application (or iTerm or whatever you use)
  3. Type "perl proximity.pl" ("proximity.pl" should be the full path to the downloaded file)

When you stop moving for 30 seconds, your Adium status will be set to away. Once you've been moving again for 5 seconds, your Adium status returns to available.

Future Improvements

It would be great if this kind of proximity detection was built directly into the Periscope software. Since it's already awake and taking the pictures, it would be pretty easy for it to execute scripts when it detects someone entering or leaving the room. I'll email the developer about the idea.

Instead of hard-coding the behaviors when I enter or leave the room, it would be better for the script to take the name of two other scripts. One script would be executed each time I arrive in the room. The other would be run each time I leave the room. That way, others can use the script with their preferred IM client or for some other use that I haven't thought of yet.

Is there value in having the timing constants (30 seconds and 5 seconds) configurable on the command line?

You can clone the Git repository at git://github.com/mndrix/proximity.git As with all open source projects on Github, feel free to fork this project. If I see patches that I like, I'll apply them to my repository.