One of the more annoying things about Leopard is that when you plug in an external monitor that is set up to be your primary, your open applications don’t move over to the monitor. I was having to manually drag everything over. Then I realized that if I hid all my apps before plugging in the monitor, when I brought them up again, they’d be in the right place.

So now the question was: is there an easy way to hide all your apps without cycling through them all? It took a bit of digging, but it turns out there is. Cmd-option-click on an app in the Dock and it will hide all other apps. So I tried Cmd-option-click on the desktop itself and…voilà!

Update: Technically, you’re Cmd-option-clicking on the Finder, so if you have any Finder windows open, they won’t hide. Bummer.

27th Feb, 2008

ConditionsConstructor

I hadn’t posted this before because it seemed kind of trivial and silly, but I used it again the other day and decided maybe I should share the joy after all.

Far too often I find myself jumping through hoops building ActiveRecord find conditions arrays dynamically depending on what arguments were passed in to the method. So I cooked up a simple little class to clean that up a bit:

# class to construct conditions for AR queries
# cc = ConditionsConstructor.new('foobar > ?', 7)
# cc.add('blah is not null')
# cc.add('baz in (?)', [1,2,3])
# cc.conditions
# # => ["foobar > ? and blah is not null and baz in (?)", 7, [1, 2, 3]]
class ConditionsConstructor
  def initialize(str = nil, *args)
    @conditions_strs = str ? [str] : []
    @conditions_args = args
  end

  def add(str, *args)
    @conditions_strs << str
    @conditions_args += args
  end

  def conditions
    [@conditions_strs.join(' and ')] + @conditions_args
  end
end

16th Feb, 2008

Tracking the 123 Meme

I’m pretty late to the party, but I just ran across this on Chuck Hoffman’s blog: “Grab the nearest book, open to page 123, go down to the 5th sentence, and type up the 3 following sentences.”

Before I do that, I thought it would be interesting to try to track this meme back as far as possible. For the sake of my sanity, I’m not following multiple paths from posts that link to more than one source–I’ll just pick the link that seems to be the primary source. So here goes:

