wonko.com

Hi! I'm Ryan Grove: Sorcerer at SmugMug, lover of movies, eater of pie, connoisseur of awesome.

Older posts

Displaying items 151 - 160 of 662

Moving forward

Yesterday morning I nipped down to my local Apple Store and bought a MacBook Pro. This is the second Apple product I've ever bought; the first was my iPhone. I'd be lying if I said the iPhone didn't have something to do with this.

I've been a Windows user all my life, mostly by choice. On the whole, I like Windows. I know it inside and out, upside-down and backwards, and I'm wildly productive with it. I'm also very aware of its many shortcomings, and one of the things Windows has never had that I've always wished it did is a solid Unix foundation under the hood.

Over the last few years I've watched as Mac OS X has risen in popularity and grown more mature. Part of me was always jealous — of the sexy UI, the powerful Unix underpinnings, the thriving open source community that sprang up around it — but another part of me was hesitant because there were things I liked very much about Windows that I would have to give up if I moved to Mac OS. So I watched from afar, often thinking about switching, but never making the leap because I could always think of at least a few things I wouldn't be able to live with (or without) if I switched.

Now, though, the time is right. Leopard resolves many of the minor complaints I've had about past OS X releases and brings a bevy of glorious new capabilities that Windows just can't compete with. And for those times when I still have to fall back on Windows — of which there will be, and already have been, many — I can rely on almost seamless virtualization thanks to VMWare Fusion and Parallels. And when that's not enough, I can still retreat to native Windows land via Boot Camp, whereupon my MacBook Pro shines in comparison with even the best PC laptops.

