<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:georss="http://www.georss.org/georss" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0"><id>tag:blogger.com,1999:blog-6525469191850690957</id><updated>2009-11-09T14:03:16.952-05:00</updated><title type="text">persistent.info</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.persistent.info/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.persistent.info/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default?start-index=26&amp;max-results=25" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>383</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="self" href="http://feeds.feedburner.com/PersistentInfo" type="application/atom+xml" /><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2FPersistentInfo" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FPersistentInfo" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2FPersistentInfo" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/PersistentInfo" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FPersistentInfo" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FPersistentInfo" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FPersistentInfo" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-4314454432572344746</id><published>2009-11-09T08:07:00.001-05:00</published><updated>2009-11-09T12:49:29.391-05:00</updated><title type="text">Google Reader and Closure Tools</title><content type="html">&lt;p&gt;Since Google Reader makes heavy use of the recently-open sourced &lt;a href="http://code.google.com/closure/"&gt;Closure Tools&lt;/a&gt;, &lt;a href="http://blog.louisgray.com/"&gt;Louis Gray&lt;/a&gt; asked me to give a client's perspective on using them. He wrote up &lt;a href="http://blog.louisgray.com/2009/11/story-and-impact-of-closure-googles.html"&gt;a great post&lt;/a&gt; summarizing my thoughts, and if you'd like to see the raw input, I've included it below:&lt;/p&gt;

&lt;hr size="1" noshade&gt;

&lt;p&gt;There are three pieces to the Closure Tools, the &lt;a href="http://code.google.com/closure/compiler/"&gt;compiler&lt;/a&gt;, the &lt;a href="http://code.google.com/closure/library/"&gt;library&lt;/a&gt; and the &lt;a href="http://code.google.com/closure/templates/"&gt;template&lt;/a&gt; system. They appeared gradually, roughly in that order. The compiler in its current incarnation dates back to Gmail in 2004 (which is why Paul Buchheit &lt;a href="http://ff.im/b2Dcn"&gt;refers to it&lt;/a&gt; as the "Gmail JavaScript compiler"), with the library and the template system starting a couple of years later.&lt;/p&gt;

&lt;p&gt;Reader development started in early 2005, which meant that we always had the compiler available to us, and so except for early prototypes, we always ran our code through it. Until the last month leading up to the Reader launch in &lt;a href="http://googleblog.blogspot.com/2005/10/feed-world.html"&gt;October 2005&lt;/a&gt;, the size benefits of the compiler were less important, since we were less focused on download time (and performance in general) and more on getting basic functionality up and running. Instead, the extra checks that the compiler does (e.g. if a function is called with the wrong number of parameters, typos in variable names) made it easier to catch errors much earlier. We have set up our development mode for Reader so that when the browser is refreshed, the JavaScript is recompiled on the server and is used with the page when it is reloaded. This results in a tight development loop that makes it possible to catch JavaScript errors as early as possible.&lt;/p&gt;

&lt;p&gt;Since Reader development started before the library and template tools were available, we had homegrown code for doing both. There was actually shared code that did some of the same things as basic library functionality (e.g. a wrapper around getting the size of the window, handling different browser versions and quirks). However, that shared code was of various vintages (copied from project to project) and therefore not very consistent in style or quality. &lt;a href="http://erik.eae.net/archives/2009/11/05/22.27.29/"&gt;Erik Arvidsson's post&lt;/a&gt; talks a bit more about the inception of the Closure library (he's one of the co-creators, along with &lt;a href="http://pupius.co.uk/blog/"&gt;Dan Pupius&lt;/a&gt;).&lt;br /&gt;

&lt;p&gt;Reader began using the Closure library and template systems gradually, first for new code and then replacing usages of the old shared library and our homegrown code. It was a gradual process, though I tried to keep it organized by doing an audit of all the usages of old code and their Closure equivalents, so that work could more easily be divided up (this was handled during "fixit" periods, where we focus on code quality more than features).&lt;/p&gt;

&lt;p&gt;The benefits of the compiler system are tremendous. The most obvious are the size ones, without it Reader's JavaScript would be 2 megaytes, with it it goes down to 513K, and 184K with gzip (the compiler's output is actually optimized for gzip, since nearly all browsers support it). However, all of the above-mentioned checks, as well as many more that have been added over the past few years (especially type annotations) make it much more manageable to have a large JavaScript codebase that doesn't get out of control as it ages and accumulates features.&lt;/p&gt;

&lt;p&gt;The library means that Reader is much less concerned about browser differences, since it tries very hard to hide all those away. Over time, the library has also moved up the UI "stack", going from just basic low level code (e.g. for handling events) to doing UI widgets. This means that it's not a lot of work to do auto-complete widgets, menus, buttons, dialogs, drag-and-drop, etc. in Reader. &lt;/p&gt;

&lt;p&gt;One thing to keep in mind is that, as mentioned in &lt;a href="http://googlecode.blogspot.com/2009/11/introducing-closure-tools.html"&gt;the announcement blog post&lt;/a&gt;, these tools all started out as 20% projects, and for the most part are still dependent on it. If one project needs a feature from the compiler or the library that doesn't exist, they're encouraged to contribute it, so that other teams can benefit too. To give a specific example, Reader had some home-grown code for locating elements by class name and tag name (a much more rigid and simplified version of the flexible CSS selector-based queries that you can do with jQuery or with the Dojo-based &lt;a href="http://code.google.com/p/closure-library/source/browse/trunk/third_party/closure/goog/dojo/dom/query.js"&gt;goog.dom.query&lt;/a&gt;). As part of the process of "porting" to the Closure library, we realized that though there was an equivalent library function, &lt;a href="http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/closure_goog_dom_dom.js.html#goog.dom.getElementsByTagNameAndClass"&gt;goog.dom.getElementsByTagNameAndClass&lt;/a&gt;, it didn't use some of the more recent browser APIs that could it make it much faster (e.g. &lt;a href="https://developer.mozilla.org/en/DOM/document.getElementsByClassName"&gt;getElementsByClassName&lt;/a&gt; and the &lt;a href="http://www.w3.org/TR/selectors-api/"&gt;W3C Selector API&lt;/a&gt;). Therefore we not only switched Reader's code to use the Closure version, but we also incorporated those new API calls in it. This ended up making all other apps faster; it was very nice to get a message from Dan Pupius saying that the change had shaved off a noticeable amount of time in a common Gmail operation.&lt;/p&gt;

&lt;p&gt;You can tell that there's something special about this when you look at &lt;a href="http://twitter.com/cw/status/5456937973"&gt;the&lt;/a&gt; &lt;a href="http://blog.bolinfest.com/2009/11/google-releases-closure-tools.html"&gt;ex-Googlers&lt;/a&gt; &lt;a href="http://twitter.com/krave/status/5456654337"&gt;cheering&lt;/a&gt; &lt;a href="http://ff.im/b2Dcn"&gt;its&lt;/a&gt; &lt;a href="http://twitter.com/mikeee/status/5463653549"&gt;release&lt;/a&gt;. If it had been some proprietary antiquated system that they had all been forced to use, they wouldn't have been so excited that it was out in the open now :)&lt;/p&gt;

&lt;p&gt;If you'd like to know more about Closure, I recommend keeping an eye on &lt;a href="http://blog.bolinfest.com/"&gt;Michael Bolin's blog&lt;/a&gt;. He already has &lt;a href="http://bolinfest.com/javascript/inheritance.php"&gt;a&lt;/a&gt; &lt;a href="http://blog.bolinfest.com/2009/11/google-releases-closure-tools.html"&gt;few&lt;/a&gt; &lt;a href="http://blog.bolinfest.com/2009/11/closure-compiler-turns-pattern-into.html"&gt;posts&lt;/a&gt; about what makes it special, and I'm sure there are more coming.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-4314454432572344746?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/GVHNRTC1j5k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/4314454432572344746/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2009/11/google-reader-and-closure-tools.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4314454432572344746" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4314454432572344746" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/GVHNRTC1j5k/google-reader-and-closure-tools.html" title="Google Reader and Closure Tools" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.persistent.info/2009/11/google-reader-and-closure-tools.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-4324759879039187414</id><published>2009-10-12T10:04:00.003-04:00</published><updated>2009-10-12T21:49:03.940-04:00</updated><title type="text">PuSH Bot: PubSubHubbub to XMPP Gateway</title><content type="html">&lt;p&gt;When &lt;a href="http://googleappengine.blogspot.com/2009/09/app-engine-sdk-125-released-for-python.html"&gt;XMPP support in Google App Engine&lt;/a&gt; was announced, it occurred to me that it would be pretty easy to use it to do a &lt;a href="pubsubhubbub.googlecode.com/"&gt;PubSubHubbub&lt;/a&gt;-to-XMPP bridge. Other things came up, but I was reminded again of the possibility when a "Oh, you didn't you see my Reader share?" conversation happened in a &lt;a href="http://partychapp.appspot.com/"&gt;Partychat&lt;/a&gt; room. A bit of searching turned up &lt;a href="http://grack.com/blog/2009/09/09/pubsubhubbub-to-xmpp-gateway/"&gt;someone else&lt;/a&gt; with &lt;a href="http://pubsubhubbub-xmpp.appspot.com/"&gt;the same idea&lt;/a&gt;, except it wasn't quite as user-friendly as it could be (i.e. as user friendly as a quasi-command-line interface, which isn't saying much).&lt;/p&gt;

&lt;p&gt;After a bit of weekend hacking I've created &lt;a href="http://push-bot.appspot.com/"&gt;PuSH Bot&lt;/a&gt;, which lets you subscribe to any PubSubHubbub-enabled feed and get notified of updates via XMPP (e.g. to your Google Talk account). It has some niceties like use of feed auto-discovery so you can just specify web page URLs, OPML import for bulk subscribing and throttling of updates. &lt;a href="http://push-bot.appspot.com/"&gt;The homepage&lt;/a&gt; lets you know how to get started.&lt;/p&gt;