nothing happens linked to Knowing and Doing linked to Exploration Through Example linked to a thaumaturgical compendium linked to michaelzimmer.org linked to Chronicles of Dissent linked to Fergie’s Tech Blog linked to Threat Level linked to Danger Room linked to Abu Muqawama linked to Afganistanica: Mountain Tourism in Afghanistan linked to [My] State Failure Blog linked to Kings of War linked to MountainRunner linked to zenpundit.com linked to the glittering eye who gave a bad link that I had to track down to Independent Liberal linked to Donklephant linked to Stubborn Facts linked to another post on the same blog, linked to Sideways Mencken, who did not link, but referenced Internet Ronin linked to AmbivaBlog [and now I'm starting to tire of this game] linked to The Anchoress linked [not directly, had to dig it up *sigh*] to Some Have Hats linked to [again not directly...grr] Church of the Masses linked [incorrectly] to The Daily Grotto [warning! annoying chanting] linked to Aussie Coffee Shop linked to Where Angels Go which mentioned, but failed to link to the person who tagged them, so it’s a dead end.

Whew. This was getting a bit tedious. Not to mention that it seemed to have gotten firmly entrenched in religious/pro-life blogs, which were getting on my nerves.

Just as well. Turns out Google returns almost half a million hits for “page 123″ sentence, and there doesn’t seem to be a way to sort results by date (the daterange operator failed me completely). The oldest post I could find on Google Blog search was this one, from November 24, 2004 (albeit for a slight variation on the meme–just the fifth sentence, not the three following it), but which clearly isn’t the origin. Seems it was a meme floating around on LiveJournal around then. If anyone has better search-fu and can find an earlier post, let me know.

At this point, I’ve made myself pretty sick of this meme. Often it appears to be a means by which people can brag about whatever erudite text they may or may not actually be reading. Even so, I can’t have wasted this much time and not play along. I’ve got two books nearly equidistant from my keyboard now. One is, at least in my field, too mundane to bother with: the Pickaxe book. The other sports the following 123/5/3 sentences:

The latissimus dorsi muscles have a major role in the deadlift: from the floor, the lat pulls back on the humerus to keep the arms from swinging forward away from the shins, and acts as an anchor on the upper part of the humerus to maintain the position of the bar directly under the shoulder blades until the bar crosses above the knees. The lats act in an essentially isometric way from the floor to the point where hip extension allows the arms to become vertical. At this point tension comes off the lats, and as the back becomes vertical, the arms drag the bar into the thighs as they assume an angle behind the vertical, opposite the starting position.

That’s from Starting Strength by Rippetoe and Kilgore, which is a great book for learning how to do basic barbell lifts correctly (something we do a fair bit of in Crossfit). Coincidentally, that’s just a couple pages past where I was actually reading about the deadlift. (Most interesting tidbit I’ve learned so far from the book: the importance of a solid grip when doing the dealift. If your hands start slipping, proprioceptive (love that word) feedback tells the back that what you’re trying to lift is too heavy, and it basically gives up.)

I’m not going to inflict this on anyone else, but if you’re reading this, feel free to prate on about whatever book you have near you.

5th Dec, 2007

Cheating for Fun

I recently checked out the Scramble application on Facebook, which is a knock-off of Boggle. It’s a fun diversion, even though I usually get my ass kicked. There’s an annoying chat window there, too, and invariably someone will accuse someone else of cheating, at which point a number of people will pipe up with “how would you cheat?”

This got me thinking…how *would* you cheat? You’d just have to write a Scramble puzzle solver that spit out all the words in on the board. That sounded like a fun problem. I hadn’t done anything with recursion in a while, so I gave it a go. Below is what I came up with. You’ll need a word list to run it. I originally used /usr/share/dict/words, but it was missing a lot of valid words (I think it only has base words, no plurals or tenses). So I stole the scrabble word list from the Debian scrabble package, which worked much better.

Anyway, here you go:

I did try this out a couple times on Scramble and won handily. There’s really not much fun in that, though.

Update: I posted this in one of the comments below, but I’ll put it here as well to make sure everyone sees it: This was really just an exercise in coming up with a fun algorithm, and is primarily intended for programmers. If you’re just looking to cheat at Boggle/Scramble, there are plenty of web sites that will help you with that, and with a lot less effort.

dolphins by talkrhubarb
“dolphins” by talkrhubarb

I thought I knew a fair bit about MySQL before I started working on Wesabe, but I was wrong. Once your tables start to get really massive, all sorts of fun issues come out of the woodwork. There are a thousand articles out there on the basics of database optimization (use indices, denormalize when necessary, etc.), so I’m not going to cover those again. Here are some tips–most specific to MySQL–that I’ve learned on the job:

  • MySQL only uses one index per table when doing a query, and the query optimizer doesn’t always pick the best column, so indexing everything is not necessarily a great idea. If you’re always selecting on a certain set of columns, create a single multi-column index
  • related to the above, don’t put indices on columns with a low cardinality. You can check the cardinality of a column by doing SHOW INDEX FROM table_name
  • if any text or blob fields are part of your SELECT query, MySQL will write any temp tables to disk rather than memory. So where possible, use large VARCHAR fields (on 5.0.3 or higher) instead. See: http://railspikes.com/2007/9/14/one-reason-why-MySQL-sucks
  • if you need to do case-sensitive matching, declare your column as BINARY; don’t use LIKE BINARY in your queries to cast a non-binary column. If you do, MySQL won’t use any indexes on that column.
  • SELECT ... LIMIT offset, limit, where offset is a large number == bad, at least with InnoDB tables, as MySQL actually counts out every record until it gets to the offset. Ouch. Instead, assuming you have an auto-incremented id field, SELECT * FROM foo WHERE id >= offset AND id < offset+limit (props to Coda for that one).

I’ll add more as we come across them. If you have any other non-obvious tips, leave them in the comments or email me (brad@ this domain) and I’ll add them too.

By the way, an invaluable book for learning how to squeeze the most out of MySQL is High Performance MySQL, by Jeremy Zawodny and Derek Balling.

5th Nov, 2007

Tumblr

I decided to give tumblogging a try. I’m hoping that ease of use and lowered expectations will get me to post more often, and I’m liking it so far. It covers the large middle ground between blogging and twittering. I can put up something interesting without feeling like I need to construct a coherent post around it, and I don’t feel like I’m pestering people with twitters (not to mention that it allows for richer media types).

I realize I could just do the same thing on this blog if I wanted to, but there’s something about the minimalist formatting and ease-of-use that just seems to work. So I’ll continue to post longer pieces here (I’ve got a number that I need to just sit down and write), and I’ll keep posting inane comments to Twitter, but I imagine that the majority of interesting things I find will end up on my tumblelog.

22nd Sep, 2007

Social Graphs Must Die

Chris Wanstrath made a perfectly reasonable request earlier today, so I came up with the following Greasemonkey script:

// ==UserScript==
// @name Social Graphs Must Die
// @namespace Footle
// @description Redact all mentions of "social graphs"
// ==/UserScript==
document.body.innerHTML = document.body.innerHTML.replace(/(social\s+graph(s?))/ig,"<span style='background:#000;color:#000'>$1</span>");

Actually closing a tab (that wasn’t opened by Javascript) in Firefox 2 isn’t possible AFAICT, but this is more fun anyway.

14th Aug, 2007

I Get No Spam

After hearing a couple complaints from friends about the amount of spam they’re getting, I decided to take a quick look to see where I stood. At the risk of setting myself up, I should say that I get no spam (channeling John C. Dvorak). Or at least very little–maybe one every other day gets through to my inbox.

I think the trick is just to have multiple levels of filtering. I use a Gmail address for most online transactions (for online transactions that I really don’t care about–if I’m doing a one-time registration just to get a trial key, for example–I use my Yahoo address, which is nothing but a spam bucket). My Gmail address gets forwarded to my personal email address (@footle.org), which has SpamAssassin running on the server. If it makes it past SA to Mail.app, it gets inspected by SpamSieve, an awesome Bayesian spam filter for OS X mail clients.

Anyway, my stats for the last 24 hours:

Server-level filters (spam caught):
  SpamAssassin: 281
  Gmail: 32

Client-level filter (spam caught):
  SpamSieve: 17

Spam getting to my inbox: 0
Non-spam messages: 52
False positives: 1 (just Flavorpill, so not a big deal)

(Wow…330 pieces of spam in a single day. That seems way worse than when I last checked.)

Bonjour-enabled Git. (Although Coda tells me that Dave F. at Powerset already approached him with this idea. Neither of us have time to work on it.)

I started using Git recently and although it’s a bit confounding at times, I love how cheap and easy branching is, and that you can commit incremental changes to your local repository, then later commit them in batch to subversion. Git allows for distributed development, but unless you’re on the same server as other developers, or you go through the trouble of mounting their repository over the network, you can’t check out their repository. Seems like it shouldn’t be too difficult to give Git Bonjour powers.

We recently released a very slick Firefox extension at Wesabe. It was written by my colleague Tim Mason, but I helped figure out one small piece of it—namely, how to do binary multipart POSTs in Javascript—and since it involved many hours of hair-pulling for both of us, I thought I’d share the knowledge. (Tim says I should note that “this probably only works in Firefox version 2.0 and greater _and_ that it uses Mozilla specific calls that are only allowed for privileged javascript—basically only for extensions.”)

One of the cool features of the plugin is the ability to take a snapshot of a full browser page and either save the snapshot to disk or upload it to Wesabe (so you can, for example, save the receipt for a web purchase along with that transaction in your account). The snapshot is uploaded to Wesabe via a standard multipart POST, the same way that a file is uploaded via a web form.

Tim was having trouble getting the POST to work with binary data at first, and he had other things to finish, so he wanted to just base-64-encode it and be done with it. I was reluctant to do that, as the size of the upload would be significantly larger (about 137% of the original). Also, Rails didn’t automatically decode base-64-encoded file attachments. But Tim had other bugs to fix, so I submitted a patch to Rails to do the base-64 decoding. I was pretty proud of this patch until it was pointed out to me that RFC 2616 specifically disallows the use of Content-Transfer-Encoding in HTTP. Doh. They also realized that it is a colossal waste of bandwidth.

Since Tim was cramming to meet a hard(-ish) deadline set for the release of the plugin, I offered to lend my eyeballs to the binary post problem. This could be a very long story, but I’ll just get to the point: you can read binary data in to a Javascript string and dump it right out to a file just fine, but if you try to do any concatenation with that string, Javascript ends up munging it mercilessly. I’m not sure whether it is trying to interpret it as UTF8 or if it terminates it as soon as it hits a null byte (which is what seemed to be happening), but regardless, doing "some string" + binaryData + "another string", as is necessary when putting together a mutipart post, just does not work.

The answer required employing Rube Goldbergian system of input and output streams. The seed of the solution was found on this post, although that didn’t explain how to mix in all of the strings needed for the post and MIME envelope. So here it is, in all it’s goriness:


var dataURL = this.canvas.toDataURL(this.getImageType()); // grab the snapshot as base64
var imgData = atob(dataURL.substring(13 + this.getImageType().length)); // convert to binary

var filenameTimestamp = (new Date().getTime());
var separator = "----------12345 multipart boundary " + filenameTimestamp;

// Javascript munges binary data when it undergoes string operations (such as concatenation), so we need
// to jump through a bunch of hoops with streams to make sure that doesn't happen

// create a string input stream with the form preamble
var prefixStringInputStream = Components.classes["@mozilla.org/io/string-input-stream;1"].createInstance(Components.interfaces.nsIStringInputStream);
var formData =
	“–” + separator + “\\r\\n” +
	“Content-Disposition: form-data; name=\”data\”; filename=\”snapshot_” + filenameTimestamp +
	(this.getImageType() === “image/jpeg” ? “.jpg” : “.png”) + “\”\\r\\n” +
	“Content-Type: ” + this.getImageType() + “\\r\\n\\r\\n”;
prefixStringInputStream.setData(formData, formData.length);

// write the image data via a binary output stream, to a storage stream
var binaryOutputStream = Components.classes["@mozilla.org/binaryoutputstream;1"].createInstance(Components.interfaces.nsIBinaryOutputStream);
var storageStream = Components.classes["@mozilla.org/storagestream;1"].createInstance(Components.interfaces.nsIStorageStream);
storageStream.init(4096, imgData.length, null);
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
binaryOutputStream.writeBytes(imgData, imgData.length);
binaryOutputStream.close();

// write out the rest of the form to another string input stream
var suffixStringInputStream = Components.classes["@mozilla.org/io/string-input-stream;1"].createInstance(Components.interfaces.nsIStringInputStream);
formData =
	“\\r\\n–” + separator + “\\r\\n” +
	“Content-Disposition: form-data; name=\”description\”\\r\\n\\r\\n” + description + “\\r\\n” +
	“–” + separator + “–\\r\\n”;
suffixStringInputStream.setData(formData, formData.length);

// multiplex the streams together
var multiStream = Components.classes["@mozilla.org/io/multiplex-input-stream;1"].createInstance(Components.interfaces.nsIMultiplexInputStream);
multiStream.appendStream(prefixStringInputStream);
multiStream.appendStream(storageStream.newInputStream(0));
multiStream.appendStream(suffixStringInputStream);

// post it
req.open(”POST”, “http://yoursite.com/upload_endpoint”, true);
req.setRequestHeader(”Accept”, “*/*, application/xml”);
req.setRequestHeader(”Content-type”, “multipart/form-data; boundary=” + separator);
req.setRequestHeader(”Content-length”, multiStream.available());
req.setRequestHeader(”Authorization”, “Basic ” + btoa(username + “:” + password));
req.setRequestHeader(”User-Agent”, “YourUserAgent/1.0.0″);
req.send(multiStream);

Categories