There have been, and will continue to be, frustrations and inconveniences (I still can't believe how slow and crappy Mac Firefox is), but I've made my choice. I'm betting on the OS that embraces tried and true technology rather than pretending it doesn't exist and that doesn't assault its users with DRM or unauthorized software updates.

Don't worry, Windows. We can still be friends. I just need some space.

Ruby script to sync email from any IMAP server to Gmail

Update (2009-03-16): This script has been superseded by Larch, a full-fledged Ruby application that does the same thing, only faster and more reliably.

Last night after Gmail began rolling out IMAP support, I started investigating ways to copy my huge email archive (thousands and thousands of messages dating back to 2003) from my IMAP server to Gmail’s IMAP server.

Copying the messages from one account to the other in Thunderbird works, but it’s glacially slow, needs babysitting, and is prone to creating duplicate messages unless the entire copy operation works right the first time. Great for copying a few messages, not so great for copying thousands.

I also investigated imapsync, a Perl script that’s somewhat faster and more reliable than Thunderbird and doesn’t create duplicate messages, but for some reason using imapsync results in the messages on Gmail being timestamped with the time they were imported rather than the time they were sent or received, which is unacceptable. I tried using the --syncinternaldates option to rectify this, but it didn’t work.

So, since the best way to get something done right is to do it yourself, I set about writing my own tool to transfer my email. Thanks to Ruby and Net::IMAP, this turned out to be pretty easy.

Here’s what I came up with. It’s not pretty, it’s not user friendly, and it doesn’t do much error checking, but it’s extremely fast, it works, and if it fails at any point you can just run it again and it’ll pick up where it left off. Share and enjoy.

#!/usr/bin/env ruby
require 'net/imap'

# Source server connection info.
SOURCE_NAME = 'username@example.com'
SOURCE_HOST = 'mail.example.com'
SOURCE_PORT = 143
SOURCE_SSL  = false
SOURCE_USER = 'username'
SOURCE_PASS = 'password'

# Destination server connection info.
DEST_NAME = 'username@gmail.com'
DEST_HOST = 'imap.gmail.com'
DEST_PORT = 993
DEST_SSL  = true
DEST_USER = 'username@gmail.com'
DEST_PASS = 'password'

# Mapping of source folders to destination folders. The key is the name of the
# folder on the source server, the value is the name on the destination server.
# Any folder not specified here will be ignored. If a destination folder does
# not exist, it will be created.
FOLDERS = {
  'INBOX' => 'INBOX',
  'sourcefolder' => 'gmailfolder'
}

# Maximum number of messages to select at once.
UID_BLOCK_SIZE = 1024

# Utility methods.
def dd(message)
   puts "[#{DEST_NAME}] #{message}"
end

def ds(message)
   puts "[#{SOURCE_NAME}] #{message}"
end

def uid_fetch_block(server, uids, *args)
  pos = 0

  while pos < uids.size
    server.uid_fetch(uids[pos, UID_BLOCK_SIZE], *args).each {|data| yield data }
    pos += UID_BLOCK_SIZE
  end
end

@failures = 0
@existing = 0
@synced   = 0

# Connect and log into both servers.
ds 'Connecting...'
source = Net::IMAP.new(SOURCE_HOST, SOURCE_PORT, SOURCE_SSL)

ds 'Logging in...'
source.login(SOURCE_USER, SOURCE_PASS)

dd 'Connecting...'
dest = Net::IMAP.new(DEST_HOST, DEST_PORT, DEST_SSL)

dd 'Logging in...'
dest.login(DEST_USER, DEST_PASS)

# Loop through folders and copy messages.
FOLDERS.each do |source_folder, dest_folder|
  # Open source folder in read-only mode.
  begin
    ds "Selecting folder '#{source_folder}'..."
    source.examine(source_folder)
  rescue => e
    ds "Error: select failed: #{e}"
    next
  end

  # Open (or create) destination folder in read-write mode.
  begin
    dd "Selecting folder '#{dest_folder}'..."
    dest.select(dest_folder)
  rescue => e
    begin
      dd "Folder not found; creating..."
      dest.create(dest_folder)
      dest.select(dest_folder)
    rescue => ee
      dd "Error: could not create folder: #{e}"
      next
    end
  end

  # Build a lookup hash of all message ids present in the destination folder.
  dest_info = {}

  dd 'Analyzing existing messages...'
  uids = dest.uid_search(['ALL'])

  if uids.length > 0
    uid_fetch_block(dest, uids, ['ENVELOPE']) do |data|
      dest_info[data.attr['ENVELOPE'].message_id] = true
    end
  end

  dd "Found #{uids.length} messages"

  # Loop through all messages in the source folder.
  uids = source.uid_search(['ALL'])

  ds "Found #{uids.length} messages"

  if uids.length > 0
    uid_fetch_block(source, uids, ['ENVELOPE']) do |data|
      mid = data.attr['ENVELOPE'].message_id

      # If this message is already in the destination folder, skip it.
      if dest_info[mid]
        @existing += 1
        next
      end

      # Download the full message body from the source folder.
      ds "Downloading message #{mid}..."
      msg = source.uid_fetch(data.attr['UID'], ['RFC822', 'FLAGS',
          'INTERNALDATE']).first

      # Append the message to the destination folder, preserving flags and
      # internal timestamp.
      dd "Storing message #{mid}..."

      tries = 0

      begin
        tries += 1
        dest.append(dest_folder, msg.attr['RFC822'], msg.attr['FLAGS'],
            msg.attr['INTERNALDATE'])

        @synced += 1
      rescue Net::IMAP::NoResponseError => ex
        if tries < 10
          dd "Error: #{ex.message}. Retrying..."
          sleep 1 * tries
          retry
        else
          @failures += 1
          dd "Error: #{ex.message}. Tried and failed #{tries} times; giving up on this message."
        end
      end
    end
  end

  source.close
  dest.close
end

puts "Finished. Message counts: #{@existing} untouched, #{@synced} transferred, #{@failures} failures."

Update: Now includes Steve K’s patch to fix BadResponseError exceptions. Thanks Steve!

Update (2009-03-02): Brought the script up to date with several bug fixes and enhancements (including those contributed in comments below). Thanks everyone!

Update (2009-03-16): This script has been superseded by Larch, a full-fledged Ruby application that does the same thing, only faster and more reliably.

Gmail + IMAP == <3

Gmail has just added full-on IMAP support, and it's awesome. And by awesome I mean totally sweet.

This has pushed me over the edge. I'm finally ready to use Gmail as my primary email provider. Now that I can access all my email (including my old archived mail) however I want to from whatever client I choose to use, whether that's Gmail itself, Thunderbird, my iPhone, or good old Pine, Gmail is perfect.

Google, you rock.

Location-based mailing addresses suck

The lovely thing about email is that it works no matter where you are. Email addresses are tied to people, not locations. This makes your email address a convenient globally-unique identifier, since it follows you even if you move across the country.

It's high time that snail mail addresses worked the same way. When you send a letter or a package, you're sending it to a person, not a location. You don't care where it goes, you just care that it makes it into the hands of the intended recipient. Street addresses are great when you're giving someone directions to a party, but they're suboptimal when used as mailing addresses.

As someone who tends to move about once a year on average, I've begun to dread changing my address in a hundred different places almost as much as I dread packing and lifting furniture. I invariably forget to update it somewhere important, the USPS invariably continues to deliver the occasional important piece of mail to my old address instead of forwarding it to the new address, and by the time I've finally got everything sorted out, I've moved again.

What I want is a single, unchanging, globally unique identifier that will serve as a pointer to my current physical address. It would rock if I could use my email address or even OpenID for this. The USPS would maintain a central database mapping unique IDs to street addresses and would provide a simple REST API to allow authorized consumers (such as UPS, FedEx, etc.) to retrieve the current street address for any given ID.

Never again would I have to update my address in a million different places. I'd just log into usps.com, change my address there, and all my mail would be delivered to the new address automatically from that point forward. In addition to making things more convenient for me, this would also save the postal service huge amounts of time and money by eliminating the need for temporary change-of-address forwarding and drastically reducing the amount of misdelivered mail that gets returned to sender.

Why hasn't this been done already?

Things you may not have noticed about Yahoo! Search Assist

One of the awesome things about getting to work on Yahoo! Search Assist is that I got to toss a few extra little features in there just for myself. I haven’t seen these mentioned in the press or blog coverage, possibly because nobody’s noticed them yet.

Quick access to the search box

On search.yahoo.com, the cursor is automatically placed in the search box when the page is loaded, but on the search result page, we don’t automatically give focus to the search box because that would prevent you from scrolling using the keyboard.

So, what if you want to edit your query without moving your hand all the way from the keyboard to the mouse to click on the search box? Well, now it’s easy: after the page loads, just press tab. Voila, cursor in search box, ready to edit, no mouse necessary.

Quick access to the Search Assist tray

By default, Search Assist isn’t displayed unless you appear to be having difficulty deciding what to search for. It does this by analyzing your typing speed and noticing when you pause longer than usual. Sometimes, though, you want to see suggestions right away without waiting. You can use the mouse to click on the little arrow beneath the search box, but that requires more pesky hand movement. Luckily, there’s another option.

When the cursor is in the search box, you can simply press the down arrow on your keyboard to expand the tray. If you decide you don’t want the tray after all, hit the escape key to close it again. As has been mentioned elsewhere, you can also use the arrow keys to select suggestions and concepts.

Mouse wheel scrolling

If you’re a mouse person and aren’t fond of having to click the tiny arrows to scroll the lists of suggestions and concepts, just move your cursor over the list you want to scroll and use the mouse wheel. When the cursor isn’t over a suggestion list, the mouse wheel causes the entire page to scroll, but when the cursor is over a list, the mouse wheel only scrolls that list.

A little something extra

I can’t take credit for this one, but it’s one of my favorite new features nonetheless: Yahoo! Search goes up to 11. Google only goes to 10. You do the math.

SftpDrive rocks

At work I have a dedicated FreeBSD dev box, but I prefer to write code on my Windows laptop. Until today I was using the NFS client in Microsoft's Windows Services for UNIX package to mount my FreeBSD home directory. This worked okay, but for some reason it's ridiculously slow when doing anything that involves walking a directory tree.

This morning I finally got fed up. I killed NFS in the face and installed the trial version of SftpDrive. It's such a huge improvement over NFS that I've already bought a license even though the trial version is free for six weeks. I highly recommend it.

Headlights: a beginner's guide

As the days grow shorter and the sunset tends to precede my evening drive home, I can't help but notice that roughly one in every five of my fellow highway commuters is—how shall I put it—an addlebrained fucktard.

This is a scientific fact which I know to be true because only an addlebrained fucktard would rocket down 101 at 85 miles per hour after dark with his headlights off. And yet, every evening, I observe so many examples of wanton fucktardery that I can only assume the state of California lacks the ability to properly educate its citizenry in the delicate art of headlight usage.

As a public service, then, please allow me to present this simple beginner's guide to using your fucking headlights.

Step 1: What is a headlight?

The following things are headlights:

  • headlights

Here are some things that are not headlights:

  • fog lights
  • daytime running lights
  • parking lights
  • hazard lights
  • brake lights
  • your mom

Step 2: Is it dark outside?

You can tell when it's dark outside because you won't be able to see the sun. Sun equals light. No sun equals dark. Dark equals use your fucking headlights.

Step 3: Do you like dying in car crashes?

If you like dying in car crashes, you can disregard the advice given in steps 1 and 2. Here's how to tell if you like dying in car crashes: stab yourself in the eyeball with a fish hook. Did you like that? If you did, then you'll probably like dying in car crashes.

Congratulations. Now you know how to use your fucking headlights.

FedEx has graciously donated my new speakers to someone else (update: resolved!)

Last Thursday I ordered a pair of lovely new Polk RM50T speakers from Woot. They were shipped via FedEx on Tuesday and were scheduled to arrive today.

I checked the tracking page this morning and saw that they were out for delivery, so I relaxed and played some TF2 while I waited. A few hours later I checked again and saw that they had been delivered. They were signed for by R. Grove, which is odd, because R. Grove had been sitting on his ass for the past three hours pwning bitches.

not my signature The signature on the proof of delivery document certainly isn't mine. Whoever signed for the packages didn't even bother signing my name. It looks a lot more like "Paul L." than "R. Grove".

Naturally, I called FedEx to ask them where they had delivered my expensive new speakers. They put me on hold for a few minutes, then told me they'd ask the driver when he got back this evening, and then they'd get back to me by Tuesday. Couldn't they just ask the driver now and save some time? No, they couldn't. Surely, if the driver is capable of transmitting the signature to the website electronically, there must also be some way of contacting the driver while he's out and about? No. Apparently not. But "don't worry," said the FedEx support agent, "this isn't the first time something like this has happened."

Clearly, FedEx has incompetence down to a science, so I should sit back and allow them to do what they do best. In the meantime, if you happen to have taken delivery of a couple of fancy speakers that you didn't order, could you hold onto them for me? Thanks.

Update: At around 5pm, the FedEx guy showed up at my door with my speakers. It turns out someone else on his route had ordered the same speakers from Woot, but in a different color. Since they were in almost identical boxes, he accidentally delivered my speakers to that guy (which is where the strange signature came from). He eventually realized his mistake, swapped out the speakers, and got everything sorted out.

Talk about a funny coincidence.

Team Fortress 2: still vaporware

Hey Valve, guess what I'm doing right now? That's right: not playing the Team Fortress 2 beta. Can you guess why I'm not playing Team Fortress 2 beta, Valve? Because you haven't released it yet, that's why. Can you guess what I've been doing since Team Fortress 2 was announced back in 1999? Yep: not playing Team Fortress 2.

Something tells me I'm going to continue not playing Team Fortress 2, in spite of the promises that I'd have it today if I shelled out my $44.95 a little early.

Update: 12:04am PDT and everyone who preordered and preloaded TF2 has discovered that, even after Valve finally activated the game with 10 minutes to spare, it does nothing but crash a few seconds after launching. Way to tease your customers until the last minute and then poke them in the eye with a sharp stick, Valve.

Update 2: Finally got to play the game. Is it hilariously fun? Yes. Was it worth $44.95? Hell yes. Was it worth an 8-year wait? Um, no.