&lt;p style="text-align: center"&gt;
&lt;img src="http://persistent.info/images/push-bot.png" width="448" height="237" alt="PuSH Bot Screenshot"&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="http://code.google.com/p/partychapp/source/browse/#svn/trunk/pushbot"&gt;The code&lt;/a&gt; is available, though there shouldn't be anything too special about it. Feed parsing is done via &lt;a href="https://rome.dev.java.net/"&gt;ROME&lt;/a&gt; (as is &lt;a href="http://wiki.java.net/bin/view/Javawsxml/OPML"&gt;OPML parsing&lt;/a&gt;, though that needed &lt;a href="http://markmail.org/message/2azavtuzcrkzdhqh"&gt;some patching&lt;/a&gt; to get working). Feed auto-discovery is handled by &lt;a href="http://code.google.com/apis/ajaxfeeds/documentation/reference.html#LookupResultFormat"&gt;Google AJAX Feed API&lt;/a&gt; because life is too short for HTML parsing.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-4324759879039187414?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/cswrp29jh80" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/4324759879039187414/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2009/10/push-bot-pubsubhubbub-to-xmpp-gateway.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4324759879039187414" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4324759879039187414" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/cswrp29jh80/push-bot-pubsubhubbub-to-xmpp-gateway.html" title="PuSH Bot: PubSubHubbub to XMPP Gateway" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.persistent.info/2009/10/push-bot-pubsubhubbub-to-xmpp-gateway.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-4461026376082300074</id><published>2009-08-26T10:46:00.001-04:00</published><updated>2009-08-26T22:23:41.890-04:00</updated><title type="text">Twitter PubSubHubbub Bridge</title><content type="html">&lt;p&gt;During the Twitter &lt;a href="http://status.twitter.com/post/157191978/ongoing-denial-of-service-attack"&gt;DDoS attacks&lt;/a&gt;, there was &lt;a href="http://groups.google.com/group/twitter-development-talk/browse_thread/thread/8665766f5e262d60"&gt;a thread&lt;/a&gt; on the Twitter API group about using &lt;a href="http://pubsubhubbub.googlecode.com/"&gt;PubSubHubbub&lt;/a&gt; to get low latency notifications from Twitter. This would be an alternative to the &lt;a href="http://apiwiki.twitter.com/Streaming-API-Documentation"&gt;streaming API&lt;/a&gt; that Twitter already has. The response from a &lt;a href="http://twitter.com/jkalucki"&gt;Twitter engineer&lt;/a&gt; wasn't all that positive, and it is indeed correct that the streaming API already exists and seems to satisfy most developers' needs.&lt;/p&gt;

&lt;p&gt;However, my interest was piqued and I thought it might be a useful exercise to see what Twitter PubSubHubbub support could look like. I therefore decided to write a simple bridge between the streaming API and a PubSubHubbub hub. The basic idea was that there would be a simple streaming client that would in turn publish events to a hub. The basic flow would be:&lt;/p&gt;

&lt;p style="text-align: center;font-size:smaller; color: #999;"&gt;&lt;img src="http://persistent.info/images/twitter-pshb-flow1.png" width="300" height="450" alt="Twitter PubSubHubbub flow 1"&gt;&lt;br&gt;(created using &lt;A href="http://www.kushaldave.com/"&gt;Kushal&lt;/a&gt;'s &lt;a href="http://diagrammr.com/"&gt;Diagrammr&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I'm using FriendFeed as the PubSubHubbub client, but obviously anything else could substitute for it. The "publisher" is where the bulk of the work happens. It uses the &lt;a href="http://apiwiki.twitter.com/Streaming-API-Documentation#statuses/filter"&gt;statuses/filter&lt;/a&gt; streaming API method* to get notified of when a user of interest has posted, and then it notifies the &lt;a href="http://pubsubhubbub.appspot.com/"&gt;reference hub&lt;/a&gt; that there is an update. It also has a companion Google App Engine &lt;a href="http://twitter-pshb.appspot.com/"&gt;app&lt;/a&gt; that serves feeds for Twitter updates. This is both because the hub needs a feed to crawl and because the feed needs to have a &lt;code&gt;&amp;lt;link rel="hub"&amp;gt;&lt;/code&gt; element, something which Twitter's own feeds don't have. Unfortunately the publisher itself can't run on App Engine since the streaming API requires long-lived HTTP connections, and App Engine will not let requests execute for more than 30 seconds. I considered using the &lt;a href="http://code.google.com/appengine/docs/python/taskqueue/"&gt;tasks queue&lt;/a&gt; API to create a succession of connections, but that seemed too hacky.&lt;/p&gt;

&lt;p&gt;In any case, it all seems to work, as this screencast shows:&lt;/p&gt;

&lt;p style="text-align: center"&gt;&lt;object width="560" height="291"&gt;
  &lt;param name="movie" value="http://www.youtube.com/v/P-9KoVwQFVQ&amp;hl=en&amp;fs=1&amp;rel=0&amp;showinfo=0"&gt;&lt;/param&gt;
  &lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;
  &lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;
  &lt;embed src="http://www.youtube.com/v/P-9KoVwQFVQ&amp;hl=en&amp;fs=1&amp;rel=0&amp;showinfo=0" 
      type="application/x-shockwave-flash"
      allowscriptaccess="always"
      allowfullscreen="true"
      width="560"
      height="291"&gt;
  &lt;/embed&gt;
&lt;/object&gt;&lt;/p&gt;

&lt;p&gt;On the right is the Twitter UI where messages are posted. In the middle is the publisher which receives these messages and relays them to the hub. On the left is FriendFeed which gets updates from the Hub.&lt;/p&gt;

&lt;p&gt;Latency isn't great, and as mentioned in the group &lt;a href="http://groups.google.com/group/twitter-development-talk/browse_thread/thread/8665766f5e262d60"&gt;thread&lt;/a&gt;, Twitter could have to deal with the hub being slow. Part of the reason why latency isn't great is because the hub has to crawl the feed to get at the update, even though the publisher already knows exactly what the update is. This could be fixed by running a custom hub (possibly even by Twitter, see the &lt;a href="http://code.google.com/p/pubsubhubbub/wiki/PublisherEfficiency#Serving_feeds_from_multiple_datacenters"&gt;hub can be integrated into the publisher's content management system&lt;/a&gt; option), with the flow becoming something like:&lt;/p&gt;

&lt;p style="text-align: center"&gt;&lt;img src="http://persistent.info/images/twitter-pshb-flow2.png" width="220" height="320" alt="Twitter PubSubHubbub flow 2"&gt;&lt;/p&gt;

&lt;p&gt;In the meantime, here's &lt;a href="http://persistent.info/files/twitter-pshb.zip"&gt;the source&lt;/a&gt; to both the publisher and the app.&lt;/p&gt;

&lt;p&gt;* This was called the "follow" method until very recently.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-4461026376082300074?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/8v8HpW5CBj4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/4461026376082300074/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2009/08/twitter-pubsubhubbub-bridge.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4461026376082300074" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4461026376082300074" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/8v8HpW5CBj4/twitter-pubsubhubbub-bridge.html" title="Twitter PubSubHubbub Bridge" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://blog.persistent.info/2009/08/twitter-pubsubhubbub-bridge.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-1663740353737766664</id><published>2009-08-24T23:10:00.000-04:00</published><updated>2009-08-24T23:10:56.682-04:00</updated><title type="text">Twitter Streaming API from Python</title><content type="html">&lt;p&gt;I'm playing around with Twitter's &lt;a href="http://apiwiki.twitter.com/Streaming-API-Documentation"&gt;streaming API&lt;/a&gt; for a (personal) project. &lt;a href="http://bitbucket.org/runeh/tweetstream/src/"&gt;tweetstream&lt;/a&gt; is a simple wrapper for it that seemed handy. Unfortunately it has &lt;a href="http://bitbucket.org/runeh/tweetstream/issue/2/followstream-not-working"&gt;a known issue&lt;/a&gt; that the HTTP library that it uses (&lt;a href="http://docs.python.org/library/urllib2.html"&gt;urllib2&lt;/a&gt;) uses buffering in the file object that it creates, which means that responses for low volume streams (e.g. when using the &lt;code&gt;follow&lt;/code&gt; method) are not delivered immediately. The culprit appears to be this line from &lt;code&gt;urllib2.py&lt;/code&gt; (in the &lt;code&gt;AbstractHTTPHandler&lt;/code&gt; class's &lt;code&gt;do_open&lt;/code&gt; method):&lt;/p&gt;

&lt;pre&gt;fp = socket._fileobject(r, close=True)&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;socket._fileobject&lt;/code&gt; does have a &lt;code&gt;bufsize&lt;/code&gt; parameter, and its default value is 8192. Unfortunately the &lt;code&gt;AbstractHTTPHandler&lt;/code&gt; doesn't make it easy to override the file object creation. As is pointed out in the bug report, using &lt;a href="http://docs.python.org/library/httplib.html"&gt;httplib&lt;/a&gt; directly would allow this to be worked around, but that would mean losing all of the 401 response/HTTP Basic Auth handling that &lt;code&gt;urllib2&lt;/code&gt; has.&lt;/p&gt;

&lt;p&gt;Instead, while holding my nose, I chose the following monkey patching solution:&lt;/p&gt;

&lt;pre&gt;# Wrapper around socket._fileobject that forces the buffer size to be 0
_builtin_socket_fileobject = socket._fileobject
class _NonBufferingFileObject(_builtin_socket_fileobject):
  def __init__(self, sock, mode='rb', bufsize=-1, close=False):
    builtin_socket_fileobject.__init__(
        self, sock, mode=mode, bufsize=0, close=close)

# Wrapper around urllub2.HTTPHandler that monkey-patches socket._fileobject
# to be a _NonBufferingFileObject so that buffering is not use in the response
# file object
class _NonBufferingHTTPHandler(urllib2.HTTPHandler):
  def do_open(self, http_class, req):
    socket._fileobject = _NonBufferingFileObject
    # urllib2.HTTPHandler is a classic class, so we can't use super()
    resp = urllib2.HTTPHandler.do_open(self, http_class, req)
    socket._fileobject = _builtin_socket_fileobject
    return resp&lt;/pre&gt;

&lt;p&gt;Then in &lt;code&gt;tweetstream&lt;/code&gt;'s &lt;code&gt;urllib2.build_opener()&lt;/code&gt; call an instance of &lt;code&gt; _NonBufferingHTTPHandler&lt;/code&gt; can be added as a parameter, and it will replace the built-in &lt;code&gt; HTTPHandler&lt;/code&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-1663740353737766664?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/Wz6a_R5kUic" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/1663740353737766664/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2009/08/twitter-streaming-api-from-python.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/1663740353737766664" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/1663740353737766664" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/Wz6a_R5kUic/twitter-streaming-api-from-python.html" title="Twitter Streaming API from Python" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://blog.persistent.info/2009/08/twitter-streaming-api-from-python.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-5703572610587577122</id><published>2009-07-16T12:02:00.002-04:00</published><updated>2009-10-18T11:51:31.880-04:00</updated><title type="text">Exporting likes from Google Reader</title><content type="html">&lt;i&gt;&lt;p&gt;I started this as another protip comment on &lt;a href="http://friendfeed.com/jenna/de01dd1d/protip-like-something-in-reader-while-konami"&gt;this  FriendFeed thread about Reader likes&lt;/a&gt; but it got kind of long, so here goes:&lt;/p&gt;&lt;/i&gt;

&lt;p&gt;Reader recently launched liking (and a &lt;a href="http://googlereader.blogspot.com/2009/07/following-liking-and-people-searching.html"&gt;bunch of other features&lt;/a&gt;). One of the nice things about liking is that it's completely public*. It would therefore make sense to be pretty liberal with liking data, and in fact Reader does try to expose liking in our feeds. If you look at &lt;a href="http://www.google.com/reader/public/atom/user/14548369432350969777/state/com.google/broadcast"&gt;my shared items feed&lt;/a&gt; you will see a bunch of entries like:&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #c0c";&gt;&amp;lt;gr:likingUser&amp;gt;&lt;/span&gt;00298835408679692061&lt;span style="color: #c0c";&gt;&amp;lt;/gr:likingUser&amp;gt;&lt;/span&gt;
&lt;span style="color: #c0c";&gt;&amp;lt;gr:likingUser&amp;gt;&lt;/span&gt;11558879684172144796&lt;span style="color: #c0c";&gt;&amp;lt;/gr:likingUser&amp;gt;&lt;/span&gt;
&lt;span style="color: #c0c";&gt;&amp;lt;gr:likingUser&amp;gt;&lt;/span&gt;07538649935038400809&lt;span style="color: #c0c";&gt;&amp;lt;/gr:likingUser&amp;gt;&lt;/span&gt;
&lt;span style="color: #c0c";&gt;&amp;lt;gr:likingUser&amp;gt;&lt;/span&gt;09776139491686191852&lt;span style="color: #c0c";&gt;&amp;lt;/gr:likingUser&amp;gt;&lt;/span&gt;
&lt;span style="color: #c0c";&gt;&amp;lt;gr:likingUser&amp;gt;&lt;/span&gt;02408713980432217881&lt;span style="color: #c0c";&gt;&amp;lt;/gr:likingUser&amp;gt;&lt;/span&gt;
&lt;span style="color: #c0c";&gt;&amp;lt;gr:likingUser&amp;gt;&lt;/span&gt;05429296530037195610&lt;span style="color: #c0c";&gt;&amp;lt;/gr:likingUser&amp;gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;These are the users that have liked. Users are represented by their IDs, which you can use to generate &lt;a href="http://www.google.com/reader/shared/05429296530037195610"&gt;Reader shared page URLs&lt;/a&gt;. More interestingly, you can plug these into &lt;a href="http://socialgraph-resources.googlecode.com/svn/trunk/samples/findyours.html?q=http%3A%2F%2Fwww.google.com%2Freader%2Fshared%2F05429296530037195610"&gt;the Social Graph API&lt;/a&gt; to see who these users are.&lt;/p&gt;

&lt;p&gt;Liking information isn't just limited to Reader shared item feeds. If you use Reader's view of a feed, for example &lt;a href="http://www.google.com/reader/public/atom/feed/http%3A%2F%2Fwww.boston.com%2Fbigpicture%2Findex.xml"&gt;The Big Picture's&lt;/a&gt;, you can see the &lt;code&gt;&amp;lt;gr:likingUser&amp;gt;&lt;/code&gt; elements there too. This means that as a publisher you can extract this information and see which of your items Reader users find interesting.&lt;/p&gt;

&lt;p&gt;For now liking information that is included inline in the feed is limited to 100 users, mainly for performance reasons. That number may go up (or down) as we see how this feature is used. However, if you'd like to get at all of the liker information for a specific item, you can plug in an item ID into the &lt;code&gt;/reader/api/0/likers&lt;/code&gt; API endpoint, and then get at it in either &lt;a href="http://www.google.com/reader/api/0/item/likers?i=tag:google.com,2005:reader/item/8576bd3b572cf58c&amp;output=json"&gt;JSON&lt;/a&gt; or &lt;a href="http://www.google.com/reader/api/0/item/likers?i=tag:google.com,2005:reader/item/8576bd3b572cf58c&amp;output=xml"&gt;XML&lt;/a&gt; formats.&lt;/p&gt;

&lt;p&gt;* I've seen some wondering what the difference between liking, sharing and starring is. To some degree that's up to each user, but one nice thing about liking is that it has less baggage associated with it. We learned that if we try to redefine existing behaviors (like sharing) &lt;a href="http://fhonearth.blogspot.com/2007/12/google-reader-shares-private-data-ruins.html"&gt;users get upset&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-5703572610587577122?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/1oWItf14Yeg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/5703572610587577122/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2009/07/exporting-likes-from-google-reader.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/5703572610587577122" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/5703572610587577122" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/1oWItf14Yeg/exporting-likes-from-google-reader.html" title="Exporting likes from Google Reader" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.persistent.info/2009/07/exporting-likes-from-google-reader.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-5491358834351725045</id><published>2009-03-03T20:07:00.001-05:00</published><updated>2009-07-16T09:55:30.440-04:00</updated><title type="text">HTML Color OneBox</title><content type="html">&lt;p&gt;&lt;a href="http://www.google.com/"&gt;Work&lt;/a&gt; is the sort of place that &lt;a href="http://www.nytimes.com/2009/03/01/business/01marissa.html"&gt;cares&lt;/a&gt; about specific colors, so HTML color hex triplets come up in conversation quite often. &lt;a href="http://twitter.com/nsanch"&gt;Neil&lt;/a&gt; suggested that this should be a OneBox in search results. It occurred to me that this could done via the &lt;a href="http://www.google.com/coop/subscribedlinks/"&gt;Subscribed Link&lt;/a&gt; feature that we offer for search results. It turned out that subscribed links can use &lt;a href="http://www.google.com/coop/docs/subscribedlinks/ui.html#gadgets"&gt;gadgets&lt;/a&gt;, which meant that an inline preview of colors was even possible. Regular expression matching also meant that I didn't have to list out every color by hand. &lt;a href="http://persistent.info/coloronebox/"&gt;This page&lt;/a&gt; has more information on the OneBox, or you can &lt;a href="http://www.google.com/coop/trust/add?user=004304562003907849137&amp;sig=__dNjpUKWgIyi6nkMystUTqVEfcJA="&gt;subscribe&lt;/a&gt; directly. &lt;/p&gt;

&lt;p style="text-align: center"&gt;&lt;img src="http://persistent.info/images/coloronebox.png" width="488" height="170" alt="Color OneBox preview"&gt;&lt;/p&gt;

&lt;p&gt;Once you have installed this, you can search for things like &lt;a href="http://www.google.com/search?q=%23fafafa"&gt;#fafafa&lt;/a&gt; or &lt;a href="http://www.google.com/search?q=%23ccc"&gt;#ccc&lt;/a&gt; and get an immediate preview (in fact, the # can be omitted).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-5491358834351725045?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/1Jv_LiyuDCE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/5491358834351725045/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2009/03/html-color-onebox.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/5491358834351725045" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/5491358834351725045" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/1Jv_LiyuDCE/html-color-onebox.html" title="HTML Color OneBox" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.persistent.info/2009/03/html-color-onebox.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-3280863692104848287</id><published>2008-05-11T20:26:00.002-04:00</published><updated>2008-11-23T22:07:18.245-05:00</updated><title type="text">Porting Twitter Digest to Google App Engine</title><content type="html">&lt;p&gt;I've been meaning to play around with &lt;a href="http://appengine.google.com/"&gt;Google App Engine&lt;/a&gt; for a while, and as a quick project, I decided to port &lt;a href="http://blog.persistent.info/2007/08/twitter-digest.html"&gt;Twitter Digest&lt;/a&gt; to it (not as exciting as Kushal's &lt;a href="http://blog.kushaldave.com/2008/04/30/introducing-the-millidunst-calculator/"&gt;Millidunst Calculator&lt;/a&gt;). This looked to be pretty straightforward: the original version was already in Python, and wasn't very complicated (just a single CGI script). It did indeed end up pretty easy; the whole process took a couple of hours.&lt;/p&gt;

&lt;p&gt;The first step was to port the script from CGI-style invocation to the App Engine &lt;a href="http://code.google.com/appengine/docs/webapp/"&gt;webapp&lt;/a&gt; framework. Then I looked into what it would take to get &lt;a href="http://code.google.com/p/python-twitter/"&gt;Python Twitter&lt;/a&gt; (the library I used for fetching data from Twitter) running. Switching it from urllib2 to &lt;a href="http://code.google.com/appengine/docs/urlfetch/"&gt;urlfetch&lt;/a&gt; was pretty painless (though I don't use the posting parts of the API, so I didn't check if those work too). The other part of the library that I was relying on was its caching mechanism (since the digests are daily, there's no point in querying Twitter more often). &lt;a href="http://blog.unto.net/"&gt;DeWitt&lt;/a&gt; (the library's author) had thoughtfully put the caching functionality into a separate class, so it was easy to replace it with another one that implemented the same interface but was backed by App Engine's &lt;a href="http://code.google.com/appengine/docs/datastore/"&gt;datastore&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://twitter-digest.appspot.com/"&gt;The result&lt;/a&gt; (complete with &lt;a href="http://appgallery.appspot.com/about_app?app_id=agphcHBnYWxsZXJ5chMLEgxBcHBsaWNhdGlvbnMYyQsM"&gt;App Gallery entry&lt;/a&gt;) is not that exciting, in the sense that it functions identically to &lt;a href="http://persistent.info/twitter-digest/"&gt;the original&lt;/a&gt;. The only issue that I've run into so far is that when there are several cache misses, the URL fetches can take long enough that the request hits App Engine's deadline. However, since the successful fetches are cached, repeating the request will eventually succeed (so if consuming the digest via a feed, this shouldn't be a big deal). Ideally the &lt;code&gt;urlfetch&lt;/code&gt; functionality would also support asynchronous fetches, since it would be easy to adapt the code to fetch all user timelines in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update on 11/23/2008:&lt;/strong&gt; Since I've gotten some requests for the modifications to &lt;code&gt;twitter-digest&lt;/code&gt; that I made to get it to run on App Engine, here's &lt;a href="http://persistent.info/files/twitter_appengine.patch"&gt;a patch&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-3280863692104848287?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/i3KzW18rzxM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/3280863692104848287/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2008/05/porting-twitter-digest-to-google-app.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/3280863692104848287" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/3280863692104848287" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/i3KzW18rzxM/porting-twitter-digest-to-google-app.html" title="Porting Twitter Digest to Google App Engine" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.persistent.info/2008/05/porting-twitter-digest-to-google-app.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-3393553598065557339</id><published>2008-04-24T08:38:00.000-04:00</published><updated>2008-04-24T08:39:16.636-04:00</updated><title type="text">Intern on the Google Reader team</title><content type="html">&lt;p&gt;Having interns has worked out well for the Reader team. Following &lt;a href="http://blog.persistent.info/2007/03/intern-on-google-reader-team.html"&gt;my blog post&lt;/a&gt;, we were very pleased to get Nitin Shantharam and Jason Hall to help us out with Reader development. Their stints on the team resulted in a &lt;a href="http://googlereader.blogspot.com/2007/11/attack-of-interns-recommendations-and.html"&gt;a bunch of features&lt;/a&gt;, and Jason is now back at Google working full-time (Nitin wasn't a slacker, he's just still in school).&lt;/p&gt;

&lt;p&gt;We're looking for another intern or two this year. Internships generally last a couple of months to twelve weeks, are for full-time students, and would be in Google's &lt;a href="http://www.google.com/support/jobs/bin/static.py?page=why-ca-mv.html"&gt;Mountain View, California&lt;/a&gt; office. You can work on either Reader's backend (a C++ system for crawling millions of feeds, handling lots of items being read, shared, starred or tagged per second) or frontend (Java servers and JavaScript/AJAX-y craziness) depending on your interests and experience.&lt;/p&gt;

&lt;p&gt;If you or anyone you know is interested in this internship, contact me at &lt;code&gt;mihaip at google dot com&lt;/code&gt;. &lt;a href="http://www.google.com/support/jobs/bin/static.py?page=students.html&amp;sid=internships"&gt;This page&lt;/a&gt; also has more general information about interning at Google.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-3393553598065557339?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/qPpPahJHeL0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/3393553598065557339/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2008/04/intern-on-google-reader-team.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/3393553598065557339" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/3393553598065557339" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/qPpPahJHeL0/intern-on-google-reader-team.html" title="Intern on the Google Reader team" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.persistent.info/2008/04/intern-on-google-reader-team.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-7276228201441256054</id><published>2008-04-03T14:46:00.000-04:00</published><updated>2008-04-03T14:47:17.633-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Meta" /><title type="text">persistent.coffee</title><content type="html">&lt;p&gt;I've been trying my hand at &lt;a href="http://en.wikipedia.org/wiki/Latte_art"&gt;latte art&lt;/a&gt;. Though I have &lt;a href="http://www.flickr.com/photos/tonx/sets/48921/"&gt;a very long way to go&lt;/a&gt;, I've been  documenting my efforts, with a hope of learning from my mistakes. Blogger's &lt;a href="http://www.blogger.com/mobile-start.g"&gt;mobile&lt;/a&gt; support makes it pretty easy to collect pictures, and I've finally gotten around to making a decent template for the "blog."&lt;/p&gt;

&lt;p&gt;&lt;a href="http://coffee.persistent.info"&gt;coffee.persistent.info&lt;/a&gt; is the result. Technically, this isn't a Blogger template, since I just have some static HTML as the content. Instead, it uses the &lt;a href="http://googledataapis.blogspot.com/2006/11/calling-all-web-hackers-json-support.html"&gt;JSON output&lt;/a&gt; that Blogger's GData API supports. Rendering the page in JavaScript allows for more flexibility. I wanted to make pictures that I liked take up 4 slots (a layout inspired by &lt;a href="http://twitterposter.com/"&gt;TwitterPoster&lt;/a&gt;). This imposed additional constraints (in order to prevent overlap between sequential large pictures). The display is generally reverse-chronological starting from the top left, but images are occasionally shuffled around to prevent such overlaps. There is also a bit of interactivity, the pictures are clickable to display larger versions. To help with all this, I've been experimenting with &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; (also on &lt;a href="http://code.google.com/p/mail-trends/"&gt;Mail Trends&lt;/a&gt;), and am liking it quite a bit.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-7276228201441256054?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/xvG2VpvPykw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/7276228201441256054/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2008/04/persistentcoffee.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/7276228201441256054" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/7276228201441256054" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/xvG2VpvPykw/persistentcoffee.html" title="persistent.coffee" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://blog.persistent.info/2008/04/persistentcoffee.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-2375831617290146364</id><published>2008-03-25T10:32:00.011-04:00</published><updated>2008-03-25T10:53:22.711-04:00</updated><title type="text">Mail Trends</title><content type="html">&lt;p&gt;&lt;img src="http://persistent.info/images/mail-trends-graph.png" width="250" height="145" align="left" style="padding: 5px 3px 2px 0"&gt;I get a lot of email (especially at work). I'm trying a &lt;a href="http://www.43folders.com/izero"&gt;Inbox Zero&lt;/a&gt;-&lt;wbr&gt;&lt;/wbr&gt;like approach in order to keep up with it. Though that's helping me to stay on top of things, I had the nagging feeling that I was probably on too many mailing lists, and that some of them were probably not worth it from a signal-to-noise ratio perspective.&lt;/p&gt;

&lt;p&gt;Ideally something like the &lt;a href="http://googlereader.blogspot.com/2007/01/i-like-big-charts-and-i-cannot-lie.html"&gt;Reader Trends&lt;/a&gt; or &lt;a href="http://www.google.com/history/trends"&gt;Search History Trends&lt;/a&gt; page would exist for Gmail. I thought I could perhaps build it myself, but the absence of an official Gmail API deterred me. However, it occurred to me that the recently added &lt;a href="http://mail.google.com/support/bin/answer.py?hl=en&amp;answer=75725"&gt;IMAP support&lt;/a&gt; could act as an API of sorts. It should be easy to get just the message headers and slice and dice them to extract the stats that I was interested in.&lt;/p&gt;

&lt;p&gt;Thus was born &lt;a href="http://code.google.com/p/mail-trends/"&gt;Mail Trends&lt;/a&gt;, an IMAP-&lt;wbr&gt;&lt;/wbr&gt;based email analysis project. It can generate a bunch of tables, graphs and distributions based on time of day, senders, recipients, mailing lists, etc. To get a feel for what it can output, see &lt;a href="http://persistent.info/mail-trends/enron/"&gt;the results&lt;/a&gt; of running it on a piece of the &lt;a href="http://www.cs.cmu.edu/~enron/"&gt;Enron Email Dataset&lt;/a&gt;. To run it over your own email, see the &lt;a href="http://code.google.com/p/mail-trends/wiki/GettingStarted"&gt;getting started page&lt;/a&gt;. As a caveat, the program currently loads everything into memory, so my run on 200,000 messages resulted in 1.6 gigabytes being used. You may want to use the &lt;code&gt;--max_messages=&lt;/code&gt; flag to limit the dataset, at list for initial runs.&lt;/p&gt;

&lt;p&gt;The project is still in its early stages, so patches and suggestions are definitely welcome (my email address is at the footer). You can also subscribe to &lt;a href="http://persistent.info/mail-trends/svnlog.xml"&gt;the feed of check-ins&lt;/a&gt; to see changes as they are made. The &lt;a href="http://code.google.com/p/mail-trends/wiki/Plan"&gt;plan&lt;/a&gt; wiki page has a very brief outline of what I'm planning on working on next.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-2375831617290146364?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/-rKvu4ZEbcE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/2375831617290146364/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2008/03/mail-trends.html#comment-form" title="21 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/2375831617290146364" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/2375831617290146364" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/-rKvu4ZEbcE/mail-trends.html" title="Mail Trends" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">21</thr:total><feedburner:origLink>http://blog.persistent.info/2008/03/mail-trends.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-4967715591011246001</id><published>2008-03-22T14:23:00.001-04:00</published><updated>2008-03-22T14:25:13.538-04:00</updated><title type="text">Two Safari 3.1 Tips</title><content type="html">&lt;p&gt;&lt;a href="http://www.apple.com/safari"&gt;Safari 3.1&lt;/a&gt; is out, and I've upgraded my Mac to it. Besides some issues with arrow keys in Reader (we're on it), it's working out well. Here are two hidden prefs that you may find useful:&lt;/p&gt;

&lt;pre&gt;defaults write com.apple.Safari IncludeInternalDebugMenu -bool true&lt;/pre&gt;

&lt;p&gt;The &lt;a href="http://www.simplebits.com/notebook/2008/03/18/safari.html"&gt;Develop&lt;/a&gt; menu that 3.1 includes is nice, but it seems to supplant the old "Debug" menu (i.e. the preference key that used to toggle it - &lt;code&gt;IncludeDebugMenu&lt;/code&gt; - now toggles the "Develop" menu). The old menu had functionality that isn't present in the official one, most notably the "Caches" window that displayed the number of live JavaScript objects and made tracking down memory leaks much easier. If you'd like to bring back the old menu, you can use the new &lt;code&gt;IncludeInternalDebugMenu&lt;/code&gt; key shown above&lt;/p&gt;

&lt;pre&gt;defaults write com.apple.Safari TargetedClicksCreateTabs -bool true&lt;/pre&gt;

&lt;p&gt;First &lt;a href="http://tweetscan.com/index.php?s=TargetedClicksCreateTabs"&gt;spotted on Twitter&lt;/a&gt;, this forces new windows to open in tabs, one &lt;a href="http://support.mozilla.com/en-US/kb/Options+window#Tabs_span_style_text_align_left_float_none_clear_none_class_win_Options_span_span_style_text_align_left_float_none_clear_none_class_noWin_Preferences_span_"&gt;feature&lt;/a&gt; that I missed from my Firefox days.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-4967715591011246001?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/aDFzupUmHVA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/4967715591011246001/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2008/03/two-safari-31-tips.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4967715591011246001" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/4967715591011246001" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/aDFzupUmHVA/two-safari-31-tips.html" title="Two Safari 3.1 Tips" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://blog.persistent.info/2008/03/two-safari-31-tips.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-2804284005628138851</id><published>2008-02-18T21:50:00.000-05:00</published><updated>2008-02-18T21:52:53.583-05:00</updated><title type="text">Decommissioning Mscape Software</title><content type="html">&lt;p&gt;Before I became enamored with web development, I used to be a Mac software developer under the &lt;a href="http://www.mscape.com/"&gt;Mscape Software&lt;/a&gt; moniker. My first public release (clip2cicn, a helper tool for making the icon resources necessary for &lt;a href="http://kaleidoscope.net/"&gt;Kaleidoscope&lt;/a&gt; schemes) was almost 10 years ago, on June 26, 1998. My flagship product was Iconographer, an icon editing tool.&lt;/p&gt;

&lt;p&gt;I haven't had time for Mscape Software for about 4 years (the last Iconographer release was in July of 2003, and I last touched the code in early 2004). The site was still up, and I kept receiving registrations (Iconographer was a $15 shareware product). As time went on, I began to feel more and more guilty about not providing any support for (paying) customers. To this end, I have finally gotten around to dismantling Mscape Software, replacing the site with a &lt;a href="http://www.mscape.com/"&gt;placeholder&lt;/a&gt; with download links for all products. I've also put up a registration code for Iconographer so that the (annoying) registration reminder can be shut off.&lt;/p&gt;

&lt;p&gt;I've gotten some requests to open-source Iconographer, but I'm not sure I'll have time even for that. I'm not 100% sure I can build the product given software on hand (it was built with CodeWarrior). Even then, the codebase shows its age (it was my first large-ish project and still uses many Classic constructs like a &lt;code&gt;WaitNextEvent&lt;/code&gt; event loop).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-2804284005628138851?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/VqI-bYkkTzY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/2804284005628138851/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2008/02/decommissioning-mscape-software.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/2804284005628138851" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/2804284005628138851" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/VqI-bYkkTzY/decommissioning-mscape-software.html" title="Decommissioning Mscape Software" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.persistent.info/2008/02/decommissioning-mscape-software.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-8581270645226207790</id><published>2007-11-16T10:14:00.000-05:00</published><updated>2007-12-21T00:29:21.530-05:00</updated><title type="text">plusplusbot: Karma for anything and anyone</title><content type="html">&lt;p&gt;The recent release of &lt;a href="http://foamee.com"&gt;Foamee&lt;/a&gt; reminded me that I had never blogged here about &lt;a href="http://plusplusbot.com/"&gt;plusplusbot&lt;/a&gt;, a little toy of a site that I started to work on this past summer. The idea behind plusplusbot is that you can express your (dis)pleasure with something or someone over Twitter, and the site will keep track of the target's score over time. All of this is done over &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt;, in a &lt;a href="http://bokardo.com/archives/foamee-a-barnacle-app-for-indebted-drinkers/"&gt;barnacle-like fashion&lt;/a&gt;. The thinking being that Twitter handles message delivery over a variety of mediums (Jabber, SMS, Web) and provides a ready-made social network to piggyback on (you can view the activity of you and those you follow, for example, &lt;a href="http://plusplusbot.com/users/mihai#friends-header"&gt;here's mine&lt;/a&gt;). This concept should be familiar to those that have enountered "karmabots" in chatrooms.&lt;/p&gt;

&lt;p&gt;The targets can be anything. &lt;a href="http://plusplusbot.com/targets/apple"&gt;Companies&lt;/a&gt;, &lt;a href="http://plusplusbot.com/targets/coffee"&gt;food&lt;/a&gt;, &lt;a href="http://plusplusbot.com/targets/ak"&gt;people&lt;/a&gt;, even &lt;a href="http://plusplusbot.com/targets/twitter"&gt;Twitter itself&lt;/a&gt;. For whatever reason, the site has caught on most with Japanese Twitter users, so the homepage is often incomprehensible.&lt;/p&gt;

&lt;p&gt;Technically, the site is not too exciting. Like &lt;a href="http://persistent.info/twitter-digest"&gt;Twitter Digest&lt;/a&gt;, it also uses &lt;a href="http://code.google.com/p/python-twitter/"&gt;python-twitter&lt;/a&gt; to talk to Twitter and &lt;a href="http://davidbau.com/archives/2007/02/18/python_templates.html"&gt;templet&lt;/a&gt; for simple templates. The site itself is not dynamic at all, instead a script running on one of my machines polls Twitter every two minutes (since it has to fetch both friend updates and direct messages, fetching both every minute would go over Twitter's 70 requests/hour API rate limit). If it determines that a new plusplus or minusminus has been sent, it re-generates those pages (the user's and the target's) and uploads them. The simple design means that the site can be hosted nearly anywhere.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-8581270645226207790?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/nYIQcj69whM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/8581270645226207790/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/11/plusplusbot-karma-for-anything-and.html#comment-form" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/8581270645226207790" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/8581270645226207790" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/nYIQcj69whM/plusplusbot-karma-for-anything-and.html" title="plusplusbot: Karma for anything and anyone" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">8</thr:total><feedburner:origLink>http://blog.persistent.info/2007/11/plusplusbot-karma-for-anything-and.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-6510723071626999022</id><published>2007-11-05T21:09:00.001-05:00</published><updated>2008-07-19T13:10:36.413-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Greasemonkey" /><title type="text">Macros for the new version of Gmail</title><content type="html">&lt;p&gt;As those of you on &lt;a href="http://gmailblog.blogspot.com/2007/10/code-changes-to-prepare-gmail-for.html"&gt;the new version of Gmail&lt;/a&gt; might have noticed, nearly all Greasemonkey scripts that used to work on the old version no longer do. Even though it looks pretty similar, the new Gmail is entirely different from a JavaScript, HTML and CSS perspective, so this isn't surprising. Some of the scripts are no longer necessary. For example, &lt;a href="http://blog.persistent.info/2005/03/adding-persistent-searches-to-gmail.html"&gt;saved searches&lt;/a&gt; aren't really needed, since searches now get their own URLs and can be bookmarked.&lt;/p&gt;

&lt;p&gt;However, my &lt;a href="http://blog.persistent.info/2005/12/greasemonkey-christmas.html"&gt;Macros&lt;/a&gt; script is still needed; while the new version of Gmail does have additional keyboard shortcuts, it's still not possible to do everything from the keyboard. I've therefore ported it for the new version of Gmail, to install it, click below.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://gmail-greasemonkey.googlecode.com/svn/trunk/scripts/gmail-new-macros.user.js"&gt;Install Gmail Macros&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More specifically, the following keyboard shorcuts have been ported over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;g:&lt;/strong&gt; Go to label&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;l:&lt;/strong&gt; Apply label&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b:&lt;/strong&gt; Remove label&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;e:&lt;/strong&gt; Archive (regardless of view, unlike "y")&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;d:&lt;/strong&gt; Discard (mark as read and archive)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a new keyboard shortcut, which the old script didn't have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;f:&lt;/strong&gt; "Focus" the current view (only show unread, starred or inbox messages)&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;You may remember the script supporting other keyboard shorcuts. Since the new version of Gmail supports additional shortcuts, those have obsoleted. The new ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;shift + i&lt;/strong&gt;: Mark as read&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shift + u&lt;/strong&gt;: Mark as unread&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shift + 3&lt;/strong&gt;: Move to trash (not actually new, but not many people seem to know this one)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shift + 8&lt;/strong&gt; followed by &lt;strong&gt;a&lt;/strong&gt;, &lt;strong&gt;n&lt;/strong&gt;, &lt;strong&gt;r&lt;/strong&gt;, &lt;strong&gt;u&lt;/strong&gt;, &lt;strong&gt;s&lt;/strong&gt;, or &lt;strong&gt;t&lt;/strong&gt;: Select all, none, read, unread, starred, or unstarred&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those of you adventurous enough to look at the script source, you'll notice that it uses a &lt;code&gt;gmonkey&lt;/code&gt; object that is present on the window, which in turn gets you a &lt;code&gt;gmail&lt;/code&gt; object with methods like &lt;code&gt;getNavPaneElement()&lt;/code&gt; and &lt;code&gt;getActiveViewType()&lt;/code&gt;. What this means is that the version of Gmail, in addition to being faster, also has semi-official support for Greasemonkey scripts. I'm pretty sure docs for this API will be out soon, but in the meantime, feel free to look at the script and use a tool like Firebug to investigate the properties of the &lt;code&gt;gmonkey&lt;/code&gt; and &lt;code&gt;gmail&lt;/code&gt; objects and play around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update on 11/06/2007:&lt;/strong&gt; And here are &lt;a href="http://code.google.com/p/gmail-greasemonkey/wiki/GmailGreasemonkey10API"&gt;the semi-official docs&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-6510723071626999022?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/t_Fm4XI0N20" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/6510723071626999022/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/11/macros-for-new-version-of-gmail.html#comment-form" title="159 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/6510723071626999022" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/6510723071626999022" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/t_Fm4XI0N20/macros-for-new-version-of-gmail.html" title="Macros for the new version of Gmail" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">159</thr:total><feedburner:origLink>http://blog.persistent.info/2007/11/macros-for-new-version-of-gmail.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-3387954595038339056</id><published>2007-10-01T20:24:00.000-04:00</published><updated>2007-10-01T20:14:07.340-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hammers" /><title type="text">Feed Proxy: View Raw RSS/Atom Feed Data in Firefox 2.0</title><content type="html">&lt;p&gt;Firefox 2.0 has a nice RSS/Atom feed preview feature, which pretty-prints the feed and allows you easily subscribe to it in a feed reader. While this is nice from a user's standpoint, it can get in the way when trying to debug or otherwise see a raw feed as a developer. There is no option to turn this mode off, which seems to be making &lt;a href="http://www.evilgeniuschronicles.org/wordpress/2007/03/17/xml-view-in-mozilla/"&gt;some&lt;/a&gt; &lt;a href="http://groups.google.com/group/mozilla.dev.apps.firefox/browse_thread/thread/146f70eaf0e1686f/f35c316db3883cf8"&gt;people&lt;/a&gt; unhappy.&lt;/p&gt;

&lt;p&gt;To help with this use-case, I made &lt;a href="http://persistent.info/feed-proxy"&gt;Feed Proxy&lt;/a&gt;, a simple service that gives you back the raw XML output (by adding a 512 byte dummy header that makes Firefox not invoke its feed preview code). Its page has more details on how to invoke it and what its caveats are.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-3387954595038339056?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/LPIeabU8PSA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/3387954595038339056/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/10/feed-proxy-view-raw-rssatom-feed-data.html#comment-form" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/3387954595038339056" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/3387954595038339056" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/LPIeabU8PSA/feed-proxy-view-raw-rssatom-feed-data.html" title="Feed Proxy: View Raw RSS/Atom Feed Data in Firefox 2.0" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">12</thr:total><feedburner:origLink>http://blog.persistent.info/2007/10/feed-proxy-view-raw-rssatom-feed-data.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-8439220368791256078</id><published>2007-09-26T18:11:00.000-04:00</published><updated>2007-09-26T19:08:10.813-04:00</updated><title type="text">Valleywag, brought to you by....Google?</title><content type="html">&lt;p&gt;Gawker Media's use of virtual hosting for its sites was being discussed in &lt;a href="http://techwalla.googlepages.com/home"&gt;PartyChat&lt;/a&gt;, so to verify this, I ran &lt;code&gt;host valleywag.com&lt;/code&gt; to compare it with &lt;code&gt;host fleshbot.com&lt;/code&gt;. Imagine my surprise when I saw this output:&lt;p&gt;

&lt;pre&gt;$ host valleywag.com
valleywag.com has address 69.60.7.199
valleywag.com mail is handled by 1 aspmx.l.google.com.
valleywag.com mail is handled by 5 alt1.aspmx.l.google.com.
valleywag.com mail is handled by 5 alt2.aspmx.l.google.com.
valleywag.com mail is handled by 10 aspmx2.googlemail.com.&lt;/pre&gt;

&lt;p&gt;If I am interpreting this correctly, Valleywag's email is being handled by Gmail/Google Apps, which is slightly ironic.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-8439220368791256078?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/uMhMCBvNros" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/8439220368791256078/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/09/valleywag-brought-to-you-bygoogle.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/8439220368791256078" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/8439220368791256078" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/uMhMCBvNros/valleywag-brought-to-you-bygoogle.html" title="Valleywag, brought to you by....Google?" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://blog.persistent.info/2007/09/valleywag-brought-to-you-bygoogle.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-9181566390968601787</id><published>2007-08-31T10:30:00.000-04:00</published><updated>2007-09-03T12:46:50.144-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hammers" /><title type="text">Twitter Digest</title><content type="html">&lt;p&gt;There are quite a few people on &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt; that I'd like to follow but can't, since they update way too often. For example, I'd like to know how &lt;a href="http://twitter.com/chockenberry"&gt;Craig Hockenberry&lt;/a&gt;'s &lt;a href="http://code.google.com/p/mobile-twitterrific/"&gt;MobileTwitterrific&lt;/a&gt; work is going. &lt;a href="http://twitter.com/gruber"&gt;John Gruber&lt;/a&gt; often posts advance tidbits from Daring Fireball. However, since Twitter doesn't seem to allow different notification levels for contacts, I'd rather not get overwhelmed with their updates on a regular basis.&lt;/p&gt;

&lt;p&gt;Twitter does have an &lt;a href="http://www.twitter.com/help/api"&gt;API&lt;/a&gt; though, and a pretty complete one at that. Additionally, &lt;a href="http://code.google.com/p/python-twitter/"&gt;python-twitter&lt;/a&gt; makes it pretty easy to code against it. The result is that it was pretty easy to make &lt;a href="http://persistent.info/twitter-digest"&gt;&lt;strong&gt;Twitter Digest&lt;/strong&gt;&lt;/a&gt;. You can give it a bunch of usernames and it will update a page once a day with all of those people's tweets. Feeds can also be generated, so that you can subscribe to your digest in your favorite feed reader. To give an example, here's &lt;a href="http://persistent.info/twitter-digest/generate?usernames=gruber+alcor+brentsimmons+cabel+chockenberry+jkottke+rael+rands+siracusa+stevenf+diveintomark+nelson&amp;output=html"&gt;my digest&lt;/a&gt; (and &lt;a href="http://persistent.info/twitter-digest/generate?usernames=gruber+alcor+brentsimmons+cabel+chockenberry+jkottke+rael+rands+siracusa+stevenf+diveintomark+nelson&amp;output=atom"&gt;its feed&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Since python-twitter already has the the concept of caching API replies, this was especially easy to develop. Simply by juggling with cache timeout values, API replies are kept around for up to a day, thus I don't have to persist anything in my code. All digests are refreshed at GMT day boundaries because supporting timezones seemed like too much work.&lt;/p&gt;

&lt;p&gt;Digests can be thematic too. Here's &lt;a href="http://persistent.info/twitter-digest/generate?usernames=al3x+biz+blaine+bs+crystal+ev+goldman+jack+krissy&amp;output=html"&gt;one for the Twitter team&lt;/a&gt;. I'm sure there are other uses too. I'm pretty happy with how this turned out, and the only thing missing is better reply support (if a message is in reply to another, then (optionally) show that message inline too). Right now the API is missing support for this (even though the regular UI surfaces this information), but I'm sure something can be hacked together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update on 9/3/2007:&lt;/strong&gt; Adding support for "in reply to" messages turned out to be easy enough (look for the first message from the other username that's before the one that's replying, but only up to an hour earlier, in case it's not actually connected).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-9181566390968601787?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/krYo-ttLVlM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/9181566390968601787/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/08/twitter-digest.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/9181566390968601787" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/9181566390968601787" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/krYo-ttLVlM/twitter-digest.html" title="Twitter Digest" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.persistent.info/2007/08/twitter-digest.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-2545031396202703507</id><published>2007-07-22T15:29:00.001-04:00</published><updated>2007-07-22T16:20:22.693-04:00</updated><title type="text">iPhone Game Development: A Tale of Failure</title><content type="html">&lt;p&gt;Having acquired an iPhone, I've been playing around with it to see just how much can be done with the "SDK" that is the MobileSafari browser. I thought it would be fun to see if I could make a simple game for it, since that's different enough from the kind of UI coding I do at &lt;a href="http://www.google.com"&gt;work&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the games for the iPhone that I've encountered thus far were of the puzzle/card variety, and there wasn't anything too exciting/button-mashing/mindless. It seemed like this was mainly caused by the limitations of the events that the browser exposes. With no real &lt;code&gt;mousemove&lt;/code&gt; events, and with &lt;code&gt;mousedown&lt;/code&gt; events actually firing when the cursor was lifted (i.e. at the same time as &lt;code&gt;mouseup&lt;/code&gt;), it was hard to get anything to happen on the screen quickly in response to a user action. One avenue that I tried was to have the screen covered in narrow vertical &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;'s, and to listen for click events on each of them. Unfortunately, while this worked when each was clicked separately (piano key-style), it didn't work when the user dragged his finger from one to another, since this would switch the browser into panning mode, even if the page/viewport was set to be only 320 pixels wide (i.e. no scrolling could actually occur).&lt;/p&gt;

&lt;p&gt;Since the viewport panning does not seem to expose any events, I thought that maybe there could be other ways of catching this user action. When scrolling quickly, the iPhone renders newly-visible areas with a checkerboard pattern before actually displaying their contents. I wondered if this meant that images contained in these areas would not be loaded until they were scrolled into view and rendered. If so, it would be possible to use a &lt;code&gt;load&lt;/code&gt; event listener as a notification that the user had scrolled to a particular part of the screen. Unfortunately this didn't seem to be the case, images are loaded as soon as the document is displayed, even if they are off the screen.&lt;/p&gt;

&lt;p&gt;I then remembered that the iPhone supports an additional gesture, a two-fingered scroll that can be used to scroll text-areas, elements with &lt;code&gt;overflow: auto&lt;/code&gt; and possibly other nodes. It would appear that &lt;code&gt;scroll&lt;/code&gt; events trigger for this scrolling motion, meaning that it's possible to detect and get continuous updates for this. Though having to use two fingers seemed a bit clumsy, this seemed pretty promising insofar as it allowed for quick responses to user input.&lt;/p&gt;

&lt;p&gt;I had lofty goals for my iPhone game (I thought a vertical scrolling shooter was a good fit for the dragging input method and the vertical screen layout), but I decided that I should do something simple for my first attempt. I thought I would recreate aa racing game that I hard first played on my TI-82 calculator, where a car that can only be moved left or right moves at a constant vertical speed through a winding, narrowing road, with the objecting being to avoid hitting the sides. I thought this would map well to a horizontal scrolling motion that I could get with a &lt;code&gt;scroll&lt;/code&gt; event handler as mentioned above.&lt;/p&gt;

&lt;p&gt;After writing a simple random road generator and the animation code, I was about to add collision detection. Out of curiosity, I tried to move the "car" left/or right as road moved by, even without collision detection, to see if the speed was too high. To my surprise, the road stopped moving as soon as I put my two fingers on the screen. It appeared that intervals (set up with &lt;code&gt;window.setInterval&lt;/code&gt;) do not fire when two-finger scrolling mode is engaged. I thought that maybe I could refresh the animation from the &lt;code&gt;scroll&lt;/code&gt; event handler, but that only seems to fire when there is actual motion, thus smooth animation can't be ensured.&lt;/p&gt;

&lt;p&gt;My failed attempt is visible &lt;a href="http://persistent.info/files/iphone-racer.html"&gt;here&lt;/a&gt;. Before pressing the "Start" button, use the two-fingered scrolling motion left and right to confirm the the "car" (the blue square) moves as expected. Then press "Start" and obverse the reasonably smooth animation. Unfortunately it all stops when placing two fingers on the screen again.&lt;/p&gt;

&lt;p&gt;This is rather disappointing, since there doesn't seem to be any fundamental technical reason why this should be the case. We're not talking about Mac OS 9 here (there, due to the way UI events were processed, normal event loop execution would be suspended while a menu was held open or a scrollbar thumb was dragged). It's quite clear, given what native iPhone apps can do, that the platform has quite a lot of CPU and graphics leeway. It's therefore unfortunate just how constrained the MobileSafari browser is, since just a few additional hooks would allow much richer interactions with the user.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-2545031396202703507?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/p_4ZBQQcRFM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/2545031396202703507/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/07/iphone-game-development-tale-of-failure.html#comment-form" title="14 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/2545031396202703507" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/2545031396202703507" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/p_4ZBQQcRFM/iphone-game-development-tale-of-failure.html" title="iPhone Game Development: A Tale of Failure" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">14</thr:total><feedburner:origLink>http://blog.persistent.info/2007/07/iphone-game-development-tale-of-failure.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-1685135159612437284</id><published>2007-05-04T09:43:00.000-04:00</published><updated>2007-05-04T08:43:21.436-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Meta" /><title type="text">Bad Headlines</title><content type="html">&lt;p&gt;Headlines seen over the past few weeks on the start page that Verizon forces on its users, and what they refer to in parentheses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Daily Life is a Struggle" (life in Baghdad)&lt;/li&gt;
&lt;li&gt;"All Sides Deny Deals" (Iran/UK sailors hostage situation)&lt;/li&gt;
&lt;li&gt;"Panel Seeks Documents" (Alberto Gonzales' federal prosecutors firings)&lt;/li&gt;
&lt;li&gt;"Cheruiyot Wins Marathon" (Robert Kipkoech Cheruiyot won the Boston Marathon)&lt;/li&gt;
&lt;li&gt;"Gates: Clock is ticking" (Defense Secretary Robert Gates on the situation in Iraq)&lt;/li&gt;
&lt;li&gt;"Donkey Becomes Witness" (Dallas man is sued for noise disturbance caused by a donkey)&lt;/li&gt;
&lt;li&gt;"Dems Predict a Win" (House Democratic leaders predict that they can win a vote on a bill calling for a withdrawal from Iraq)&lt;/li&gt;
&lt;li&gt;"Candidates to Debate" (The Republican presidential candidate debate)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Someone forgot to read up on &lt;a href="http://www.useit.com/alertbox/980906.html"&gt;microcontent headlines&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-1685135159612437284?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/v6jcTnWyO0U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/1685135159612437284/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/05/bad-headlines.html#comment-form" title="9 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/1685135159612437284" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/1685135159612437284" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/v6jcTnWyO0U/bad-headlines.html" title="Bad Headlines" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">9</thr:total><feedburner:origLink>http://blog.persistent.info/2007/05/bad-headlines.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-1960370276299529429</id><published>2007-04-11T11:40:00.000-04:00</published><updated>2007-04-11T11:46:14.059-04:00</updated><title type="text">Tiled Windows Are the Way of the Future</title><content type="html">&lt;p&gt;Sorry Bill Atkinson. I know you &lt;a href="http://www.folklore.org/StoryView.py?project=Macintosh&amp;story=I_Still_Remember_Regions.txt"&gt;worked really hard&lt;/a&gt; to make regions and thus overlapping windows work. But since I now have 30 inch displays at both home and work, I've been tiling my windows and using &lt;a href="http://desktopmanager.berlios.de/"&gt;Desktop Manager&lt;/a&gt; (and soon Spaces?) to switch between workspaces. Not having to deal with z-ordering makes mental context switches faster.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-1960370276299529429?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/OG5VYnPXEeQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/1960370276299529429/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/04/tiled-windows-are-way-of-future.html#comment-form" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/1960370276299529429" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/1960370276299529429" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/OG5VYnPXEeQ/tiled-windows-are-way-of-future.html" title="Tiled Windows Are the Way of the Future" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://blog.persistent.info/2007/04/tiled-windows-are-way-of-future.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-6716318358896879544</id><published>2007-04-06T13:32:00.000-04:00</published><updated>2007-04-06T15:32:13.469-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Meta" /><title type="text">Communicating through screenshots</title><content type="html">&lt;p&gt;My posts to the &lt;a href="http://googlereader.blogspot.com/"&gt;Reader&lt;/a&gt; blog often have screenshots, and I'm pretty picky about what goes in them. It's nice to see that others have noticed this too. My &lt;a href="http://googlereader.blogspot.com/2007/04/go-go-reader-gadget.html"&gt;most recent post&lt;/a&gt; has a link to &lt;a href="http://www.qwantz.com/"&gt;Dinosaur Comics&lt;/a&gt;, which the author &lt;a href="http://qwantz.livejournal.com/78807.html"&gt;seems to appreciate&lt;/a&gt;. Similarly, I intentionally left a reference to Valleywag in my &lt;a href="http://googlereader.blogspot.com/2007/01/i-like-big-charts-and-i-cannot-lie.html"&gt;trends post&lt;/a&gt;, which &lt;a href="http://valleywag.com/tech/self_congratulation/google-geek-exposed-as-valleywag-reader-226019.php"&gt;they noticed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In some fashion, Apple does this too. A &lt;a href="http://www.appleinsider.com/article.php?id=2637"&gt;patent&lt;/a&gt; of theirs had a BBEdit reference, which &lt;a href="http://www.glorifiedtypist.com/2007/04/in_soviet_russia_apple_patents.html"&gt;amused the author of the software&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-6716318358896879544?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/_4_TlrU0-3I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/6716318358896879544/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/04/communicating-through-screenshots.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/6716318358896879544" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/6716318358896879544" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/_4_TlrU0-3I/communicating-through-screenshots.html" title="Communicating through screenshots" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.persistent.info/2007/04/communicating-through-screenshots.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-8192515560137189845</id><published>2007-03-25T20:32:00.001-04:00</published><updated>2009-08-22T10:07:08.939-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hammers" /><title type="text">Two Web Development Tips</title><content type="html">&lt;h3&gt;Memory Leaks&lt;/h3&gt;

&lt;p&gt;Fighting memory leaks in fancy AJAXy apps is a way of life for most UI developers. Besides &lt;a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp"&gt;Internet Explorer-specific&lt;/a&gt; issues, leaks can also occur due to inadvertent remaining references to objects you don't actually need (frequent culprits are global event registries and history/state management stacks). Safari/WebKit turn out to have some useful built-in hooks for tracking these down. By enabling Safari's &lt;a href="http://developer.apple.com/internet/safari/faq.html#anchor14"&gt;debug menu&lt;/a&gt; and choosing the "Show Caches Window" command from there, you can see JavaScript object counts, force a garbage collection and see the types of objects that still have references pointing to them (note that garbage collection has been improved in &lt;a href="http://www.webkit.org/"&gt;WebKit&lt;/a&gt; nightly builds so you'll probably want to use that).&lt;/p&gt;

&lt;p&gt;&lt;a href="http://outofhanwell.com/ieleak/index.php?title=Main_Page"&gt;Drip&lt;/a&gt; can provide some similar information for IE, and &lt;a href="http://dbaron.org/mozilla/leak-monitor/"&gt;Leak Monitor&lt;/a&gt; finds some Firefox-specific leak types, but it would be good for browser/add-on creators to provide even more debugging information to authors.&lt;/p&gt;

&lt;h3&gt;Browser Bugs&lt;/h3&gt;

&lt;p&gt;WebKit and Firefox/Mozilla both have publicly visible bug tracking systems. As they work on their respective new versions, regressions will occur that will break web sites, possibly including yours. Often, this is a genuine bug in the browser code, and hopefully it'll be fixed before the final release. However, it can sometimes be due to incorrect assumptions made by your code which no longer hold true  when browser code is tightened up. Both &lt;a href="http://bugs.webkit.org/query.cgi?format=specific"&gt;WebKit's&lt;/a&gt; and &lt;a href="https://bugzilla.mozilla.org/query.cgi"&gt;Mozilla's&lt;/a&gt; Bugzilla instances support generating RSS feeds from search results. By subscribing to feeds for bugs that mention your product name, you can stay on top of such bug reports. For example, here's &lt;a href="http://www.google.com/reader/shared/user/14548369432350969777/label/browser-bugs"&gt;a shared page&lt;/a&gt; (with &lt;a href="http://www.google.com/reader/public/atom/user/14548369432350969777/label/browser-bugs"&gt;feed&lt;/a&gt;) for the bugs that mention Reader on either site.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-8192515560137189845?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/wi6WzdwXh9E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/8192515560137189845/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/03/two-web-development-tips.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/8192515560137189845" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/8192515560137189845" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/wi6WzdwXh9E/two-web-development-tips.html" title="Two Web Development Tips" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.persistent.info/2007/03/two-web-development-tips.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-645543767147579896</id><published>2007-03-16T11:01:00.000-04:00</published><updated>2007-03-25T21:16:41.676-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Meta" /><title type="text">Determining Twitter's Growth</title><content type="html">&lt;p&gt;Opinion on &lt;a href="http://www.twitter.com/"&gt;Twitter&lt;/a&gt; &lt;a href="http://web1979.wordpress.com/2007/03/14/rip-twitter-2007-2007/"&gt;is&lt;/a&gt; &lt;a href="http://www.feedblog.org/2007/03/rip_twitter.html"&gt;divided&lt;/a&gt;. What seems to be undisputed is that right now it's growing very quickly. I was curious just how "quickly" quickly was, preferably going beyond just anecdotal "my network doubled in size in the last 5 seconds" kind of observations. It seems like Twitter assigns globally unique, incrementing IDs to all messages it receives. By looking at the values of these IDs over time, it's possible to see how many status messages Twitter is keeping track of. I've generated a logarithmic graph of this.&lt;/p&gt;

&lt;p&gt;I'm not sure why there was an inflection point in early November. It's also possible that this is affected by technical changes on Twitter's side. Still possibly interesting. Also, Joshua's post on &lt;a href="http://joshua.schachter.org/2007/01/autoincrement.html"&gt;autoincrement considered harmful&lt;/a&gt; is related and an interesting read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; As it was pointed out to me in the comments, Andy Baio had &lt;a href="http://www.waxy.org/archive/2007/03/15/tracking.shtml"&gt;the same idea&lt;/a&gt; except he executed it more throughly.&lt;/p&gt;

&lt;p style="text-align: center"&gt;&lt;img src="http://persistent.info/images/twitter-message-ids.png" width="592" height="389" alt="Twitter Message IDs"/&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-645543767147579896?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/OpJ3ohgfUcM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/645543767147579896/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/03/determining-twitters-growth.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/645543767147579896" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/645543767147579896" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/OpJ3ohgfUcM/determining-twitters-growth.html" title="Determining Twitter's Growth" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.persistent.info/2007/03/determining-twitters-growth.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-745789294912098473</id><published>2007-03-09T15:22:00.000-05:00</published><updated>2007-03-09T15:22:44.800-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Meta" /><title type="text">Intern on the Google Reader team</title><content type="html">&lt;p&gt;The &lt;a href="http://www.google.com/reader"&gt;Reader&lt;/a&gt; team is hoping to have a student intern or two this coming summer. We're fast moving and always have more ideas than manpower, so an internship can be quite rewarding as far as the "working on real stuff" factor goes. For example, our intern last year, Brad, worked on the subscribe and feed search functionalities of the new Reader that launched last September. You can intern in Google's New York or Mountain View offices, working on either Reader's frontend/UI or backend.&lt;/p&gt;

&lt;p&gt;If you or anyone you know is interested in this internship, contact me at &lt;code&gt;mihai at persistent dot info&lt;/code&gt;. &lt;a href="http://www.google.com/support/jobs/bin/static.py?page=students.html&amp;sid=intern"&gt;This page&lt;/a&gt; also has more general information about interning at Google.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-745789294912098473?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/rlTbkNs6e84" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/745789294912098473/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/03/intern-on-google-reader-team.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/745789294912098473" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/745789294912098473" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/rlTbkNs6e84/intern-on-google-reader-team.html" title="Intern on the Google Reader team" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.persistent.info/2007/03/intern-on-google-reader-team.html</feedburner:origLink></entry><entry><id>tag:blogger.com,1999:blog-6525469191850690957.post-5118205039792752332</id><published>2007-02-25T16:55:00.000-05:00</published><updated>2007-02-25T16:57:08.464-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Meta" /><title type="text">Blogger Migration Part II: Getting Data Into Blogger</title><content type="html">&lt;p&gt;Blogger doesn't have any built-in entry importing facilities. My plan for dealing with this was to use their &lt;a href="http://code.google.com/apis/blogger/overview.html"&gt;API&lt;/a&gt; to re-post all of the entries I had &lt;a href="http://blog.persistent.info/2007/02/blogger-migration-part-i-getting-data.html"&gt;exported&lt;/a&gt; from Movable Type. A quick test showed that such entries could be back-dated, which was my main concern.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="http://vim-postblog.googlecode.com/svn/trunk/bloggerpost.py"&gt;some sample code&lt;/a&gt; that I found as a starting point, it was pretty easy to write a simple importing script for entries. Since I needed to parse the Atom responses from the API (e.g. to get at entry IDs), the &lt;a href="http://www.feedparser.org/"&gt;Universal Feed Parser&lt;/a&gt; came in handy. Since I had used the &lt;a href="http://www.sixapart.com/movabletype/docs/3.2/04_posting_entries/new_entry.html"&gt;Convert Line Breaks&lt;/a&gt; option on quite a few posts, I had to HTML-ify some post bodies before sending them to Blogger (I've turned off Blogger's similar setting. In addition to being only at the blog level, instead of a per-post setting, I've decided that the closer to regular HTML my posts are, the easier it will be to migrate again in the future).&lt;/p&gt;

&lt;p&gt;The Blogger API allows for posting of comments too, but unfortunately without the ability to &lt;a href="http://thread.gmane.org/gmane.comp.web.blogger.api/2892"&gt;impersonate others&lt;/a&gt; (even when anonymous comments are enabled). The solution was to impersonate the regular (non-API) comment posting form, which does allow for authors to be specified (but no backdating, which is why all imported comments are dated February 10th). This was made slightly trickier since a security token is required (to avoid &lt;a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery"&gt;XSRF&lt;/a&gt; attacks), so the posting page had to be scraped first to extract it.&lt;/p&gt;

&lt;p&gt;Finally, since Blogger proved to be occasionally flaky when doing many API operations in a row, I had to add some simple checkpointing so that if the process failed I could restart it and it would continue from where it left off. Once I did all that, importing 350+ entries and 600+ comments took around an hour, but worked flawlessly.&lt;/p&gt;

&lt;p&gt;I've uploaded &lt;a href="http://persistent.info/files/blogger-upload.zip"&gt;an archive of my code&lt;/a&gt; that I used for the importing. It's not the cleanest it could be, but others may find it useful too. Additionally, in the time that has passed since I began my importing, it looks like &lt;a href="http://groups.google.com/group/bloggerDev/browse_frm/thread/d1c7855675023228/caad4b1ee97b0eb5#caad4b1ee97b0eb5"&gt;a similar script&lt;/a&gt; has appeared that does a similar import operation from WordPress, which may be worth a look too.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6525469191850690957-5118205039792752332?l=blog.persistent.info'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/PersistentInfo/~4/73qn05o7o4w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.persistent.info/feeds/5118205039792752332/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.persistent.info/2007/02/blogger-migration-part-ii-getting-data.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/5118205039792752332" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6525469191850690957/posts/default/5118205039792752332" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/PersistentInfo/~3/73qn05o7o4w/blogger-migration-part-ii-getting-data.html" title="Blogger Migration Part II: Getting Data Into Blogger" /><author><name>Mihai Parparita</name><uri>http://www.blogger.com/profile/12343650264888591427</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd="http://schemas.google.com/g/2005" name="OpenSocialUserId" value="14473909087239144495" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.persistent.info/2007/02/blogger-migration-part-ii-getting-data.html</feedburner:origLink></entry></feed>
