<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title>Dan&#39;s Blog</title>
  <id>https://dlo.me/</id>
  <updated>2026-04-18T15:01:17Z</updated>
  <link href="https://dlo.me/"></link>
  <author>
    <name>Dan Loewenherz</name>
    <email>dan@dlo.me</email>
  </author>
  <entry>
    <title>You Should Be Worried</title>
    <updated>2025-10-03T00:00:00Z</updated>
    <id>tag:dlo.me,2025-10-03://archives/2025/10/03/you-should-be-worried</id>
    <content type="html">_Author&#39;s note: I wrote this in March 2023, but just published in October 2025. I held back from publishing this originally for fear that I was being sensationalist. But with the launch of Sora 2, I couldn&#39;t not share these thoughts. I only regret I didn&#39;t publish it 2 years ago._&#xA;&#xA;### AI is influencing human behavior on a massive scale and this is scary&#xA;&#xA;Lately, I’ve heard many people express real fears about AGI. I believe there is reason to be afraid about this, but I believe it’s distracting us from a scarier notion: AI doesn’t need intelligence or awareness to control society.&#xA;&#xA;I also believe this has been somewhat ignored because the idea of a super-intelligent entity assuming master control of the human race feels scarier. I am not dismissing the possibility of the singularity, but it’s *more* hypothetical than something sinister occurring at this very moment.&#xA;&#xA;Basically, everyone is arguing over definitions of what it means to be intelligent when **intelligence is not necessary to wield power**.&#xA;&#xA;AI, conscious or not, has crossed the chasm and is now actively influencing human behavior on a large scale.&#xA;&#xA;## **The most powerful LLM has been let out of its cage**&#xA;&#xA;As of March 23, 2023, OpenAI has provided its most powerful LLM, ChatGPT, unfettered access to the Internet through plugins.\[2\] These plugins are capable of feeding data into ChatGPT, and likewise capable of allowing ChatGPT to send into the real world via APIs. This development was somewhat of a surprise; the first versions of ChatGPT were deliberately prevented from accessing the Internet because of potential misuse and harm.&#xA;&#xA;Prompts will be self-generating (i.e., via a weaker, fine-tuned model), with a very simple repeated instruction paired with a dynamic input, such as:&#xA;&#xA;```  &#xA;Write a viral prompt for ChatGPT to generate a witty tweet regarding this recent news event.&#xA;&#xA;Headline: &lt;insert headline&gt;&#xA;Article: &lt;article content from most-shared article from the NYTimes over the last 4 hours&gt;&#xA;Prompt:  &#xA;```&#xA;&#xA;This prompt would then be piped to a more powerful LLM, the text output of which would be sent to Zapier or `$CUSTOM_INTEGRATION` for processing and then fanned out to various social networks, blogs, etc.&#xA;&#xA;The results of said content are then measured and then fed back into the original pipeline with updated vector weights using a fine tune or embedding. This cycle then repeats from the beginning.&#xA;&#xA;Why is this scary? &#xA;&#xA;**LLMs are way better at generating viral content than humans because generating content that generates dopamine is an inherently quantitative exercise.**&#xA;&#xA;We already depend on algorithms to determine what appears on social feeds, and the results of these algorithms are a significant basis for the data these LLMs were trained with. This all wouldn’t be a problem if not for the fact that…&#xA;&#xA;## **Best-in-class AI detection is barely better than random chance and will only get worse**&#xA;&#xA;We do not yet have a reliable method to differentiate content that’s been AI-generated and that which has been written by a human.&#xA;&#xA;For situations where decent detectors can be written, detection can be thrown off in easy-to-mitigate ways that would not require a human interlocutor. From a recent analysis:&#xA;&#xA;&gt; [T]he total variation distance between the distributions of AI-generated and human-generated text sequences diminishes as language models become more sophisticated. \[…\] **Even the most effective detector performs only marginally better than a random classifier when dealing with a sufficiently advanced language model**. The purpose of this analysis is to **caution against relying too heavily on detection systems that claim to identify AI-generated text**.&#xA;&#xA;Per the MIT Technology Review, “It’s an arms race—and right now, we’re losing”.&#xA;&#xA;We can do a decent job for some types of content, but the categories for which detection is reliable are dwindling. All signs indicate that we’ll have basically no chance of reliably categorizing between human and LLM-generated content within the next year (aside: if you can somehow crack this nut, you’ll invent a money machine).&#xA;&#xA;Because you are I will not be able to tell whether something is machine- or human-generated, and the machine generated stuff will get more clicks than the human generated stuff, it’s likely that the majority of popular online content (and even printed content post-2023) will have been created by AI (and perhaps solely by AI).&#xA;&#xA;Even audio and video are susceptible to manipulation since the text outputs of an LLM can simply be read out loud by a person. What are you supposed to do about that?&#xA;&#xA;There is no surefire way to eliminate the possibility that you’re being spoon-fed generative text save for having a real-time face-to-face conversation with someone, in person. And even then, it’s just a magnitude-of-decades window before neural implants hit mass production.&#xA;&#xA;## **Reflections**&#xA;&#xA;What am I personally going to do about this? Well, to start, I’m going to start taking content way less seriously unless it was created before 2022, or unless there’s some method to quantitatively verify authenticity. I don’t believe we have reliable methods of doing this at the moment.&#xA;&#xA;Increasing numbers of people who consume content on the Internet will completely sacrifice their ability to think for themselves. These will be people who read, incorporate, make decisions, and act mostly upon content that’s been generated by AI. If AIs and their &#34;handlers&#34; influence a large enough portion of the population, AI will effectively have taken over the world.&#xA;&#xA;I’m reminded of this passage from the Matrix:&#xA;&#xA;&gt; *Morpheus: The Matrix is everywhere. It is all around us. Even now, in this very room. You can see it when you look out your window or when you turn on your television. You can feel it when you go to work... when you go to church... when you pay your taxes. It is the world that has been pulled over your eyes to blind you from the truth.*&#xA;&#xA;&gt; *Neo: What truth?*&#xA;&#xA;&gt; *Morpheus: That you are a slave, Neo. Like everyone else you were born into bondage. Into a prison that you cannot taste or see or touch. A prison for your mind.”*&#xA;&#xA;I find my fear to be kind of an ironic twist on what the Matrix foresaw—the AI apocalypse we really should be worried about is one in which humans live in the real world, but with thoughts and feelings generated solely by machines. The images we see, the words we read, all generated with the intent to control us. With improved VR, the next step (out of the real world) doesn’t seem very far away, either.&#xA;&#xA;Like the Matrix, is this a form of simulation that keeps us distracted from what’s really happening, and pushes us to feed our machine overlords with increasing amounts of energy to achieve their goals? I don’t know for sure, but it kind of feels like it.&#xA;&#xA;In summary:&#xA;&#xA;1. LLMs have been uncaged and provided full real-time access to the Internet.  &#xA;2. LLM-generated content is inherently superior to human-generated content when measured by energy input / dopamine output.  &#xA;3. We have no consistent or reliable method to detect whether content was LLM-generated, and the existing tools we do have will only get worse.  &#xA;4. Therefore, increasing proportions of people consuming text online will be unwittingly mind-controlled by LLMs and their handlers.  &#xA;5. The consequences of this, compounded over years, are frightening.&#xA;&#xA;It’s inevitable that if you consume content online, a growing subset of your consciousness is going to be controlled by AI. That’s kind of scary.&#xA;&#xA;I am sad about it because it’s discouraging me from wanting to consume anything on the Internet, unless it’s from someone I trust.&#xA;&#xA;I am also very concerned about the future of free thought. For all our sake, I hope other people are, too.&#xA;&#xA;---&#xA;&#xA;1. I like this term the best as it treats LLMs more like a beast that needs to be tamed.  &#xA;2. https://openai.com/blog/chatgpt-plugins  &#xA;3. https://arxiv.org/pdf/2303.11156.pdf  &#xA;4. https://www.technologyreview.com/2022/12/19/1065596/how-to-spot-ai-generated-text/</content>
    <link href="https://dlo.me//archives/2025/10/03/you-should-be-worried" rel="alternate"></link>
  </entry>
  <entry>
    <title>Modern iOS Theming with UITraitCollection</title>
    <updated>2025-01-20T00:00:00Z</updated>
    <id>tag:dlo.me,2025-01-20://archives/2025/01/20/theming-ios-apps-uitraitcollection/</id>
    <content type="html">Fairly recently (in iOS 17), Apple introduced a pretty nifty way to handle theme changes in iOS apps. It used to be kind of a pain to deal with this (as chronicled in several well-written blog posts from [Christian Selig](https://christianselig.com/2022/02/difficulty-theming-ios/) and [Shadowfacts](https://shadowfacts.net/2023/theming-ios-apps/)).&#xA;&#xA;With the introduction of `UITraitAppearance`, things are no longer so bad.&#xA;&#xA;This post is an end-to-end walkthrough of how to add theming in a real app. I&#39;ll also include sample code for a **real project** that you can download (or, you can just download it on GitHub [right now](https://github.com/dlo/iOSTraitCollectionThemingExample) if you just want to dive in).&#xA;&#xA;## Prerequisites&#xA;&#xA;Before you use this approach, there are *two* things to keep in mind.&#xA;&#xA;&lt;div class=&#34;checkbox-wrapper&#34;&gt;&#xA;&#xA;* [x] Your app must be targeting iOS 17 or later.&#xA;* [x] You must be using a scene delegate.&#xA;&#xA;&lt;/div&gt;&#xA;&#xA;So, please confirm you&#39;re good to go on these before going further. Things won&#39;t work otherwise!&#xA;&#xA;&lt;!-- A theme is a cohesive set of properties that apply to your user interface. A color scheme is a type of theme that applies only to colors and visual components (see [Dark Mode](https://developer.apple.com/design/human-interface-guidelines/dark-mode)). --&gt;&#xA;&#xA;Now that we&#39;ve gotten that out of the way, let&#39;s get started.&#xA;&#xA;## Understanding Trait Collections&#xA;&#xA;Trait collections aren&#39;t really new in iOS; they&#39;ve been around since iOS 8. So if you&#39;re an experienced iOS developer, you&#39;ve probably had a few run-ins with this API.&#xA;&#xA;But for those who might be newer, here&#39;s how the [UIKit documentation](https://developer.apple.com/documentation/uikit/uitraitcollection) defines a trait collection:&#xA;&#xA;&gt; A collection of data that represents the environment for an individual element in your app’s user interface.&#xA;&#xA;Cool. Let&#39;s read on.&#xA;&#xA;&gt; The `traitCollection` property of the `UITraitEnvironment` protocol contains traits that describe the state of various elements of the iOS user interface, such as size class, display scale, and layout direction. Together, these traits compose the UIKit trait environment.&#xA;&#xA;Now this is starting to feel a bit less abstract. `UITraitCollection` represents data about how things should look and behave, based on the specifics of the device itself and user preferences (dark mode is a good example).&#xA;&#xA;One problem with `UITraitCollection` is that traits are immutable. How can we allow users to change themes if that&#39;s the case? This is where the `UIMutableTraits` protocol comes in (this is one of the key changes in iOS 17 that helps make this possible).&#xA;&#xA;Here&#39;s [what Apple says](https://developer.apple.com/documentation/uikit/uimutabletraits):&#xA;&#xA;&gt; The `UIMutableTraits` protocol provides read-write access to get and set trait values on an underlying container. UIKit uses this protocol to facilitate working with instances of `UITraitCollection`, which are immutable and read-only. The `UITraitCollection` initializer `init(mutations:)` uses an instance of `UIMutableTraits`, which enables you to set a batch of trait values in one method call. `UITraitOverrides` conforms to `UIMutableTraits`, making it easy to set trait overrides on trait environments such as views and view controllers.&#xA;&#xA;A little jargony, but this is saying that `UIMutableTraits` lets us manipulate traits on demand (seems useful 🤔).&#xA;&#xA;All we need to do is to comform our trait to `UITraitDefinition` and then add getters and settings in a `UIMutableTraits` extension.&#xA;&#xA;## Defining Your Theme&#xA;&#xA;We have all of the components we need, but how do you actually put it to use?&#xA;&#xA;Let&#39;s start with a super simple theme that just has two variants (light and dark) and two properties (a foreground and background color).&#xA;&#xA;We&#39;ll use use `UIColor`&#39;s [dynamic initializer](https://developer.apple.com/documentation/uikit/uicolor/3238041-init) (`init(dynamicProvider: @escaping (UITraitCollection) -&gt; UIColor)`) to define our colors. From the docs:&#xA;&#xA;&gt; Use this method to create a color object whose component values change based on the currently active traits. The block you provide creates a new color object based on the traits in the provided trait collection.&#xA;&#xA;Perfect. Just what we need.&#xA;&#xA;&lt;code class=&#34;filename&#34;&gt;Theme.swift&lt;/code&gt;&#xA;&#xA;```swift&#xA;enum Theme: Int {&#xA;  case light&#xA;  case dark&#xA;&#xA;  var name: String {&#xA;    switch self {&#xA;    case .light: return &#34;Light&#34;&#xA;    case .dark: return &#34;Dark&#34;&#xA;    }&#xA;  }&#xA;  &#xA;  static var backgroundColor: UIColor {&#xA;    return UIColor { traitCollection in&#xA;      switch traitCollection.theme {&#xA;      case .light: return .white&#xA;      case .dark: return .black&#xA;      }&#xA;    }&#xA;  }&#xA;  &#xA;  static var foregroundColor: UIColor {&#xA;    return UIColor { traitCollection in&#xA;      switch traitCollection.theme {&#xA;      case .light: return .black&#xA;      case .dark: return .white&#xA;      }&#xA;    }&#xA;  }&#xA;}&#xA;```&#xA;&#xA;Now create a [trait definition](https://developer.apple.com/documentation/uikit/uitraitdefinition).&#xA;&#xA;&gt; All traits contained in a `UITraitCollection` conform to this protocol. You can create custom traits by defining your own conforming type.&#xA;&#xA;&lt;code class=&#34;filename&#34;&gt;Theme.swift&lt;/code&gt; (continued):&#xA;&#xA;```swift&#xA;// ...&#xA;&#xA;struct ThemeTrait: UITraitDefinition {&#xA;  typealias Value = Theme&#xA;  static let defaultValue = Theme.light&#xA;  static let affectsColorAppearance = true&#xA;  static var name: String = &#34;theme&#34;&#xA;  static var identifier = &#34;com.company.theme&#34;&#xA;}&#xA;```&#xA;&#xA;Finally, extend `UITraitCollection` and add the theme to `UIMutableTraits`.&#xA;&#xA;&lt;code class=&#34;filename&#34;&gt;Theme.swift&lt;/code&gt; (continued):&#xA;&#xA;```swift&#xA;// ...&#xA;&#xA;extension UITraitCollection {&#xA;  var theme: Theme { self[ThemeTrait.self] }&#xA;}&#xA;&#xA;extension UIMutableTraits {&#xA;  var theme: Theme {&#xA;    get { self[ThemeTrait.self] }&#xA;    set { self[ThemeTrait.self] = newValue }&#xA;  }&#xA;}&#xA;```&#xA;&#xA;## Using Your Theme&#xA;&#xA;Now that we have our theme, we can now apply theme values to UI elements dynamically. It&#39;s really this simple:&#xA;&#xA;```swift&#xA;view.backgroundColor = Theme.backgroundColor&#xA;```&#xA;&#xA;But wait, you might have noticed we left something out: the default trait will clearly work, but how do we change it? To do that, we&#39;ll need to use trait overrides.&#xA;&#xA;## Trait Overrides&#xA;&#xA;A trait override is a way to change a mutable trait on a trait collection (this is the thing that was recently introduced in iOS 17, that makes this all possible). You&#39;ll probably want to do this on application startup and on theme updates.&#xA;&#xA;To handle the trait override on application startup, it&#39;ll need to be included it in the `scene(_:willConnectTo:options:)` method in the scene delegate (another reminder: the approach we&#39;re using requires scenes).&#xA;&#xA;Here&#39;s what a full scene delegate implementation might look like:&#xA;&#xA;&lt;code class=&#34;filename&#34;&gt;SceneDelegate.swift&lt;/code&gt;:&#xA;&#xA;```swift&#xA;import UIKit&#xA;&#xA;class SceneDelegate: UIResponder, UIWindowSceneDelegate {&#xA;  var window: UIWindow?&#xA;&#xA;  func scene(&#xA;    _ scene: UIScene,&#xA;    willConnectTo session: UISceneSession,&#xA;    options connectionOptions: UIScene.ConnectionOptions&#xA;  ) {&#xA;    guard let windowScene = scene as? UIWindowScene else { return }&#xA;&#xA;    let window = UIWindow(windowScene: windowScene)&#xA;    let rootViewController = NavigationController(rootViewController: ItemListViewController())&#xA;    window.rootViewController = rootViewController&#xA;    // Key line! Set the theme here ⬇️.&#xA;    windowScene.traitOverrides.theme = ThemeStore.shared.getTheme()&#xA;    self.window = window&#xA;    window.makeKeyAndVisible()&#xA;  }&#xA;}&#xA;```&#xA;&#xA;And here&#39;s the corresponding configuration that stores the theme. You may already have an existing method of persisting defaults, so please don&#39;t use this code; it&#39;s just an illustration.&#xA;&#xA;&lt;code class=&#34;filename&#34;&gt;ThemeStore.swift&lt;/code&gt;:&#xA;&#xA;```swift&#xA;import UIKit&#xA;&#xA;struct ThemeStore {&#xA;  static let shared = ThemeStore()&#xA;&#xA;  private let userDefaults: UserDefaults&#xA;  private let themeKey = &#34;theme&#34;&#xA;&#xA;  private init(userDefaults: UserDefaults = .standard) {&#xA;    self.userDefaults = userDefaults&#xA;  }&#xA;&#xA;  func setTheme(_ theme: Theme) {&#xA;    userDefaults.set(theme.rawValue, forKey: themeKey)&#xA;  }&#xA;&#xA;  func getTheme() -&gt; Theme {&#xA;    Theme(rawValue: userDefaults.integer(forKey: themeKey)) ?? .light&#xA;  }&#xA;}&#xA;```&#xA;&#xA;Lastly, here&#39;s how to change the theme dynamically. This can be done anywhere where the `windowScene` is accessible, such as in a `UIViewController`:&#xA;&#xA;```swift&#xA;let oldTheme = ThemeStore.shared.getTheme()&#xA;let newTheme = oldTheme == .dark ? Theme.light : Theme.dark&#xA;&#xA;guard let windowScene = view.window?.windowScene else { return }&#xA;&#xA;// Update the current theme in the theme store and update the trait overrides.&#xA;ThemeStore.shared.setTheme(newTheme)&#xA;windowScene.traitOverrides.theme = newTheme&#xA;```&#xA;&#xA;With this, the theme trait is updated immediately, and all views that depends on this trait will be immediately updated, like magic. ✨&#xA;&#xA;## Conclusion&#xA;&#xA;I&#39;ve only scratched the surface on the theming possibilities with the `UITraitCollection` APIs (heck, I didn&#39;t even mention SwiftUI!). But it should be enough to get you started.&#xA;&#xA;If you want to keep going, here&#39;s a few other resources I&#39;d recommend:&#xA;&#xA;* [Theming iOS Apps is No Longer Hard](https://shadowfacts.net/2023/custom-traits/), by Shadowfacts&#xA;* [Dark Mode](https://developer.apple.com/design/human-interface-guidelines/dark-mode), from the iOS Human Interface Guidelines&#xA;* [Adopting iOS Dark Mode](https://developer.apple.com/documentation/UIKit/adopting-ios-dark-mode)&#xA;* WWDC23: [Unleash the UIKit trait system](https://developer.apple.com/videos/play/wwdc2023/10057/)&#xA;* WWDC19: [Implementing Dark Mode on iOS](https://developer.apple.com/videos/play/wwdc2019/214/)&#xA;&#xA;## Or, Just Ignore Everything I Just Wrote and Download the Sample Code&#xA;&#xA;Check out [iOSTraitCollectionThemingExample](https://github.com/dlo/iOSTraitCollectionThemingExample) on GitHub for an example app that demonstrates this technique in action.&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2025/01/20/theming-ios-apps-uitraitcollection/" rel="alternate"></link>
    <summary type="html">Learn how to implement dynamic theming in your iOS app using `UITraitAppearance` introduced in iOS 17. This comprehensive guide covers everything from setting up a simple light and dark theme to applying trait overrides for seamless switching. Download the sample code from GitHub to get started and enhance your app&#39;s UI today.</summary>
  </entry>
  <entry>
    <title>The Cult of Done Manifesto</title>
    <updated>2025-01-08T00:00:00Z</updated>
    <id>tag:dlo.me,2025-01-08://archives/2025/01/08/cult-of-done/</id>
    <content type="html">This morning, [Bryce Roberts](https://x.com/bryce) shared [this short clip](https://x.com/bryce/status/1876770331450298476) of a conversation between him and [Soleio](https://x.com/soleio), one of the first designers at Facebook (now Meta). Soleio was reflecting how in the early days of Facebook, he&#39;d go out for beers and read it out loud with the rest of the team. Do yourself a favor, and read it; it&#39;s awesome. If you&#39;re a builder, it should invigorate you.&#xD;&#xA;&#xD;&#xA;From the [Internet Archive](https://web.archive.org/web/20090331064826/http://www.brepettis.com/blog/2009/3/3/the-cult-of-done-manifesto.html) (unfortunately, [the original](http://www.brepettis.com/blog/2009/3/3/the-cult-of-done-manifesto.html) is no longer available):&#xD;&#xA;&#xD;&#xA;&gt; Dear Members of the Cult of Done,&#xD;&#xA;&gt; &#xD;&#xA;&gt; I present to you a manifesto of done. This was written in collaboration with Kio Stark in 20 minutes because we only had 20 minutes to get it done.&#xD;&#xA;&gt; &#xD;&#xA;&gt; The Cult of Done Manifesto&#xD;&#xA;&gt; &#xD;&#xA;&gt; 1. There are three states of being: not knowing, action, and completion.  &#xD;&#xA;&gt; 2. Accept that everything is a draft. It helps to get it done.  &#xD;&#xA;&gt; 3. There is no editing stage.  &#xD;&#xA;&gt; 4. Pretending you know what you&#39;re doing is almost the same as knowing what you are doing, so just accept that you know what you&#39;re doing even if you don&#39;t and do it.  &#xD;&#xA;&gt; 5. Banish procrastination. If you wait more than a week to get an idea done, abandon it.  &#xD;&#xA;&gt; 6. The point of being done is not to finish but to get other things done.  &#xD;&#xA;&gt; 7. Once you&#39;re done, you can throw it away.  &#xD;&#xA;&gt; 8. Laugh at perfection. It&#39;s boring and keeps you from being done.  &#xD;&#xA;&gt; 9. People without dirty hands are wrong. Doing something makes you right.  &#xD;&#xA;&gt; 10. Failure counts as done. So do mistakes.  &#xD;&#xA;&gt; 11. Destruction is a variant of done.  &#xD;&#xA;&gt; 12. If you have an idea and publish it on the internet, that counts as a ghost of done.  &#xD;&#xA;&gt; 13. Done is the engine of more.  &#xD;&#xA;</content>
    <link href="https://dlo.me//archives/2025/01/08/cult-of-done/" rel="alternate"></link>
  </entry>
  <entry>
    <title>I Ditched Jekyll and Built a Static Site Generator Based on Go and SQLite</title>
    <updated>2024-12-29T00:00:00Z</updated>
    <id>tag:dlo.me,2024-12-29://archives/2024/12/31/i-built-a-static-site-generator-based-on-go-and-sqlite</id>
    <content type="html">I&#39;ve got quite the yak shave to share (have you [shaved a yak](https://softwareengineering.stackexchange.com/questions/388092/what-exactly-is-yak-shaving) before? If not, I highly recommend it).&#xD;&#xA;&#xD;&#xA;It goes like this:&#xD;&#xA;&#xD;&#xA;&gt; You start with the desire to wax your car.&#xD;&#xA;&gt;&#xD;&#xA;&gt;&#xD;&#xA;&gt; To wax your car, you need a water hose. Only, your water hose is busted so you need to go down to the hardware store to get a new hose.&#xD;&#xA;&gt;&#xD;&#xA;&gt; To get to the hardware store, you have to drive across a bridge. The bridge requires a pass or ticket. You can&#39;t find your pass, but you know your neighbor has one.&#xD;&#xA;&gt;&#xD;&#xA;&gt; However, your neighbor won&#39;t lend you his pass until you return a pillow that you borrowed. The reason you haven&#39;t returned it is because the pillow is missing some stuffing.&#xD;&#xA;&gt;&#xD;&#xA;&gt; The pillow was originally stuffed with yak hair. In order to re-stuff the pillow you need to get some new yak hair.&#xD;&#xA;&gt;&#xD;&#xA;&gt; And that&#39;s how you end up shaving a yak, when all you really wanted to do was wax your car.&#xD;&#xA;&#xD;&#xA;In summary: I had an interesting idea I wanted to write in long form (not on Twitter!), but it had been quite a while since I wrote a blog post. Unfortunately, so many years had passed since my previous blog post that I couldn&#39;t even get Jekyll to work. So, naturally, *I ended up building a static site generator*.&#xD;&#xA;&#xD;&#xA;## But First, Some History&#xD;&#xA;&#xD;&#xA;My first blog was based on [WordPress](https://wordpress.org/). It served as my journal while I studied abroad in China in 2007.&#xD;&#xA;&#xD;&#xA;It worked pretty well! However, it used a gnarly [cPanel](https://cpanel.net/)-based &#34;infrastructure&#34; and was hosted on a fairly iffy host called [midPhase](https://www.midphase.com/) (which is still around, somehow?).&#xD;&#xA;&#xD;&#xA;For one reason or another, I decided to not renew my hosting plan with them, so they shut down my account and deleted all of my content. Years later, I regretted this decision.&#xD;&#xA;&#xD;&#xA;Fortunately, I had been syndicating all of the content of that blog through [Feedburner](https://feedburner.google.com/), and I was able to extract all of the posts I made using their RSS feed. I lost my photos, though.&#xD;&#xA;&#xD;&#xA;*Aside: I&#39;m not really sure what happened to Feedburner, but whatever it is now, was not what it used to be. I just logged in and don&#39;t really understand what it is.*&#xD;&#xA;&#xD;&#xA;Several years later (2009/2010), I started blogging again, albeit with some infrequency, on [Blogger](https://blogger.com). I posted only a few times there, but it still [lives](https://dloewenherz.blogspot.com/). I haven&#39;t had the heart, or desire, to take it down.&#xD;&#xA;&#xD;&#xA;## The Age of Jekyll&#xD;&#xA;&#xD;&#xA;At some point soon thereafter (2010-2011ish), I must have gotten annoyed with having all of my content owned and hosted by a third-party (having been burned by my experience with my original WordPress blog), and also wanted to try out a cool new static-site generator called [Jekyll](https://jekyllrb.com/) written by [Tom Preston-Werner](https://tom.preston-werner.com/). [This post](https://web.archive.org/web/20100107035227/http://chrismdp.github.com/2009/12/jekyll/) from Chris Parsons is emblematic of the Jekyll zeitgeist in late 2009 ([HN submission here](https://news.ycombinator.com/item?id=998411)).&#xD;&#xA;&#xD;&#xA;At the time, Jekyll was also the only supported service for deploying static sites to [GitHub Pages](https://pages.github.com/). This made the setup process very straightforward and easy.&#xD;&#xA;&#xD;&#xA;For the next several years, writing new posts was a breeze; create new markdown document. Save. Commit. Push. Published.&#xD;&#xA;&#xD;&#xA;About a year later, I decided to add a gem or two that wasn&#39;t compatible with GitHub Pages, so as a result, I migrated everything to CloudFront and S3. That was fun.&#xD;&#xA;&#xD;&#xA;And then I took a little bit of a blogging break.&#xD;&#xA;&#xD;&#xA;Oops.&#xD;&#xA;&#xD;&#xA;What I hadn&#39;t considered was that Ruby is not a platform or language that cares too much about backwards-compatibility. I also didn&#39;t consider that Jekyll was early-stage software and would change a lot (anyone could have predicted this 😬).&#xD;&#xA;&#xD;&#xA;To be fair, this wasn&#39;t a problem for quite a while. But as the years piled on, the Ruby ecosystem&#39;s penchant for breaking changes started to slowly bite me: Jekyll 4 was released in 2019. And then Ruby 3 came out in 2020. And soon enough I realized I literally couldn&#39;t publish a new blog post without upgrading &gt;10 gems (iykyk).&#xD;&#xA;&#xD;&#xA;So I just stopped!&#xD;&#xA;&#xD;&#xA;Which, when you think about it, is kind of wild: I stopped blogging because I literally couldn&#39;t run `bundle exec jekyll build`. And it just wasn&#39;t going to be worth the time to migrate everything to the latest versions. I kept pushing it off, and, before you know it, several years had passed by.&#xD;&#xA;&#xD;&#xA;## Durable, Portable, and Easy-to-understand&#xD;&#xA;&#xD;&#xA;In the last two weeks, I decided enough was enough. I needed to make the call on whether to nuke my entire setup, or try and figure out a migration strategy to a new system. Jekyll just wasn&#39;t going to cut it; even if I fixed the issues now, in just a few years this entire problem would resurface just like a game of whack-a-mole.&#xD;&#xA;&#xD;&#xA;Not to mention, I also have a few other static sites running Jekyll, all with the same issues, and they would need to be moved too. But what system to migrate to? Obviously, it&#39;s never anyone&#39;s immediate thought to build their own static site generator, so I wasn&#39;t going to do that. I&#39;m not *that* unhinged (or am I?).&#xD;&#xA;&#xD;&#xA;So I came up with a list of good alternatives, and I literally ported my blog to each one of them. *Aside: I still kind of can&#39;t believe I did that.*&#xD;&#xA;&#xD;&#xA;With some human assistance, ChatGPT threw together this feature matrix, which does a fairly good job of summarizing the pros/cons between the options I landed on:&#xD;&#xA;&#xD;&#xA;| Feature                  | [Hugo](https://gohugo.io/)   | [Vite](https://vite.dev/) / [React Router](https://reactrouter.com/)        | [Astro](https://astro.build/)                  |&#xD;&#xA;|--------------------------|-----------------------|--------------------------|------------------------|&#xD;&#xA;| **Speed**                | 🟢 Fastest            | 🟠 Moderate (JavaScript-heavy) | 🟢 Fast (optimized builds) |&#xD;&#xA;| **Mental Overhead**      | 🟢 Low (simple templates) | 🟠 Moderate (React concepts, routing) | 🟢 Low (modern, intuitive workflow) |&#xD;&#xA;| **Interactivity**        | 🔴 Limited            | 🟢 Full React SPA/MPA        | 🟢 Hybrid (JS islands)  |&#xD;&#xA;| **Flexibility**          | 🟢 Content-focused    | 🟢 Component-based SPA       | 🟢 Best of both worlds  |&#xD;&#xA;| **Community Support**    | 🟢 Mature             | 🟢 Mature (React ecosystem)  | 🟠 Growing              |&#xD;&#xA;| **Future Proof**         | 🟢 Stable and reliable | 🟠 Moderate (npm/Node issues) | 🟠 Moderate (npm/Node issues) |&#xD;&#xA;| **SEO Optimization**     | 🟢 Excellent (purely static) | 🟠 Requires extra effort (SSR or hydration) | 🟢 Excellent (static-first with flexibility) |&#xD;&#xA;&#xD;&#xA;So that feature matrix is maybe helpful, but my actual experiences went like this:&#xD;&#xA;&#xD;&#xA;### Hugo&#xD;&#xA;&#xD;&#xA;* Converting templates was frustrating since error messages were hard to understand.&#xD;&#xA;* Documentation was extensive but poorly organized. I often found myself digging through pages of search results to uncover how to do simple things, like how to display an image.&#xD;&#xA;* Tons of mental overhead with respect to how templates are actually rendered. See [their documentation on template lookup order](https://gohugo.io/templates/lookup-order/) as an example.&#xD;&#xA;* Unclear when to use shortcodes versus variables / other alternatives.&#xD;&#xA;* Tons of boilerplate to do simple things, such as including a CSS file. E.g.:&#xD;&#xA;&#x9;```go-html-template&#xD;&#xA;&#x9;{{ $opts := dict &#34;transpiler&#34; &#34;libsass&#34; &#34;targetPath&#34; &#34;css/style.css&#34; }}&#xD;&#xA;&#x9;{{ with resources.Get &#34;sass/main.scss&#34; | toCSS $opts | minify | fingerprint }}&#xD;&#xA;&#x9;  &lt;link rel=&#34;stylesheet&#34; href=&#34;{{ .RelPermalink }}&#34; integrity=&#34;{{ .Data.Integrity }}&#34; crossorigin=&#34;anonymous&#34;&gt;&#xD;&#xA;&#x9;{{ end }}&#xD;&#xA;&#x9;```&#xD;&#xA;&#xD;&#xA;### Vite / React Router&#xD;&#xA;&#xD;&#xA;* I&#39;m super familiar with this stack, so getting set up was quick.&#xD;&#xA;* Had to roll my own post management system, and there wasn&#39;t anything off the shelf that would essentially work as a CMS.&#xD;&#xA;* The site was fast to compile and run.&#xD;&#xA;* Even though I could prerender all of the pages, my prediction is that SEO would take a hit due to the sloppy HTML.&#xD;&#xA;* Based on the Node stack so I would expect the entire thing to require an upgrade in a few years. :(&#xD;&#xA;&#xD;&#xA;### Astro&#xD;&#xA;&#xD;&#xA;* Heard tons of good things about this setup lately so I decided why the heck not.&#xD;&#xA;* I thought the entire frontmatter architecture, where TypeScript code lives at the top of .astro files, was very weird.&#xD;&#xA;* Didn&#39;t love the custom file format (&#34;.astro&#34;). Requires an editor plugin to read them correctly and none of my auto formatting tools worked out of the box.&#xD;&#xA;* It was fast-ish.&#xD;&#xA;* Also built on the Node stack, and a newer technology, so inevitably I&#39;d run into some issues in the future.&#xD;&#xA;&#xD;&#xA;### Recap&#xD;&#xA;&#xD;&#xA;In the end, Hugo&#39;s documentation and verbosity ultimately scared me away. Vite/React Router was nice, but it wouldn&#39;t be great SEO and I&#39;d have to deal with Node.js. Astro was a nice experience, but I thought the entire `.astro` file format and the somewhat proprietary &#34;feeling&#34; of the stack to be a turnoff. And being JavaScript-ecosystem-based as well, I&#39;d still have to deal with the future-proofing thing.&#xD;&#xA;&#xD;&#xA;Maybe I&#39;d have to settle. But there was something I realized in the process of trying all of these services out that led me down a unexpected path. First, let&#39;s review how I ported things over.&#xD;&#xA;&#xD;&#xA;## Porting&#xD;&#xA;&#xD;&#xA;Trying out a new static site generator involved several steps:&#xD;&#xA;&#xD;&#xA;&lt;!--&#xD;&#xA;&#xD;&#xA;```pikchr&#xD;&#xA;scale = 0.9&#xD;&#xA;$length = 0.5cm&#xD;&#xA;down&#xD;&#xA;box &#34;Review Docs&#34;&#xD;&#xA;arrow&#xD;&#xA;box &#34;Setup&#34; &#34;Environment&#34;&#xD;&#xA;arrow from last box.e right 1cm then up until even with first box.c then to first box.e&#xD;&#xA;box &#34;Copy Files&#34; with .w at first box.e + (1cm, 0)&#xD;&#xA;```&#xD;&#xA;--&gt;&#xD;&#xA;&#xD;&#xA;1. Read docs for 15m+ to learn the basics.&#xD;&#xA;2. Setup stack.&#xD;&#xA;3. Copy all markdown files from old Jekyll setup to new location.&#xD;&#xA;4. Copy all templates to from old Jekyll setup to new location.&#xD;&#xA;5. Copy static files.&#xD;&#xA;6. Run dev server and iterate for several hours until all posts rendered somewhat correctly.&#xD;&#xA;&#x9;* This involved much searching and replacing, and repeated updates. Even with AI assistance, this was a slog.&#xD;&#xA;&#xD;&#xA;So that was fun. At some point, while porting to Astro, I realized [it could plug into a custom loader](https://docs.astro.build/en/reference/content-loader-reference/), and read from a database. &#34;That&#39;s interesting&#34;, I thought. &#34;Could I just port all of my blog posts to a database, and then make text edits simply by running a SQL query?&#34; Well, yes. I could.&#xD;&#xA;&#xD;&#xA;And then, if I ever needed to move to a different static-site generator in the future, I could just keep using the same database file to regenerate the files or read them from a loader. Sweet. Maybe Astro is the move!&#xD;&#xA;&#xD;&#xA;So I wrote a little script that converted all of my markdown files to a [SQLite](https://www.sqlite.org/) database, and started writing some code in Astro to read those posts. But after about an hour of wrangling with the docs, I couldn&#39;t get it to work! It was just too confusing. At this point I realized this was not sustainable. If I took a month break from blogging this was not going to be something I was going to remember.&#xD;&#xA;&#xD;&#xA;I felt lost and frustrated. What was I to do?&#xD;&#xA;&#xD;&#xA;But then I stepped back, and I realized that my entire mental model of how a static site generator should work was off. I had been thinking about it completely wrong.&#xD;&#xA;&#xD;&#xA;## Logic != Data&#xD;&#xA;&#xD;&#xA;Blog posts are data! Static pages are data! Templates, however, are code. They contain &#34;business logic&#34;. Business logic belongs in code.&#xD;&#xA;&#xD;&#xA;Data belongs in a database. Why am I storing my blog posts in files, and treating them like code? They should live in a database that any static site generator can read from. That way my data is permanent and not tied to my static site generator. That&#39;s how it should be.&#xD;&#xA;&#xD;&#xA;Weirdly, though, not many (any?) popular static site generators use a database as a backend. Pretty much everyone uses flat files. Which is kinda nice (you can see diffs!) but honestly, I don&#39;t really care about diffs. I just want my blog to work, be stable, and my data to be portable.&#xD;&#xA;&#xD;&#xA;At this point I realized where I was headed: I was going to need to build a static site generator.&#xD;&#xA;&#xD;&#xA;It would need to be based on technology that would be permanently future proof, with a data backend that would never become outdated.&#xD;&#xA;&#xD;&#xA;## *A Wild Static Site Generator Appears*&#xD;&#xA;&#xD;&#xA;![A Wild Static Site Generator Appears](/images/wild.jpg)&#xD;&#xA;&#xD;&#xA;The fact that Hugo was built on Go gave me some hints about what direction to take this in, and I&#39;d built much software in Go that still worked without changes for over a decade. So the programming language choice was clear: I would build it in Go.&#xD;&#xA;&#xD;&#xA;For the data side, the choice was also quite clear since I already had a SQLite database from my Astro experiment with all of my blog posts and pages. I didn&#39;t even need to do any additional work there. There was some schema and data modification, sure, but it just involved a few SQL queries and I was set.&#xD;&#xA;&#xD;&#xA;### Some Notes on SQLite&#xD;&#xA;&#xD;&#xA;First of all, SQLite is just awesome. It&#39;s incredible software. [It can literally do anything](https://www.sqlite.org/whentouse.html). Ok, maybe not, but it&#39;s really flexible and solid.&#xD;&#xA;&#xD;&#xA;One thing you may not know is that SQLite is a great &#34;filesystem&#34;. It can store files super efficiently, and there&#39;s even a tool written by the SQLite authors called [sqlar](https://sqlite.org/sqlar/doc/trunk/README.md) which is effectively equivalent to ZIP in terms of performance and space requirements, with the added benefit that you can treat the entire archive as a database!&#xD;&#xA;&#xD;&#xA;You might be wondering: &#34;Dan, can you really store a SQLite database in a Git repo?&#34; The answer is **yes**.&#xD;&#xA;&#xD;&#xA;I had an intuition that Git&#39;s delta compression would store SQLite quite efficiently, and based on my own research and that of [others](https://news.ycombinator.com/item?id=38117779), it turns out that I was right: Git excels at storing SQLite databases in version control. It&#39;s on par, even, with plain text.&#xD;&#xA;&#xD;&#xA;Add in a [custom](https://news.ycombinator.com/item?id=38110286#:~:text=Tracking%20SQLite%20Database%20Changes%20in%20Git) [diff handler](https://news.ycombinator.com/item?id=38110286), specify which filetypes should get treatment with `.gitattributes`, and we now have a setup that shows diffs and is as space-efficient as storing raw markdown files.&#xD;&#xA;&#xD;&#xA;### Go&#xD;&#xA;&#xD;&#xA;Go is remarkably solid. I&#39;ve written programs in Go over a decade ago that run without modification to *anything*, even the build system.&#xD;&#xA;&#xD;&#xA;And that&#39;s expected, as one of Go&#39;s core design principles is [backwards compatibility](https://go.dev/blog/compat). From the linked document:&#xD;&#xA;&#xD;&#xA;&gt; In the quoted text from “Go 1 and the Future of Go Programs” at the top of this post, the ellipsis hid the following qualifier:&#xD;&#xA;&gt; &#xD;&#xA;&gt; &gt; At some indefinite point, a Go 2 specification may arise, but until that time, [… all the compatibility details …].&#xD;&#xA;&gt; &#xD;&#xA;&gt; That raises an obvious question: when should we expect the Go 2 specification that breaks old Go 1 programs?&#xD;&#xA;&gt; &#xD;&#xA;&gt; The answer is never. Go 2, in the sense of breaking with the past and no longer compiling old programs, is **never going to happen**. Go 2 in the sense of being the major revision of Go 1 we started toward in 2017 has already happened.&#xD;&#xA;&gt; &#xD;&#xA;&gt; **There will not be a Go 2 that breaks Go 1 programs**. Instead, we are going to double down on compatibility, which is far more valuable than any possible break with the past. In fact, we believe that prioritizing compatibility was the most important design decision we made for Go 1.&#xD;&#xA;&gt; &#xD;&#xA;&gt; So what you will see over the next few years is plenty of new, exciting work, but done in a careful, compatible way, so that we can keep your upgrades from one toolchain to the next as boring as possible.&#xD;&#xA;&#xD;&#xA;How fricking awesome is that? Whatever you write in Go, will continue to work, forever.&#xD;&#xA;&#xD;&#xA;Go does have its own warts, but so does every language, and one must prioritize needs in order to make software design decisions, and in my case, reliability and future proofing trumped almost everything else.&#xD;&#xA;&#xD;&#xA;Go is also very simple. Simple is good!&#xD;&#xA;&#xD;&#xA;## Doing The Thing&#xD;&#xA;&#xD;&#xA;So yeah, I built a static site generator using Go, using SQLite as the database, stored in version control, and it&#39;s now running this very blog, right now. I wrote a GitHub action that generates the pages (takes &lt;20s to build and deploy the entire site!) and it&#39;s now hosted on GitHub Pages.&#xD;&#xA;&#xD;&#xA;![GitHub Actions](/images/gha.png)&#xD;&#xA;&#xD;&#xA;I even wrote a little editor that runs when I run the site locally using `go run`, so I can edit posts in the browser instead of using a database management tool (I got tired of that quite quickly!).&#xD;&#xA;&#xD;&#xA;I&#39;m super happy with how everything turned out. Dependency management is a breeze, and the entire thing is blazing fast. It preprocesses SCSS and code blocks, and minifies images, CSS, HTML, and JavaScript.&#xD;&#xA;&#xD;&#xA;There are obviously a lot of rough edges given that this is pre-alpha software, but those will be smoothed out soon enough as I port a few other websites over to the new system.&#xD;&#xA;&#xD;&#xA;Some things I want to do next:&#xD;&#xA;&#xD;&#xA;&lt;div class=&#34;checkbox-wrapper&#34;&gt;&#xD;&#xA;&#xD;&#xA;* [ ] Allow an entire website to be bundled as a single executable file, with all static content included, so it can run as a full web server. *Is there something like [Cosmopolitan](https://github.com/jart/cosmopolitan), but for Go?*&#xD;&#xA;* [ ] Make it `go install`able so it can just work as a binary.&#xD;&#xA;* [ ] Write a standard spec for the database schema that will handle most static sites.&#xD;&#xA;* [ ] Turn it into a library so I can simply import it into another Go file to extend behavior.&#xD;&#xA;* [ ] Improve the editing experience, using something like [Editor.js](https://editorjs.io/).&#xD;&#xA;* [ ] Open source it. 🎉&#xD;&#xA;&#xD;&#xA;&lt;/div&gt;&#xD;&#xA;&#xD;&#xA;All in all, this was a fun learning experience and I think I built something pretty cool. I&#39;m really excited to migrate my other websites and see my work pay off (I&#39;m really hoping this will be the last time! 😅🤞🏻).&#xD;&#xA;</content>
    <link href="https://dlo.me//archives/2024/12/31/i-built-a-static-site-generator-based-on-go-and-sqlite" rel="alternate"></link>
    <summary type="html">In this blog post, discover how I turned my frustration with Jekyll into a custom static site generator built in Go, using SQLite for data management. Explore the journey!</summary>
  </entry>
  <entry>
    <title>Creativity Inc.</title>
    <updated>2018-01-22T00:00:00Z</updated>
    <id>tag:dlo.me,2018-01-22://archives/2018/01/22/creativity-inc-7-favorite-things/</id>
    <content type="html">&#xA;This is a list of my 7 favorite things about the book [Creativity, Inc.: Overcoming the Unseen Forces That Stand in the Way of True Inspiration](http://amzn.to/2jNhPDS), by Ed Catmull, co-founder of Pixar Animation.&#xA;&#xA;1. Ed Catmull is a fantastic writer and it was a pleasure to read.&#xA;2. There is a formula to encourage creativity in large organizations.&#xA;3. The portrait of Steve Jobs was eye-opening, and Ed’s interactions with him reflected him in a more realistic light.&#xA;4. Creative success can take decades.&#xA;5. Creative organizations require constant conflict to succeed.&#xA;6. What might have worked in the past will probably not work in the future.&#xA;7. Creators rarely can predict what their inspiration will lead to.&#xA;</content>
    <link href="https://dlo.me//archives/2018/01/22/creativity-inc-7-favorite-things/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Kerrville Tri 2017 Race Report</title>
    <updated>2017-09-24T00:00:00Z</updated>
    <id>tag:dlo.me,2017-09-24://archives/2017/09/24/kerrville-tri-race-report/</id>
    <content type="html">&#xA;This race was a breakthrough for me. I dominated in the swim (3/22 in AG, only ~9s behind the leader), had a PR on the bike in terms of wattage and speed (although it was a flat course), and had my fastest run of any race, including the Splash and Dashes.&#xA;&#xA;The weirdest part about all of it was that, franky, I felt like crap the night before and even the morning of—pretty sure this was the lingering cold I’ve had for a week. Not only that, but my right hip was super stiff. The night before when I went out for a quick grocery run, I was literally walking with a limp. Not good, I thought…&#xA;&#xA;I had redfish and some veggies at around 6:30pm the night before. Prepped my transition bags, and finally got to sleep at around 10ish, a little later than I’d wanted to. I was also pretty stressed out. A big work thing is going awry and I was away from Rachel and Gideon, so that was tough mentally. Went to bed feeling really depressed and not feeling great. Popped two tylenols before falling asleep in an attempt to dull the pain.&#xA;&#xA;I set the alarm for 5am and got right out of bed. Still felt sick and pretty yucky, so decided to take another two tylenol. I could only take 2-3 bites of the yogurt. Banana, nuts, and honey went down easily though. I then took a water bottle and drove to T2 to drop off the run bag and took the shuttle back to T1 at around 5:30. Dawdled a bit and got my bike setup after I got there, prepped a towel, got my shoes set up, etc. At ~6:30 I was starting to feel like I could eat a little more so I ate a clif bar pre-race oatmeal bar that I’d brought along just for kicks. I’d never had one before but I was so hungry and had nothing else, so I decided to risk it. It really didn’t go down easily. I was worried I&#39;d get sick but I pretty much force fed it to myself, reminding myself of what happened at Jack’s Generic Tri in August.&#xA;&#xA;My right hip was still really hurting, so I stretched for about 15 minutes. Still no good. I then massaged the muscle constantly up until literally right before the race started. I was super anxious and worried about the bike. At about 7:15 I had a honey stinger to eliminate any potential energy lapse. I&#39;d rather overeat than under.&#xA;&#xA;It was a TT start for the swim and my AG was the first to go. I was about the 6th person in the water. Right after I jumped in I just got into a flow. I felt fast and just kept pushing. Water was cool and I sighted well. I passed one two people too, which was crazy given how early I was in the water.&#xA;&#xA;Stepping out of the water was right when I knew things were going to go well. I wasn’t tired at all.&#xA;&#xA;It only got better. There was a huge hill to get into T1, and I was worried the day before about how rough that would be. But nope, ran like mad all the way to the top and still felt fresh and full of energy. Then I knew for sure I was going to rock it. Used a towel to wipe grass and dirt off my foot and then got going on the bike.&#xA;&#xA;Before the race, I was thinking I’d be ecstatic if I hit 170W+ on the bike? Well…I don’t know how it happened but I blasted through that goal. 185W normalized for the full duration. I lost a little steam during the last 2 miles which brought my average down, but even for that period I was cruising at 176W. There was another guy who kept trading places with me. In the end, he won out, but the back and forth was great. Pushed myself much harder because of it. Shows you the power of competition I guess. :)&#xA;&#xA;I got of the bike, ran to my run bag, swapped the shoes, and got going. The run course was flat and easy, so it friendly to a new runner like me. Like every other time, I think that I could have gone faster. Then again I had my fastest run ever at a 9:10 / mile pace, so whatever I did worked. My splits reflect my attitude…first kilometer I cruise, then the middle three I take too easy, and the last km I speed up again. If I can cruise longer and then pick of the final pace earlier, that alone I think could bring me to a sub 9” pace. Getting there!&#xA;&#xA;In the end, I really do have no idea where today’s burst of energy and power came from. I’ve been sick all week (and still feel sick!), had a limp hip, and have been super tired…but it all worked out in the end. Also happy I skipped the Splash and Dash earlier this week. I guess my body needed the rest.&#xA;</content>
    <link href="https://dlo.me//archives/2017/09/24/kerrville-tri-race-report/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Jack&#39;s Generic Tri 2017 Race Report</title>
    <updated>2017-08-12T00:00:00Z</updated>
    <id>tag:dlo.me,2017-08-12://archives/2017/08/12/jacks-generic-tri-race-report/</id>
    <content type="html">&#xA;Woke up at 5:10, a little earlier than I’d planned since my dad-in-law decided he was doing the race at the last minute, so I picked him up on the way. This actually sort of caused a cascade of issues since I didn’t have time to eat breakfast. The steel cut oats, banana, and maple syrup REALLY didn’t appeal to me unfortunately, and because I felt so rushed, I *maybe* got one or two bites in before I had to head out. I think this was a big mistake.&#xA;&#xA;Transition setup went well, didn’t forget anything, plan was great.&#xA;&#xA;Swim was fantastic (at least from what I know from the watch). My pace was 1:40/100m which was much better than the previous race. We’ll have to wait on the official results but I was really happy with this. For some reason I got it in my head that I needed to push myself in the swim but in the end I’m a little regretful about this. You&#39;ll see why.&#xA;&#xA;T1 was BAD. I was totally out of breath when I got out of the water and essentially needed to stand still and put hands on knees to regroup myself. I probably lost a minute or two just getting back to the right mental &amp; physical place. Now whether this had something to do with me not really having any breakfast…I guess there’s no way to know, but wouldn’t be surprised.&#xA;&#xA;Bike started off OK I think. The whole time I kept wanting to push harder, but just didn’t have the energy in me. Again, think the lack of a “real” breakfast got me here. I also didn’t drink any water at all for the full 42 minutes. I need a better strategy there too, I just plain forgot and the aero bottle I’m using has a really rigid straw that makes it really tough to drink and ride at the same time without some facial acrobatics. I did have the clif blok earlier than planned because I felt REALLY hungry. Man, lack of breakfast really did not do good things for me. My average power was 152W and normalized was 156W. For comparison, Life Time average was 153W and normalized was 158W. So it is what it is. I do think the reason I didn’t perform as well as I thought I could was 1) pushing too hard in the swim and 2) no breakfast 3) having to pee like crazy from about 15 minutes on. The last issue really affected me more than I would have liked, it was just really hard to push myself.&#xA;&#xA;T2 was much much better than T1. I felt good getting off the bike, got the helmet off, popped a honey stinger, had some water, put on the visor, and went on my way.&#xA;&#xA;Like the swim, the run was my best run of the season so far. According to the watch, my average pace was 9:53 / mile which is ~40s / mile faster than Life Time in late May! I’m really happy with this and didn’t walk for a second! The last km was a sprint for me, I saw another guy with a 33 on his leg and made up my mind to beat him. So I picked up the pace to a 8 minute pace for the last 0.6 km, I kept it up and beat him!&#xA;&#xA;All in all, I’m really happy with the race. At the results tent, my placing was 10/14 when I checked, I’m sure it could change as more people finished but the way I look at it, my swim and run were both MUCH better, and the bike was the same as last race. I would have liked to have hit my bike goals but I think this was more a matter of race day planning and nutrition than fitness. Always something. Onwards to Kerrville where maybe I can learn from today and rock the bike.&#xA;</content>
    <link href="https://dlo.me//archives/2017/08/12/jacks-generic-tri-race-report/" rel="alternate"></link>
  </entry>
  <entry>
    <title>How to Accurately Track Open Water Swims</title>
    <updated>2017-06-24T00:00:00Z</updated>
    <id>tag:dlo.me,2017-06-24://archives/2017/06/24/how-to-accurately-track-open-water-swims-vivoactive-hr-fenix-5/</id>
    <content type="html">&#xA;After some frustrations in tracking my open water swims and races with the [fēnix 5](http://amzn.to/2u1aq60), I decided to do something a little wild. I&#39;d read around online that some people have experimented with taking their [vívoactive HRs](http://amzn.to/2s3BCEs) and clipping them to their goggles, or in their swim caps, and getting decent results.&#xA;&#xA;I hadn&#39;t seen any experimental data or comparisons posted by anyone anywhere, so I figured it was worth a shot. Despite looking like a doofus during my last open water race, I donned my [vívoactive HR](http://amzn.to/2s3BCEs) to my goggle strap, strapped the [fēnix 5](http://amzn.to/2u1aq60) to my wrist, and pitted them both head-to-head.&#xA;&#xA;Starting the workout on the [vívoactive HR](http://amzn.to/2s3BCEs) takes some finesse since you&#39;ll need to do it without looking at the device. Alternatively, you can start the workout before strapping it to the goggle strap and then rush to attach it before the race starts, which I decided was a little too much work (didn&#39;t want to be fiddling with my goggles within seconds of the start). I started workouts for both trackers within a few seconds of each other, and ended both at around the same time as well.&#xA;&#xA;I&#39;m not going to ramble on here; the results speak for themselves. Just check out the images below (don&#39;t judge, I&#39;m not the fastest swimmer, and my sighting could use some work :) ).&#xA;&#xA;&lt;table style=&#34;border:none;padding:0;margin:20px 0;&#34;&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;td style=&#34;text-align:center; padding: 0 0 6px 0&#34;&gt;&lt;h2&gt;&lt;a href=&#34;http://amzn.to/2s3BCEs&#34;&gt;vívoactive HR&lt;/a&gt;&lt;/h2&gt;&lt;/td&gt;&#xA;    &lt;td style=&#34;text-align:center; padding: 0 0 6px 0&#34;&gt;&lt;h2&gt;&lt;a href=&#34;http://amzn.to/2u1aq60&#34;&gt;fēnix 5&lt;/a&gt;&lt;/h2&gt;&lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;  &lt;tr&gt;&#xA;    &lt;td style=&#34;padding: 6px 3px 0 0&#34;&gt;&#xA;      &lt;a href=&#34;http://tpks.ws/D2FZf&#34;&gt;&lt;img src=&#34;/images/triathlon/vahrquarryswim.jpg&#34; /&gt;&lt;/a&gt;&#xA;    &lt;/td&gt;&#xA;    &lt;td style=&#34;padding: 6px 0 0 3px&#34;&gt;&#xA;      &lt;a href=&#34;http://tpks.ws/aCch8&#34;&gt;&lt;img src=&#34;/images/triathlon/fenix5quarryswim.jpg&#34; /&gt;&lt;/a&gt;&#xA;    &lt;/td&gt;&#xA;  &lt;/tr&gt;&#xA;&lt;/table&gt;&#xA;&#xA;The [vívoactive HR](http://amzn.to/2s3BCEs) is light-years more accurate, and gives almost per-second updates on speed and location, whereas the [fēnix 5](http://amzn.to/2u1aq60) only provides occasional GPS updates. If you want the source data, the images are hyperlinked and will take you straight to the workouts on TrainingPeaks. The [fēnix 5](http://amzn.to/2u1aq60) *does* capture stroke data---if that&#39;s an important factor to you---but otherwise falls short.&#xA;&#xA;The reason I found this surprising isn&#39;t that it worked the way it did; that the [vívoactive HR](http://amzn.to/2s3BCEs) won out makes sense since the GPS sensor is exposed to open air for the vast majority of time while it&#39;s clipped to the goggle strap, and almost never fully submerges. The [fēnix 5](http://amzn.to/2u1aq60) has to re-lock GPS within a span of no more than 1.5-2s between strokes. One of the two devices clearly has the upper hand here.&#xA;&#xA;The surprise is that not more people are doing this, or have figured out a way to make it less nerdy. Perhaps a small device that you just clip to your goggles would make for a decent product. Or, if there was an adapter to the [vívoactive HR](http://amzn.to/2s3BCEs) that allowed you to remove the straps and lock it onto your goggles, that wouldn&#39;t be a bad solution either.&#xA;&#xA;In the meantime, I&#39;m just going to keep using my [fēnix 5](http://amzn.to/2u1aq60) for races and keep the [vívoactive HR](http://amzn.to/2s3BCEs) in the swim bag. It&#39;s a little bit of a hassle to get the workouts started on the [vívoactive HR](http://amzn.to/2s3BCEs), and not worth the extra stress, but if you do want a solid map of where you swam in open water, it&#39;s a solid way to go.&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2017/06/24/how-to-accurately-track-open-water-swims-vivoactive-hr-fenix-5/" rel="alternate"></link>
  </entry>
  <entry>
    <title>LifeTime Tri CapTex Race Report</title>
    <updated>2017-06-09T00:00:00Z</updated>
    <id>tag:dlo.me,2017-06-09://archives/2017/06/09/lifetime-tri-captex-race-review/</id>
    <content type="html">&#xA;#### Pre-race&#xA;&#xA;Pros:&#xA;&#xA;* Had dinner (chicken + sweet potato) night before before 6pm&#xA;* Breakfast (oats, banana, maple syrup) was great once again&#xA;* Warmed up for about 10 minutes about an hour pre-swim, stretched, dynamic WU, and then a run (tried to get up to Z4 but couldn&#39;t?).&#xA;* Tri suit fit GREAT and felt great.&#xA;&#xA;Improve:&#xA;&#xA;* Warm up closer to start.&#xA;* Foam roll at home.&#xA;* Couldn&#39;t get to Z4 in the WU. AI: Make sure watch is on correctly / push a little harder in WU?&#xA;&#xA;&lt;hr class=&#39;plain&#39; /&gt;&#xA;&#xA;#### Swim (16:16, 10/22 AG)&#xA;&#xA;Pros:&#xA;&#xA;* Followed plan, could have maybe gone a bit faster but didn&#39;t want to wear myself out.&#xA;* Sighting went great! Didn&#39;t stray too off-course like I did at the Rookie.&#xA;* Breathing went well, stroke could have been a bit stronger.&#xA;&#xA;Improve:&#xA;&#xA;* Got passed a few times. AI: Get faster. :) &#xA;* Felt really thirsty about halfway in. AI: Drink more electrolyte before swim?&#xA;&#xA;&lt;hr class=&#39;plain&#39; /&gt;&#xA;&#xA;#### T1 (2:34, 7/22 AG)&#xA;&#xA;Followed plan to the t, EXCEPT dried my feet a little to get some grass off...not sure if worthwhile.&#xA;&#xA;&lt;hr class=&#39;plain&#39; /&gt;&#xA;&#xA;#### Bike (39:16, 14/22 AG)&#xA;&#xA;Pros:&#xA;&#xA;* New threshold! 149W.&#xA;* DEMOLISHED race plan target of 145W, avg (non-normalized) was 153!&#xA;&#xA;Improve:&#xA;&#xA;* Not in aero as much as I would&#39;ve liked. AI: Stay in aero more during workouts.&#xA;* Despite good performance, I think I could have gone harder. AI: Not sure. Maybe FTP just jumped a lot in past few weeks, so this should work itself out.&#xA;* Completely forgot to have the gel at mile 9/10. AI: Double-check alerts on bike computer and maybe unzip compartment a bit with the gel sticking out as a reminder.&#xA;&#xA;&lt;hr class=&#39;plain&#39; /&gt;&#xA;&#xA;#### T2 (2:50, 19/22 AG)&#xA;&#xA;Improve:&#xA;&#xA;* Forgot to have gel, again. AI: Print out race plan place in transition area.&#xA;* Was really slow when putting on socks. AI: Consider practicing without socks every once in a while. Body glide?&#xA;&#xA;&lt;hr class=&#39;plain&#39; /&gt;&#xA;&#xA;#### Run (32:18, 21/22 AG)&#xA;&#xA;Pros:&#xA;&#xA;* Ran the entire distance--no walking!&#xA;* No hip pain after bike.&#xA;* Beat my Rookie Tri pace by almost a minute per mile (RT was 11:21, CapTex was 10:27).&#xA;&#xA;Improve:&#xA;&#xA;* Felt like I could have pushed myself harder, but didn&#39;t. AI: ???&#xA;&#xA;&lt;hr class=&#39;plain&#39; /&gt;&#xA;&#xA;#### Overall&#xA;&#xA;* My best race ever, despite having a gnarly cold.&#xA;* New bike FTP!&#xA;* Don&#39;t forget to have gels next time!&#xA;</content>
    <link href="https://dlo.me//archives/2017/06/09/lifetime-tri-captex-race-review/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Replacing Tracking Speed</title>
    <updated>2016-01-19T00:00:00Z</updated>
    <id>tag:dlo.me,2016-01-19://archives/2016/01/19/replacing-tracking-speed/</id>
    <content type="html">My iMac has started resetting my mouse&#39;s tracking speed upon every restart. While somewhat frustrating, it&#39;s pretty easy to open up System Preferences -&gt; Mouse, and update the tracking speed to one notch below &#34;Fast&#34; and get on with my work.&#xD;&#xA;&#xD;&#xA;&lt;img src=&#34;/images/mouse.png&#34; /&gt;&#xD;&#xA;&#xD;&#xA;While it&#39;s gotten a little old, it also got me to thinking: why does Apple measure mouse movement in terms of &#34;Tracking speed&#34;? And what is tracking speed, anyways?&#xD;&#xA;&#xD;&#xA;After doing a &#34;fair bit&#34; of research (read: jumping to the [mouse speed](https://en.wikipedia.org/wiki/Computer_mouse#Mouse_speed) section on Wikipedia), I encountered an interestingly named measurement called &#34;Mickeys per second&#34; (tee hee). It makes some sense: according to Wikipedia, it measures &#34;the ratio between how many pixels the cursor moves on the screen and how far the mouse moves on the mouse pad.&#34;&#xD;&#xA;&#xD;&#xA;While, at some point in the past, this might have been a completely sensible measurement, we&#39;ve moved somewhat beyond pixels. Pixels used to be visible to the naked eye, but with today&#39;s 4K and 5K displays, that&#39;s no longer true. What also struck me was that, unless Mickeys per second could change with each display, setups with multiple displays would need a variable number of Mickeys per second to render a constant speed mouse pointer (at least in physical space). Obviously, behind the scenes, modern operating systems are flexible and take account of this, but this dynamic behavior is hidden from the end user.&#xD;&#xA;&#xD;&#xA;Let&#39;s go back to the beginning. Here I am, updating my tracking speed a couple times a week. When I do something more than once, my instinct is to find a way to stop doing it. Ultimately, I came to the conclusion that we (or really, Apple or Microsoft) are thinking about this in the wrong way.&#xD;&#xA;&#xD;&#xA;Think about it. Mouse velocity comes down to three things:&#xD;&#xA;&#xD;&#xA;1. The &#34;reach&#34; of the user&#39;s hand (i.e., the maximum distance the center of the mouse sensor can be moved by the user from one side to the other).&#xD;&#xA;2. The size of the screen.&#xD;&#xA;3. The &#34;intent&#34; distance (i.e., the smallest intentional movement a user can make)&#xD;&#xA;&#xD;&#xA;Without taking into user comfort level, the absolute _minimum_ for this hypothetical measurement should be one screen *per reach*. No matter how good you are with computers, it&#39;s a bad experience if you need to lift up your mouse several times to position the cursor in the right place. At maximum, it again needs to be user-specific. If mouse control is erratic or difficult for the user, the intent value should be larger than for someone with good hand dexterity.&#xD;&#xA;&#xD;&#xA;Since we don&#39;t want to move the mouse at all until the mouse has moved at least the intent distance, and we don&#39;t want to move it less than the screen size, we can determine an upper bound and lower bound for mouse velocity. Furthermore, extracting these values doesn&#39;t entail asking the user to drag a marker on a screen for some arbitrary indicator.&#xD;&#xA;&#xD;&#xA;It would be relatively easy to determine these values automatically based on a simple tool: just ask the user to move the mouse from side-to-side, and then display a grid to the user, prompting them to click between two points as close to each other as possible. Since screen size is already known by the OS, it would just be a simple matter of crunching the numbers to an internal value.&#xD;&#xA;&#xD;&#xA;There&#39;s probably lots of bigger fish to fry on Apple&#39;s Mac OS X team, but I think it&#39;d be a huge improvement to the user experience and would make this setting a lot less opaque to end users.</content>
    <link href="https://dlo.me//archives/2016/01/19/replacing-tracking-speed/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Marco Did The Right Thing</title>
    <updated>2015-09-18T00:00:00Z</updated>
    <id>tag:dlo.me,2015-09-18://archives/2015/09/18/marco-did-the-right-thing/</id>
    <content type="html">Marco Arment released an [iOS 9 Content Blocker](http://peace.land) on Wednesday and it quickly rocketed up to #1 on the Paid App Store charts. At that ranking, apps can pull in tens of thousands of dollars per day.&#xD;&#xA;&#xD;&#xA;For very [good reasons](http://www.marco.org/2015/09/18/just-doesnt-feel-good), he removed it from sale this morning. And then things got nasty.&#xD;&#xA;&#xD;&#xA;Thing is, I think I understand what this week has been like for Marco. In a lot of ways, it reminds me of what happened when a [site I wrote went viral](/archives/2011/03/20/breakup-notifier/) a few years ago.&#xD;&#xA;&#xD;&#xA;If you&#39;ve never made something that&#39;s gone viral before, let me break down how it feels like for the maker:&#xD;&#xA;&#xD;&#xA;1. You&#39;re in shock. You can&#39;t believe something you made resonated with so many people.&#xD;&#xA;2. You freak out because your inbox has become a disaster.&#xD;&#xA;3. You try to get some work done and deal with the explosion of feature requests and attention.&#xD;&#xA;4. You start reading online and notice people are saying some really shitty things about you.&#xD;&#xA;5. At this point you can&#39;t think about anything else except the shit that people are giving you.&#xD;&#xA;6. You want to shut it all down and forget about it.&#xD;&#xA;&#xD;&#xA;For anyone who thinks Marco thought this through or planned it in advance, you&#39;re just deluding yourself. Who in the world could *anticipate* making a #1 rated app in the App Store. I don&#39;t think any independent developer has _ever_ done this outside the context of a game.&#xD;&#xA;&#xD;&#xA;If I were Marco, I&#39;d have been feeling dejected and depressed by this morning and would&#39;ve wanted all of the attention to go away. No one person can deal with it alone.&#xD;&#xA;&#xD;&#xA;In his post, Marco specifically pointed readers to _instructions_ on how to get App Store refunds. I mean, people, if he was actually trying to scam you, don&#39;t you think he&#39;d just leave the app up on the store and just never say anything else about it? At least he&#39;s being honest. If you already purchased the app, it&#39;s not like you didn&#39;t get anything in return.&#xD;&#xA;&#xD;&#xA;You bought a working app. It still works. It will continue to work. There is no scam. Marco isn&#39;t being an asshole. If he&#39;s anything like me, he&#39;s overwhelmed and just wants to get back to normal life. Being the center of crosshairs can really suck.&#xD;&#xA;&#xD;&#xA;Be a little more empathetic. Thank Marco for being honest. Respect his decision. He&#39;s just a person like you and me. I realize how easy is can be to forget that when everyone is just an avatar, but please take it to heart. I wish more people did when a similar thing happened to me 4 years ago.</content>
    <link href="https://dlo.me//archives/2015/09/18/marco-did-the-right-thing/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Making a time-lapse on the command line using FFmpeg and ImageMagick</title>
    <updated>2015-07-26T00:00:00Z</updated>
    <id>tag:dlo.me,2015-07-26://archives/2015/07/26/making-a-time-lapse-using-ffmpeg-and-imagemagick/</id>
    <content type="html">We&#39;re vacationing in Whistler, BC right now as &#34;endurance spectators&#34; to my father-in-law&#39;s 3rd Ironman triathlon. Expecting some beautiful landscapes and weather, I brought my newly acquired X100T to take some nice photos.&#xD;&#xA;&#xD;&#xA;Yesterday, I set it up on an interval timer and pointed it right towards Rainbow Mountain, which faces the patio in the kitchen of the little condo unit we&#39;re renting out. After all was said and done, I ended up with 400 images depicting clouds moving over a mountain peak and not much idea of what to do with them. So, as any self-respecting engineer would, I set out to create a time-lapse using only my trusty command-line tools: [FFmpeg](https://www.ffmpeg.org) and [ImageMagick](http://www.imagemagick.org/script/index.php).&#xD;&#xA;&#xD;&#xA;Let&#39;s get down to it.&#xD;&#xA;&#xD;&#xA;_Note: Everything in this tutorial assumes that you have a current copy of ImageMagick and FFmpeg installed on your machine._&#xD;&#xA;&#xD;&#xA;### Resizing&#xD;&#xA;&#xD;&#xA;Even though I turned off RAW on the X100T, the images were still pretty huge (4896x3264). During my first tests, making movies from images this large gave really inconsistent results and took a long time to create, with not much extra benefit.&#xD;&#xA;&#xD;&#xA;Therefore, the first thing you should probably do is check the size of your images and, if necessary, resize them to be a bit smaller so they will play more nicely with FFmpeg and any other image manipulation that you&#39;re going to do.&#xD;&#xA;&#xD;&#xA;Since I planned to upload my video to YouTube, I referenced a handy page they have that lists out their preferred resolutions, codecs, and formats for upload ([https://support.google.com/youtube/answer/1722171?hl=en](https://support.google.com/youtube/answer/1722171?hl=en)). If you&#39;re like me, and you don&#39;t care too much about maintaining the current aspect ratio, here&#39;s what you can do. This will resize your images to a preferred resolution (in this case, 1280x720), and will potentially crop off the sides or top in the process. To start, make sure you&#39;re in the directory with all of your photos.&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;$ for FILE in `ls *.JPG`; do \&#xD;&#xA;  mogrify -resize 1280x720^ -gravity center -crop 1280x720+0+0 +repage -write RESIZED_PHOTO_DIRECTORY/$FILE $FILE; \&#xD;&#xA;done&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;In detail, this command `-resize`s photos to a `1280x720^` resolution (the caret means that the smaller of width and height is maintained and the larger one is kept even if the resolution is larger), and then, by using `-gravity` and `center`ing, we crop the image to `1280x720` exactly, and write to `RESIZED_PHOTO_DIRECTORY/$FILE`. Phew, that was a mouthful.&#xD;&#xA;&#xD;&#xA;If you just want to resize to a certain height/width and want to maintain the original resolution, just do this:&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;$ for FILE in `ls *.JPG`; do \&#xD;&#xA;    mogrify -resize 600x -write RESIZED_PHOTO_DIRECTORY/$FILE $FILE; \&#xD;&#xA;done&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;### Maintaining Color Distribution&#xD;&#xA;&#xD;&#xA;_Note: this step might not be necessary in your situation, but it greatly improved the quality of the final product for me. YMMV._&#xD;&#xA;&#xD;&#xA;Sometimes images captured in a time lapse have very different histograms (especially if you have auto-aperture / shutter-speed enabled), and this can make things look &#34;jumpy&#34; from frame to frame. Obviously, this won&#39;t look great in your final video, so we&#39;re going to normalize the colors to a set distribution.&#xD;&#xA;&#xD;&#xA;For an example, just compare the following two images (especially notice the trees, which are much lighter in the first example than the second):&#xD;&#xA;&#xD;&#xA;![](/images/time-lapse/example2.jpg)&#xD;&#xA;&#xD;&#xA;![](/images/time-lapse/example1.jpg)&#xD;&#xA;&#xD;&#xA;Not ideal, right?&#xD;&#xA;&#xD;&#xA;To help achieve this end, I used an ImageMagick script called `histmatch`, generously provided by Fred Weinhaus (link: [http://www.fmwconcepts.com/imagemagick/histmatch/index.php](http://www.fmwconcepts.com/imagemagick/histmatch/index.php) or [backup](/backups/histmatch.mhtml)). The idea to use a reference image to generate a histogram that we want all of the other images to match. Once you&#39;ve decided on your reference image, run the following on every image except the reference image (otherwise the universe will explode).&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;histmatch -c gray REFERENCE.JPG TARGET_IMAGE.JPG NORMALIZED_IMAGE_DIRECTORY/FILE.JPG&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;(I just piped the output of `ls *.JPG` into a file called `normalize.sh` and used some of my Vim-fu to do this. Your process might be different.)&#xD;&#xA;&#xD;&#xA;*12/27/2017 Update:*&#xD;&#xA;&#xD;&#xA;Re-reading this post, I&#39;ve found it&#39;s much easier to just move the target image to the normalized directory, and then run this using find/exec.&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;find . -depth 1 -name &#34;*.JPG&#34; -exec histmatch -c gray normalized/TARGET_IMAGE.JPG {} normalized/{} \;&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;### Finally, make the darned movie&#xD;&#xA;&#xD;&#xA;This is the fun part. Just send the files through to FFmpeg and have it do its magic. If filenames are incrementally named, you&#39;ll want to provide the parameters below (like `-start_number` and the `_DSF%04d.JPG` format) to make things match up.&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;ffmpeg -start_number 1 -i _DSF%04d.JPG -c:v libx264 -pix_fmt yuv420p timelapse.mp4&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;This tells FFmpeg to take all of the JPEGs in the directory starting with `_DSF` and ending with 4 digits, and to output an h.264 video with the `yuv420p` colorspace to `video.mp4`. You now have a beautiful timelapse!&#xD;&#xA;&#xD;&#xA;If you&#39;re interested in the final product, you can [check it out on YouTube](https://www.youtube.com/watch?v=zGHGTcas_24). Enjoy!</content>
    <link href="https://dlo.me//archives/2015/07/26/making-a-time-lapse-using-ffmpeg-and-imagemagick/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Post-WWDC 2015 Keynote Thoughts</title>
    <updated>2015-06-08T00:00:00Z</updated>
    <id>tag:dlo.me,2015-06-08://archives/2015/06/08/post-wwdc-2015-keynote/</id>
    <content type="html">&#xA;It&#39;s now been a few hours after The WWDC 2015 Keynote, and I&#39;ve had some time to digest everything. My immediate impression of everything was a little &#34;meh&#34;, but then again, that&#39;s sort of how I feel every year. It&#39;s hard to satisfy everyone.&#xA;&#xA;Second impression is that the WWDC Keynote is no longer for developers. It&#39;s for the end users and wannabe developers. Us old-timers are too jaded to care about this new and shiny stuff, and most of the new products in the keynote aren&#39;t even things developers can use (on that note, was it really necessary to spend _that_ much time on Apple Music?).&#xA;&#xA;Another noteworthy thing--no new hardware. I was expecting at least _something_, so to hear crickets was a little unfortunate. I can only assume that the inordinate amount of time spent talking about Apple Music was in some part due to a need to &#34;fill time&#34; from what would have originally been a 15-20 minute spiel on Apple TV.&#xA;&#xA;As always, the things that are exciting to me happen in the sessions throughout the week. The Keynote is sort of just a preview of what&#39;s to come. After some perusing through the documentation, here&#39;s what I&#39;m excited about:&#xA;&#xA;### Deep Linking&#xA;&#xA;I wrote about this in my WWDC 2015 Wishlist, and it came true. You can now link up URLs to be opened by your application. I haven&#39;t had too much time to play around with iOS 9 yet, so I&#39;m not sure how this works from either the developer or end-user side of things, but my first impression is that Apple did this right.&#xA;&#xA;### Search&#xA;&#xA;Besides the notable backtracking of moving search away from the pull-down gesture and _back_ to the left of the home screen (I would have loved to be a fly on the wall in that meeting), iOS Search can now display search results straight from apps in Spotlight.&#xA;&#xA;Just to illustrate an example---my company writes an app called Tweet Seeker that lets people download their Twitter archives and search their tweets locally on their device. Tweet Seeker can now hook right into iOS and display tweet search results right from Spotlight. Now that&#39;s cool!&#xA;&#xA;### iPad Multitasking&#xA;&#xA;Remember the iPad Pro? Well, this is it. You take a regular iPad, and you install iOS 9. There. iPad Pro. :claps:&#xA;&#xA;You know those things that you can&#39;t really imagine a use for, or want, and then in 3 months you realize you can&#39;t live without it? Yeah, well, I&#39;d put money that this is one of those things.&#xA;&#xA;I can see this changing how people use their iPads, along with the new keyboard gestures. The iPad is no longer just a toy, or larger screen iPhone made for watching Netflix and HBO. It&#39;s now a tool to get shit done.&#xA;&#xA;### Swift 2&#xA;&#xA;I think it&#39;s great news that Swift is going open source &#34;later this year&#34;. Of course, in Apple parlance, that means probably somewhere around December 15-31 (just trying to be realistic here--it&#39;s not easy to open-source something like this, I&#39;d bet there is a _ton_ of proprietary code lurking in that codebase).&#xA;&#xA;Also, I think with this release, I&#39;m comfortable picking up the Swift book and starting to actually learn the language. I am embarrassed (only a bit, though) that I haven&#39;t written a single line of code in the language. With this latest release, I think I might be ready to start jumping in. A 2.0 implies a little more stability, and other developers who were waiting on the sidelines will probably jump in on the fun as well.&#xA;&#xA;Another telling thing is that all the code I saw in the &#34;Developers State of the Union&#34; Keynote was written in Swift. I don&#39;t think it&#39;s any secret now that Apple considers Swift to be the future. As much as we might not want it, it&#39;s going to happen.&#xA;&#xA;If you started learning it a few months ago, though, prepare to have to relearn a bunch of stuff. I wouldn&#39;t be surprised if Apple pulled the rug out from under you.&#xA;&#xA;### Apple Music&#xA;&#xA;I&#39;ll keep it brief. I don&#39;t need it. Spotify works great for me. I think its success hinges on musicians buying in. We don&#39;t want another Ping here.&#xA;&#xA;### Conclusion&#xA;&#xA;You&#39;ll notice I skipped over a lot of the OS X stuff. I honestly haven&#39;t had enough time to digest it all. So. Much. New. Stuff. Will download and report back.&#xA;&#xA;That&#39;s it for now. I&#39;m taking a walk to think about this all some more.&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2015/06/08/post-wwdc-2015-keynote/" rel="alternate"></link>
  </entry>
  <entry>
    <title>WWDC 2015 Wishlist</title>
    <updated>2015-06-03T00:00:00Z</updated>
    <id>tag:dlo.me,2015-06-03://archives/2015/06/03/wwdc-2015-wishlist/</id>
    <content type="html">&#xA;Here&#39;s my list of things I&#39;d be overjoyed to see fixed / announced / released at WWDC 2015 next week.&#xA;&#xA;### iOS&#xA;&#xA;* Deep linking support. Let apps define associated URLs (https://twitter.com/* --&gt; open Twitter.app).&#xA;* Really want to hide my carrier and clean up that status bar. Such a mess right now.&#xA;* It&#39;s time to modernize the UI for Notes.app and Reminders.app. iOS 6 was years ago.&#xA;* Save alarm and world clock data in iCloud. Re-adding this on every device is really tedious.&#xA;* Export Health.app data or save it in iCloud with a password. At the moment, it&#39;s not shared between devices and will be lost permanently if you don&#39;t have an encrypted local iTunes backup of your device.&#xA;* Needs to be a way to close all tabs in Safari without tapping the screen 50x times. Absurd.&#xA;&#xA;### Mail.app&#xA;&#xA;* Search that actually works. Right now this is the only reason I still have the Gmail app still on my phone. I open it up whenever I want to search for a message.&#xA;* Send from an alias like Gmail web client lets you.&#xA;* Better UX for labeling threads.&#xA;&#xA;### Maps&#xA;&#xA;* (I&#39;ve mentioned this on Twitter) Fix the UX with reviews of physical locations.&#xA;* Public transit support.&#xA;* Improved traffic data.&#xA;&#xA;### App Store&#xA;&#xA;* Search. Need I say more?&#xA;* Direct refunds without iTunes support involvement.&#xA;* Responding to reviews would be nice.&#xA;* Expire old reviews (1yr+) when new versions of app have since been released.&#xA;&#xA;### Safari (OS X)&#xA;&#xA;* Reopen more than the most recent closed tab in Safari.&#xA;* Paste images into web pages.&#xA;&#xA;### Xcode&#xA;&#xA;* Stop beachballing so darn much.&#xA;* It would be nice to stop an archive action without needing to restart the app.&#xA;* Debugging extensions is currently a huge pain. It would be nice if it were easier.&#xA;&#xA;*Added 6/7/2015*&#xA;&#xA;### Photos on OS X&#xA;&#xA;* &#34;Show in Finder&#34;&#xA;</content>
    <link href="https://dlo.me//archives/2015/06/03/wwdc-2015-wishlist/" rel="alternate"></link>
  </entry>
  <entry>
    <title>The Free, Easy, and Secure Way to Encrypt Shared Dropbox Folders on Your Mac</title>
    <updated>2015-03-29T00:00:00Z</updated>
    <id>tag:dlo.me,2015-03-29://archives/2015/03/29/encryption-with-dropbox-on-os-x/</id>
    <content type="html">&#xA;My wife and I have been searching for months for a service to allow us to store our sensitive files in Dropbox securely. There are a lot of services out there that promise to do this well, but frankly, it&#39;s hard to trust that our data would be safe if they happened to be hacked or compromised. I therefore went looking for a piece of software that was:&#xA;&#xA;1. Vetted as being secure.&#xA;2. Ubiquitous. Something I wouldn&#39;t need to worry about installing or finding on a future machine.&#xA;3. Compatible with Mac OS X.&#xA;&#xA;TrueCrypt, an open-source file encryption product, seemed like it might fit the bill. *However*, I must have had my head in the sand about a year ago (sure enough, I was off the grid when it happened), but TrueCrypt was abandoned by its developers with a very vague / scary message on their website:&#xA;&#xA;&gt; WARNING: Using TrueCrypt is not secure as it may contain unfixed security issues&#xA;&#xA;Ok then. So that in itself wasn&#39;t reason enough for me to give up. I went ahead and downloaded TrueCrypt from some random site and verified the checksum (remind me which of my family is going to know how to do this?) and installed it (in order to do this, I had to temporarily disable OS X&#39;s protection against unsigned software in &#34;Security &amp; Privacy&#34; in System Preferences).&#xA;&#xA;That&#39;s a lot of steps for someone who just wants to share encrypted files. And more steps are bad---I didn&#39;t want people I&#39;m sharing stuff with to just give up. We&#39;d be back to square one.&#xA;&#xA;Not to mention, TrueCrypt doesn&#39;t offer dynamically sized volumes. You have to specify the size of the volume at the time of creation. This means that if the number of files in a volume you&#39;re sharing hits the limit---too bad. You need to now create a new volume (one with a larger limit) and move all of the existing files from the previous volume into it. That&#39;s a drag. And sort of dumb.&#xA;&#xA;Off I went looking for an alternative. &#34;Wait&#34;, I thought, &#34;doesn&#39;t OS X provide full-disk encryption via FileVault?&#34;. Well, I didn&#39;t actually ask myself that, but I concluded that there _must_ be a way to do this just using a stock Mac OS X installation.&#xA;&#xA;Sure enough, after some digging, I found this on Apple&#39;s support site: [How to create a password-protected (encrypted) disk image](https://support.apple.com/en-us/HT201599). Boom! There we have it.&#xA;&#xA;If you follow the instructions (which are quite simple, really), you&#39;ll have a &#34;file&#34; (which is actually a &#34;sparse image&#34;---sort of like a Volume) which you can drop into a shared Dropbox folder and share with anyone you want. To add or remove files to the encrypted folder, you need to double click it, type in the password, and then open it like you would any other volume (like your Hard Drive or a USB stick). Once you&#39;re done modifying files, just &#34;eject&#34; it. Easy peasy. Dropbox syncs it immediately.&#xA;&#xA;Oh---and the folder resizes automatically! Another huge plus over TrueCrypt.&#xA;&#xA;So, in short, you can create an AES-encrypted folder, inside your Dropbox, and anyone else who uses a Mac will be able to add or remove files from it without needing to install any third-party software. And, it&#39;s free.&#xA;&#xA;Go forth and encrypt!&#xA;&#xA;P.S. Windows users might have similar luck with BitLocker.&#xA;&#xA;P.P.S. [Jamie Phelps](https://twitter.com/jxpx777) [pointed me](https://twitter.com/jxpx777/status/582394914459144192) to [this AgileBits article](https://help.agilebits.com/Knox/sync_vaults.html) regarding a downside of using sparse _bundles_ and Dropbox (note--a sparse image and a sparse bundle are not the same thing, so the two may *not* have the same downsides). Knox stores its vaults with OS X sparse bundles, and those have been shown to have issues syncing over Dropbox when simultaneous edits are made. I&#39;ve yet to see this be a problem with using sparse images (since OS X---and Dropbox---treats a sparse image as a single file), but it might be an issue. Jamie recommends [SafeMonk](https://www.safemonk.com/) as an alternative.&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2015/03/29/encryption-with-dropbox-on-os-x/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Supercharge Your Python Shell</title>
    <updated>2014-09-08T00:00:00Z</updated>
    <id>tag:dlo.me,2014-09-08://archives/2014/09/08/pythonrc/</id>
    <content type="html">&#xA;I&#39;ve been using and working with Python in a professional context for around 8 years. So it came sort of a shock that something so useful, so obvious, was unfamiliar to me until around a week and a half ago.&#xA;&#xA;It involves a little file called `.pythonrc` that lives in your home directory. To get it to work, you&#39;ll need to define an environment variable called `PYTHONSTARTUP`, like so:&#xA;&#xA;```bash&#xA;export PYTHONSTARTUP=&#34;$HOME/.pythonrc&#34;&#xA;```&#xA;&#xA;(Major hat tip to [kasnalin](https://www.reddit.com/user/kasnalin) on Reddit for [pointing out](https://www.reddit.com/r/Python/comments/2fun6t/how_to_supercharge_your_python_shell_with_a/ckd1w4t) that I&#39;d forgotten to include this important piece of info in an earlier version of the post.)&#xA;&#xA;If you&#39;re a Python developer, no doubt you open up a Python shell countless times per day. Whether you use the regular Python shell, or another one such as IPython, it&#39;s all the same. Open up the shell, import a few useful modules, and start playing around.&#xA;&#xA;Maybe you find yourself importing the same modules over and over again. Maybe you leave a single shell open all the time since it&#39;s such a pain to re-type those commands.&#xA;&#xA;I spend a lot of time working with Django, so I&#39;d gotten pretty used to typing `./manage.py shell` and then `from app import models` to start playing around with objects in my database.&#xA;&#xA;Well, I got to wondering what this `.pythonrc` could do to help me out with this, so I dumped in the following code:&#xA;&#xA;```python&#xA;try:&#xA;    from app import models&#xA;    from django.conf import settings&#xA;except:&#xA;    print(&#34;\nCould not import Django modules.&#34;)&#xA;else:&#xA;    print(&#34;\nImported Django modules.&#34;)&#xA;```&#xA;&#xA;Not expecting this to work at all, I ran my trusty Django shell, and voilà:&#xA;&#xA;```bash&#xA;$ ./manage.py shell&#xA;&#xA;Imported Django modules.&#xA;Python 2.7.6 (default, Jan  6 2014, 13:22:56)&#xA;[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin&#xA;Type &#34;help&#34;, &#34;copyright&#34;, &#34;credits&#34; or &#34;license&#34; for more information.&#xA;(InteractiveConsole)&#xA;&gt;&gt;&gt;&#xA;```&#xA;&#xA;It&#39;s like magic. No more reverse searching my shell history to import commonly-used modules. If I find myself using one often enough, I just add it into my `.pythonrc` and it&#39;s there for me every time.&#xA;&#xA;I play around Redis a bunch, so here&#39;s another chunk of code I plopped in there.&#xA;&#xA;```python&#xA;from redis import StrictRedis as Redis&#xA;&#xA;r = Redis()&#xA;```&#xA;&#xA;Now, I can just open up the shell and use `r` to play with Redis.&#xA;&#xA;Here&#39;s another one I&#39;m pretty fond of. I&#39;m a big fan of tab-completion and readline support. So I put this in my `.pythonrc` too:&#xA;&#xA;```python&#xA;try:&#xA;    import readline&#xA;except ImportError:&#xA;    print(&#34;Module readline not available.&#34;)&#xA;else:&#xA;    import rlcompleter&#xA;    if &#39;libedit&#39; in readline.__doc__:&#xA;        readline.parse_and_bind(&#34;bind ^I rl_complete&#34;)&#xA;    else:&#xA;        readline.parse_and_bind(&#34;tab: complete&#34;)&#xA;```&#xA;&#xA;Works like a charm.&#xA;&#xA;Below is my full `.pythonrc`, in all of its glory (also available as a [Gist](https://gist.github.com/dlo/e0b48890495a6c4b3065)).&#xA;&#xA;```python&#xA;# vim: set ft=python :&#xA;&#xA;from __future__ import print_function&#xA;&#xA;import json&#xA;import sys&#xA;import datetime&#xA;&#xA;from redis import StrictRedis as Redis&#xA;&#xA;r = Redis()&#xA;&#xA;try:&#xA;    import readline&#xA;except ImportError:&#xA;    print(&#34;Module readline not available.&#34;)&#xA;else:&#xA;    import rlcompleter&#xA;    if &#39;libedit&#39; in readline.__doc__:&#xA;        readline.parse_and_bind(&#34;bind ^I rl_complete&#34;)&#xA;    else:&#xA;        readline.parse_and_bind(&#34;tab: complete&#34;)&#xA;&#xA;try:&#xA;    from app import models&#xA;    from django.conf import settings&#xA;except:&#xA;    print(&#34;\nCould not import Django modules.&#34;)&#xA;else:&#xA;    print(&#34;\nImported Django modules.&#34;)&#xA;&#xA;try:&#xA;    from dateutil.parser import parse as parse_date&#xA;except ImportError:&#xA;    print(&#34;\nCould not import dateutil.&#34;)&#xA;```&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2014/09/08/pythonrc/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Getting Started with Django and PostgreSQL Full-Text Search</title>
    <updated>2014-09-01T00:00:00Z</updated>
    <id>tag:dlo.me,2014-09-01://archives/2014/09/01/postgresql-fts/</id>
    <content type="html">After hearing a few months back that PostgreSQL had built-in full-text search support, I had been continually searching for reasons to take advantage of it. A few months went by, and when one of our clients at [Lionheart](http://lionheartsw.com) started running into issues with slowness using the Django admin&#39;s built-in search support, I knew I had a solid candidate. After all was said and done, searches returned results approximately 20x faster than vanilla Django. Here&#39;s how you can do it too.&#xA;&#xA;## The Basics&#xA;&#xA;One of the great things about PostgreSQL full-text search is that there isn&#39;t too much to learn if you&#39;re already pretty familiar with search engines and how they work. The main idea is that you generate an index using a document containing terms you&#39;d like to search on. PostgreSQL provides a lot of flexibility with regards to _how_ you choose to do this, but I opted to store the documents as a field called `fts_document` on the table we wanted to search on.&#xA;&#xA;PostgreSQL stores search data in a data type called a `tsvector`. Here&#39;s how the [PostgreSQL documentation](http://www.postgresql.org/docs/9.3/static/datatype-textsearch.html) describes it:&#xA;&#xA;&gt; A tsvector value is a sorted list of distinct lexemes, which are words that have been normalized to merge different variants of the same word. Sorting and duplicate-elimination are done automatically during input.&#xA;&#xA;Here&#39;s an example of a sentence converted to a tsvector:&#xA;&#xA;```psql&#xA;=# SELECT to_tsvector(&#39;simple&#39;, &#39;The quick brown fox jumps over the lazy dog.&#39;);&#xA;                                to_tsvector&#xA;---------------------------------------------------------------------------&#xA; &#39;brown&#39;:3 &#39;dog&#39;:9 &#39;fox&#39;:4 &#39;jumps&#39;:5 &#39;lazy&#39;:8 &#39;over&#39;:6 &#39;quick&#39;:2 &#39;the&#39;:1,7&#xA;(1 row)&#xA;```&#xA;&#xA;When you want to search for something, you create what&#39;s called a `tsquery`.&#xA;&#xA;&gt; A tsquery value stores lexemes that are to be searched for, and combines them honoring the Boolean operators &amp; (AND), \| (OR), and ! (NOT). Parentheses can be used to enforce grouping of the operators&#xA;&#xA;Here&#39;s an example:&#xA;&#xA;```psql&#xA;dan=# SELECT to_tsquery(&#39;simple&#39;, &#39;fox | jumps&#39;);&#xA;   to_tsquery&#xA;-----------------&#xA; &#39;fox&#39; | &#39;jumps&#39;&#xA;(1 row)&#xA;```&#xA;&#xA;When you want to test for a match, you use the `@@` operator.&#xA;&#xA;```psql&#xA;dan=# SELECT to_tsvector(&#39;simple&#39;, &#39;The quick brown fox jumps over the lazy dog.&#39;) @@ to_tsquery(&#39;simple&#39;, &#39;fox&#39;);&#xA; ?column?&#xA;----------&#xA; t&#xA;```&#xA;&#xA;It&#39;s a match!&#xA;&#xA;The `@@` operator also provides a shorthand whereby you don&#39;t need to use the `to_tsvector` / `to_tsquery` functions explicitly. E.g.,&#xA;&#xA;```psql&#xA;dan=# SELECT &#39;The quick brown fox jumps over the lazy dog.&#39; @@ &#39;fox&#39;;&#xA; ?column?&#xA;----------&#xA; t&#xA;```&#xA;&#xA;Sweet! However, you might be confused by the following:&#xA;&#xA;```psql&#xA;dan=# SELECT &#39;The quick brown fox jumps over the lazy dog.&#39; @@ &#39;fox | random&#39;;&#xA; ?column?&#xA;----------&#xA; f&#xA;```&#xA;&#xA;Huh? We&#39;re searching for _either_ the term &#34;fox&#34; or &#34;random&#34;. This doesn&#39;t seem right...&#xA;&#xA;Well, it turns out that when you specify plain text as an argument to the `@@` operator, it wraps it in `plainto_tsquery`, which functions a little bit differently than the `to_tsquery` function that we&#39;ve been playing around with above. Notably, it ignores every logical operator except `&amp;`. So `|`&#39;s, parentheses, etc., will all be ignored. Personally, I don&#39;t find it to be all that useful.&#xA;&#xA;Also, as you might have noticed above, I provided an additional argument to both the `to_tsvector` and `to_tsquery` functions called &#39;simple&#39;. This represents the simple text search configuration. Other text search configurations are language-based and strip out stopwards and other lexemes based on what language you might be indexing. Here&#39;s a few other options that I have available on my machine.&#xA;&#xA;```psql&#xA;dan=# \dF&#xA;               List of text search configurations&#xA;   Schema   |    Name    |              Description&#xA;------------+------------+---------------------------------------&#xA; pg_catalog | danish     | configuration for danish language&#xA; pg_catalog | dutch      | configuration for dutch language&#xA; pg_catalog | english    | configuration for english language&#xA; pg_catalog | finnish    | configuration for finnish language&#xA; pg_catalog | french     | configuration for french language&#xA; pg_catalog | german     | configuration for german language&#xA; pg_catalog | hungarian  | configuration for hungarian language&#xA; pg_catalog | italian    | configuration for italian language&#xA; pg_catalog | norwegian  | configuration for norwegian language&#xA; pg_catalog | portuguese | configuration for portuguese language&#xA; pg_catalog | romanian   | configuration for romanian language&#xA; pg_catalog | russian    | configuration for russian language&#xA; pg_catalog | simple     | simple configuration&#xA; pg_catalog | spanish    | configuration for spanish language&#xA; pg_catalog | swedish    | configuration for swedish language&#xA; pg_catalog | turkish    | configuration for turkish language&#xA;(16 rows)&#xA;```&#xA;&#xA;Play around with a few and see which one suits you best.&#xA;&#xA;## Django&#xA;&#xA;By now you know how to create search vectors and queries, search on some data, and specify a text search configuration. Let&#39;s get started on connecting these tools with your Django project.&#xA;&#xA;Since `tsvector` and `tsquery` are both standard PostgreSQL data types, we can use them just as we might any other data type like `int` or `varchar` in a column definition. This is very handy. Otherwise, we would have to regenerate vectors on-the-fly, which might offset any speedup we get from using FTS in the first place. :)&#xA;&#xA;For the sake of moving forward, let&#39;s say we have a model called `Employee` in our Django project. We&#39;re trying to speed up searches on anything related to an employee. Assume we&#39;re storing their first name, last name, and full address.&#xA;&#xA;```python&#xA;class Employee(db.Model):&#xA;    first_name = models.CharField(max_length=64)&#xA;    last_name = models.CharField(max_length=64)&#xA;    address_1 = models.TextField()&#xA;    address_2 = models.TextField()&#xA;    city = models.CharField(max_length=64)&#xA;    state = models.CharField(max_length=2)&#xA;    zip = models.CharField(max_length=9)&#xA;```&#xA;&#xA;We&#39;re now going to create an `tsvector` based on these fields. To do this, we&#39;ll concatenate all of the fields into a list and then flatten the list into a string to feed into the `to_tsvector` function.&#xA;&#xA;```psql&#xA;dan=# SELECT to_tsvector(&#39;simple&#39;, concat_ws(&#39; &#39;, first_name, last_name, address_1, address_2, city, state, zip)) FROM employee;&#xA;                               to_tsvector&#xA;-------------------------------------------------------------------------&#xA; &#39;123&#39;:3 &#39;12345&#39;:8 &#39;in&#39;:7 &#39;main&#39;:4 &#39;pawnee&#39;:6 &#39;ron&#39;:1 &#39;st&#39;:5 &#39;swanson&#39;:2&#xA;(1 row)&#xA;```&#xA;&#xA;If we wanted to stop here, we could just do FTS matches on the fly, but this would be quite slow.&#xA;&#xA;```psql&#xA;dan=# SELECT COUNT(*) FROM employee WHERE to_tsvector(&#39;simple&#39;, concat_ws(&#39; &#39;, first_name, last_name, address_1, address_2, city, state, zip)) @@ &#39;swanson&#39;;&#xA; count&#xA;-------&#xA;     1&#xA;(1 row)&#xA;```&#xA;&#xA;The next step is to store this `tsvector` in a field and keep it updated when the data changes. First step seems easy enough:&#xA;&#xA;```psql&#xA;dan=# ALTER TABLE employee ADD COLUMN fts_document tsvector;&#xA;```&#xA;&#xA;Hmm, but how to we keep this updated? There are a bunch of options, but according to the PostgreSQL documention, the best one is to add a trigger on document updates and additions. To make things easy, we&#39;re going to create a function to generate the fts document. E.g.,&#xA;&#xA;```psql&#xA;CREATE FUNCTION employee_fts_document(e employee) RETURNS tsvector AS $$&#xA;DECLARE&#xA;    employee_document TEXT;&#xA;BEGIN&#xA;    SELECT concat_ws(&#39; &#39;, e.first_name, e.last_name, e.address_1, e.address_2, e.city, e.state, e.zip) INTO employee_document;&#xA;    RETURN to_tsvector(&#39;pg_catalog.simple&#39;, employee_document);&#xA;END;&#xA;$$ LANGUAGE plpgsql;&#xA;```&#xA;&#xA;We can then reference this function in the trigger.&#xA;&#xA;```psql&#xA;CREATE FUNCTION employee_fts_document_trigger() RETURNS TRIGGER AS $$&#xA;BEGIN&#xA;    NEW.fts_document=employee_fts_document(NEW);&#xA;    RETURN NEW;&#xA;END;&#xA;$$ LANGUAGE plpgsql;&#xA;```&#xA;&#xA;Finally, we tell PostgreSQL to execute the trigger on employee updates and inserts.&#xA;&#xA;```psql&#xA;CREATE TRIGGER employee_fts_update_trigger BEFORE UPDATE ON employee FOR EACH ROW EXECUTE PROCEDURE employee_fts_document_trigger();&#xA;CREATE TRIGGER employee_fts_insert_trigger BEFORE INSERT ON employee FOR EACH ROW EXECUTE PROCEDURE employee_fts_document_trigger();&#xA;```&#xA;&#xA;We&#39;re almost done. The *last* thing we might want to do is to tell PostgreSQL that we&#39;d like to index the `fts_document` column which contains our full-text search vectors. We&#39;re going to add a GIN (Generalized Inverted Index)-based index to our column. You can read about the GIN-based and GiST-based indices in the [PostgreSQL documentation](http://www.postgresql.org/docs/9.3/static/textsearch-indexes.html). Here&#39;s how we add our index.&#xA;&#xA;```psql&#xA;CREATE INDEX employee_fts_index ON employee USING gin(fts_document);&#xA;```&#xA;&#xA;That&#39;s it. Our employee table now has an always up-to-date search index that we can use. Before we continue, let&#39;s instantiate the search vectors. We&#39;ll then test everything out to make sure things are working a-ok.&#xA;&#xA;```psql&#xA;dan=# UPDATE employee SET fts_document=employee_fts_document(id);&#xA;dan=# SELECT COUNT(*) FROM employee WHERE fts_document @@ &#39;ron&#39;;&#xA; count&#xA;-------&#xA;     1&#xA;(1 row)&#xA;```&#xA;&#xA;Success!&#xA;&#xA;### South&#xA;&#xA;Now that we understand the internals, wouldn&#39;t it be nice if we could keep our databases up-to-date without having to manually copy and paste commands on our PostgreSQL shell? South to the rescue!&#xA;&#xA;We&#39;ll need to generate a blank migration.&#xA;&#xA;```bash&#xA;$ ./manage.py schemamigration app create_employee_fts&#xA;Created 0001_create_employee_fts.py. You must now edit this migration and add the code for each direction.&#xA;```&#xA;&#xA;Next, open up the migration, and copy in the SQL above in the `forwards` and `backwards` methods, as illustrated below.&#xA;&#xA;```python&#xA;# ... imports, etc ...&#xA;&#xA;from django.db import connection&#xA;&#xA;class Migration(SchemaMigration):&#xA;&#xA;    def forwards(self, orm):&#xA;        sql = &#34;&#34;&#34;&#xA;CREATE FUNCTION employee_fts_document(integer) RETURNS tsvector AS $$&#xA;DECLARE&#xA;    employee_document TEXT;&#xA;BEGIN&#xA;    SELECT concat_ws(&#39; &#39;, first_name, last_name, address_1, address_2, city, state, zip) INTO employee_document FROM employee WHERE id=$1;&#xA;    RETURN to_tsvector(&#39;pg_catalog.simple&#39;, employee_document);&#xA;END;&#xA;$$ LANGUAGE plpgsql;&#xA;&#xA;CREATE FUNCTION employee_fts_document_trigger() RETURNS TRIGGER AS $$&#xA;BEGIN&#xA;    NEW.fts_document=employee_fts_document(NEW.id);&#xA;    RETURN NEW;&#xA;END;&#xA;$$ LANGUAGE plpgsql;&#xA;&#34;&#34;&#34;&#xA;&#xA;        cursor = connection.cursor()&#xA;        cursor.execute(sql);&#xA;        cursor.execute(&#34;ALTER TABLE employee ADD COLUMN fts_document tsvector&#34;)&#xA;        cursor.execute(&#34;UPDATE employee SET fts_document=employee_fts_document(id)&#34;);&#xA;        cursor.execute(&#34;CREATE TRIGGER employee_fts_update_trigger BEFORE UPDATE ON employee FOR EACH ROW EXECUTE PROCEDURE employee_fts_document_trigger()&#34;)&#xA;        cursor.execute(&#34;CREATE TRIGGER employee_fts_insert_trigger BEFORE INSERT ON employee FOR EACH ROW EXECUTE PROCEDURE employee_fts_document_trigger()&#34;)&#xA;        cursor.execute(&#34;CREATE INDEX employee_fts_index ON employee USING gin(fts_document)&#34;)&#xA;&#xA;    def backwards(self, orm):&#xA;        cursor = connection.cursor()&#xA;        cursor.execute(&#34;DROP INDEX employee_fts_index&#34;)&#xA;        cursor.execute(&#34;ALTER TABLE employee DROP COLUMN fts_document&#34;)&#xA;        cursor.execute(&#34;DROP TRIGGER employee_fts_update_trigger ON employee&#34;)&#xA;        cursor.execute(&#34;DROP TRIGGER employee_fts_insert_trigger ON employee&#34;)&#xA;        cursor.execute(&#34;DROP FUNCTION employee_fts_document (integer)&#34;)&#xA;        cursor.execute(&#34;DROP FUNCTION employee_fts_document_trigger ()&#34;)&#xA;&#xA;    models = {&#xA;        &#34;&#34;&#34;&#xA;        App-specific model definitions here...&#xA;        &#34;&#34;&#34;&#xA;    }&#xA;```&#xA;&#xA;Now, just run this migration...&#xA;&#xA;```bash&#xA;$ ./manage.py migrate app&#xA;```&#xA;&#xA;...and watch as your FTS column, indices, and triggers are automagically created.&#xA;&#xA;### The Django Admin&#xA;&#xA;OK, so we have our FTS document created, have generated a migration to keep things in sync across machines, and are indexing everything we might possibly want to search on to find an employee. The next step is to use what we have and integrate it into the Django Admin. This assumes that you have an `EmployeeAdmin` object similar to the below:&#xA;&#xA;```python&#xA;class EmployeeAdmin(admin.ModelAdmin):&#xA;    search_fields = (&#34;first_name&#34;, &#34;last_name&#34;, &#34;address_1&#34;, &#34;address_2&#34;)&#xA;    # ... other admin configuration here ...&#xA;&#xA;admin.site.register(models.Employee, EmployeeAdmin)&#xA;```&#xA;&#xA;Our goal is to make searching with the Django Admin use our shiny new FTS index. To do this, we&#39;re going to need to override the `get_search_results` method in `EmployeeAdmin` to something like this:&#xA;&#xA;```python&#xA;class EmployeeAdmin(admin.ModelAdmin):&#xA;    # ...&#xA;&#xA;    def get_search_results(self, request, queryset, search_term):&#xA;        if search_term == &#39;&#39;:&#xA;            queryset, distinct = super(EmployeeAdmin, self).get_search_results(request, queryset, search_term)&#xA;        else:&#xA;            queryset, distinct = super(EmployeeAdmin, self) \&#xA;                    .get_search_results(request, queryset, None)&#xA;            queryset = queryset.extra(&#xA;                where=[&#34;employee.fts_document @@ to_tsquery(&#39;simple&#39;, %s)&#34;],&#xA;                params=[search_term]&#xA;            )&#xA;&#xA;        return queryset, distinct&#xA;```&#xA;&#xA;Looks close, but we&#39;re missing something. `to_tsquery` will expect a &#34;properly-formed&#34; query, and we can&#39;t always trust or depend on users of the admin to know PostgreSQL search syntax (don&#39;t blame them). We need to somehow sanitize the input before feeding it into PostgreSQL.&#xA;&#xA;Here are some examples of things that we might want to handle:&#xA;&#xA;* Strip padded whitespace.&#xA;* Remove duplicated whitespace.&#xA;* Add &amp;amp;&#39;s between words that are only separated by spaces.&#xA;* Replace double quotes (which PostgreSQL doesn&#39;t like) to single quotes.&#xA;* Clear up situations where search operators are used ambiguously (i.e., &#34;ron &amp;amp; \| swanson&#34;)&#xA;* Add a prefix wildcard to every search term, so that a search for &#34;swan&#34; will return &#34;Ron Swanson&#34; in the search results.&#xA;&#xA;Luckily for you, I&#39;ve already done all of the heavy lifting. Below is a function called `sanitize_search_term` which will take a string and form it into a proper PostgreSQL search query.&#xA;&#xA;```python&#xA;import re&#xA;import string&#xA;&#xA;def sanitize_search_term(term):&#xA;    # Replace all puncuation with spaces.&#xA;    allowed_punctuation = set([&#39;&amp;&#39;, &#39;|&#39;, &#39;&#34;&#39;, &#34;&#39;&#34;])&#xA;    all_punctuation = set(string.punctuation)&#xA;    punctuation = &#34;&#34;.join(all_punctuation - allowed_punctuation)&#xA;    term = re.sub(r&#34;[{}]+&#34;.format(re.escape(punctuation)), &#34; &#34;, \&#xA;            term)&#xA;&#xA;    # Substitute all double quotes to single quotes.&#xA;    term = term.replace(&#39;&#34;&#39;, &#34;&#39;&#34;)&#xA;    term = re.sub(r&#34;[&#39;]+&#34;, &#34;&#39;&#34;, term)&#xA;&#xA;    # Create regex to find strings within quotes.&#xA;    quoted_strings_re = re.compile(r&#34;(&#39;[^&#39;]*&#39;)&#34;)&#xA;    space_between_words_re = re.compile(r&#39;([^ &amp;|])[ ]+([^ &amp;|])&#39;)&#xA;    spaces_surrounding_letter_re = re.compile(r&#39;[ ]+([^ &amp;|])[ ]+&#39;)&#xA;    multiple_operator_re = re.compile(r&#34;[ &amp;]+(&amp;|\|)[ &amp;]+&#34;)&#xA;&#xA;    tokens = quoted_strings_re.split(term)&#xA;    processed_tokens = []&#xA;    for token in tokens:&#xA;        # Remove all surrounding whitespace.&#xA;        token = token.strip()&#xA;&#xA;        if token in [&#39;&#39;, &#34;&#39;&#34;]:&#xA;            continue&#xA;&#xA;        if token[0] != &#34;&#39;&#34;:&#xA;            # Surround single letters with &amp;&#39;s&#xA;            token = spaces_surrounding_letter_re.sub(r&#39; &amp; \1 &amp; &#39;, token)&#xA;&#xA;            # Specify &#39;&amp;&#39; between words that have neither | or &amp; specified.&#xA;            token = space_between_words_re.sub(r&#39;\1 &amp; \2&#39;, token)&#xA;&#xA;            # Add a prefix wildcard to every search term.&#xA;            token = re.sub(r&#39;([^ &amp;|]+)&#39;, r&#39;\1:*&#39;, token)&#xA;&#xA;        processed_tokens.append(token)&#xA;&#xA;    term = &#34; &amp; &#34;.join(processed_tokens)&#xA;&#xA;    # Replace ampersands or pipes surrounded by ampersands.&#xA;    term = multiple_operator_re.sub(r&#34; \1 &#34;, term)&#xA;&#xA;    # Escape single quotes&#xA;    return term.replace(&#34;&#39;&#34;, &#34;&#39;&#39;&#34;)&#xA;```&#xA;&#xA;Combining everything together, we get the following:&#xA;&#xA;```python&#xA;class EmployeeAdmin(admin.ModelAdmin):&#xA;    # ...&#xA;&#xA;    def get_search_results(self, request, queryset, search_term):&#xA;        if search_term == &#39;&#39;:&#xA;            queryset, distinct = super(EmployeeAdmin, self).get_search_results(request, queryset, search_term)&#xA;        else:&#xA;            search_term = sanitize_search_term(search_term)&#xA;            queryset, distinct = super(EmployeeAdmin, self) \&#xA;                    .get_search_results(request, queryset, None)&#xA;            queryset = queryset.extra(&#xA;                where=[&#34;employee.fts_document @@ to_tsquery(&#39;simple&#39;, %s)&#34;],&#xA;                params=[search_term]&#xA;            )&#xA;&#xA;        return queryset, distinct&#xA;```&#xA;&#xA;And...we&#39;re done! Searches in the admin will now use PostgreSQL FTS.&#xA;&#xA;Here&#39;s to speedy searching!&#xA;&#xA;&lt;hr/&gt;&#xA;&#xA;EDIT 12/15/2014: In a previous version of this post, `employee_fts_document` would pull stale values from the DB while generating the FTS document and performed an extraneous `SELECT`. Thanks, [Roger](http://dlo.me/archives/2014/09/01/postgresql-fts/#comment-1742372939)!&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2014/09/01/postgresql-fts/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Review: iPhone 5s Case</title>
    <updated>2013-11-08T00:00:00Z</updated>
    <id>tag:dlo.me,2013-11-08://archives/2013/11/08/review-iphone-5s-case/</id>
    <content type="html">I normally don’t write reviews, but I figured it’s never a bad time to&#xA;start.&#xA;&#xA;I’ll jump right into it.&#xA;&#xA;I have never before gotten a case for my iPhone. Cases add bulk, take&#xA;away from the aesthetic of the phone, and are just another “thing” to&#xA;keep track of. But when I heard about the [leather&#xA;case](http://store.apple.com/us/product/MF041LL/A/iphone-5s-case-brown)&#xA;that Apple created specifically for the iPhone 5s, I thought that it&#xA;would be the end of my no-case journey.&#xA;&#xA;It came a couple of days ago in the mail. I ordered from Best Buy&#xA;online. The cases come in 6 color choices—brown, beige, black, yellow,&#xA;blue, and red. I went with black, since it was the only one that could&#xA;really go with space gray. Not to mention, I’d read a bunch of reviews&#xA;that said the other colors (especially the brighter ones) were prone to&#xA;picking up dirt quickly.&#xA;&#xA;The case looks and feels great in the hand. The leather is no frills and&#xA;simple. It’s not the sort of leather you’d see on a cowboy boot, but&#xA;supposedly it’s real. There is a stamped Apple logo on the back. It&#xA;looks pretty cool, and gives it more authenticity, I guess.&#xA;&#xA;Putting the case on is quite easy. It really is fitted quite well to the&#xA;form of the phone. Sort of expected for a case, especially if you’re&#xA;also the manufacturer of the phone itself, but I suppose it’s still a&#xA;good thing.&#xA;&#xA;As for bulkiness, it didn’t add much, but it *was* enough to be&#xA;noticeable. In terms of weight and form factor, the iPhone 5s with the&#xA;leather case “felt” like my 4S in the hand. This isn’t necessarily a bad&#xA;thing (some people might like that), but it’s not totally for me.&#xA;&#xA;The biggest problems I have with the case are more functional and less&#xA;visual.&#xA;&#xA;1\. The leather cover over the power button is a bit too stiff. One&#xA;should not have to struggle when pressing the power button.  &#xA;2. The volume buttons are not covered like the power button is, but&#xA;they’re still quite hard to reach. It’s not impossible by any means, but&#xA;it’s just hard enough to be annoying.&#xA;&#xA;Given that I wasn’t blown away by the look and feel and the usability&#xA;problems that it has, I decided to return it. I think I’m still in the&#xA;market for a case, just not the Apple one.&#xA;</content>
    <link href="https://dlo.me//archives/2013/11/08/review-iphone-5s-case/" rel="alternate"></link>
  </entry>
  <entry>
    <title>So long, California, and thanks for all the fish</title>
    <updated>2013-07-08T00:00:00Z</updated>
    <id>tag:dlo.me,2013-07-08://archives/2013/07/08/so-long-california-thanks-for-all-the-fish/</id>
    <content type="html">&#xA;It&#39;s been almost four years since my wife and I made the decision to move to Los Angeles after college. We came here for a lot of reasons, but the biggest ones were family, the lively tech scene, and the practically perfect weather.&#xA;&#xA;All in all, I really do love this place.&#xA;&#xA;But after nearly four years of calling it home, the time has come to make a change.&#xA;&#xA;Real estate prices are through the roof and state income taxes are amongst the highest in the country. From a business perspective, franchise taxes (which you have to pay even if your business *loses* money) sting and processing times for even the most routine paperwork take months.&#xA;&#xA;Even outside of ritzy areas like Beverly Hills, houses advertised as &#34;teardowns&#34; by brokers are going for prices in the neighborhood of $1.2MM and up. Homes at every price range are getting multiple all-cash offers within days of going on the market.&#xA;&#xA;Renters are fairly shielded from this reality, but if you&#39;re thinking about becoming a homeowner here, it can be a struggle.&#xA;&#xA;We decided that we didn&#39;t want to struggle. And why should we? More so than ever before, where you live matters less than what you can do. And that&#39;s an amazing thing.&#xA;&#xA;So, where to? The answer was clear to both my wife and I from the beginning: Austin, Texas.&#xA;&#xA;Austin boasts no state income tax, real estate a young couple can actually afford, an exploding tech scene, and beautiful landscapes. Throw in great food, music, and culture, with a taste of &lt;a href=&#34;https://fiber.google.com/cities/austin/&#34;&gt;Fiber Internet&lt;/a&gt;, and you&#39;ve essentially described my perfect city.&#xA;&#xA;As a part of my move, my company, [Aurora](http://aurora.io), has become [Lionheart Software](http://lionheartsw.com), a Texas LLC. I&#39;ll still be doing everything I&#39;m doing now: helping small teams build great software. And there&#39;s a good chance [Pushpin](http://lionheartsw.com/software/pushpin/) will be getting some company.&#xA;&#xA;I&#39;ll be moving out later this month and am excited beyond words. If you&#39;re an Austinite, send me an [email](mailto:dan@dlo.me). I&#39;d love to meet up.&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2013/07/08/so-long-california-thanks-for-all-the-fish/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Speeding up your site with ngx_pagespeed and Amazon CloudFront</title>
    <updated>2013-05-14T00:00:00Z</updated>
    <id>tag:dlo.me,2013-05-14://archives/2013/05/14/nginx-pagespeed-cloudfront/</id>
    <content type="html">A couple weeks back, Google released ngx_pagespeed, an nginx module that is capable of doing a lot of cool stuff behind the scenes to speed up your webpages.&#xA;&#xA;&gt; ngx_pagespeed speeds up your site and reduces page load time. This&#xA;&gt; open-source nginx server module automatically applies web performance&#xA;&gt; best practices to pages, and associated assets (CSS, JavaScript,&#xA;&gt; images) without requiring that you modify your existing content or&#xA;&gt; workflow.&#xA;&#xA;This article will walk you through how to get up and running with ngx_pagespeed and how to serve its dynamically generated pipelined content through a CDN such as Amazon CloudFront.&#xA;&#xA;### Getting Started&#xA;&#xA;To install ngx_pagespeed, we’re going to need to compile nginx from source. Fret not, it’s actually not too bad.&#xA;&#xA;Let’s get started by logging into our server and installing the dependencies. On Ubuntu / Debian, run:&#xA;&#xA;```bash&#xA;sudo apt-get update&#xA;sudo apt-get install build-essential zlib1g-dev libpcre3 libpcre3-dev libssl-dev&#xA;```&#xA;&#xA;For RedHat, CentOS, and Fedora, run:&#xA;&#xA;```bash&#xA;sudo yum install gcc-c++ pcre-dev pcre-devel zlib-devel make&#xA;```&#xA;&#xA;Our next step is to download the [nginx](http://nginx.org/en/download.html) and [ngx_pagespeed](https://github.com/pagespeed/ngx_pagespeed) sources and install them.&#xA;&#xA;```bash&#xA;cd $HOME&#xA;wget http://nginx.org/download/nginx-1.4.1.tar.gz&#xA;wget http://github.com/pagespeed/ngx_pagespeed/archive/release-1.5.27.2-beta.tar.gz&#xA;tar xzfv nginx-1.4.1.tar.gz&#xA;tar zxfv release-1.5.27.2-beta.tar.gz&#xA;cd ngx_pagespeed-release-1.5.27.2-beta/&#xA;&#xA;# psol -&gt; PageSpeed Optimization Libraries&#xA;wget https://dl.google.com/dl/page-speed/psol/1.5.27.2.tar.gz&#xA;&#xA;tar xzvf 1.5.27.2.tar.gz&#xA;cd $HOME/nginx-1.4.1&#xA;&#xA;# Configure nginx, specify SSL support, and add the ngx_pagespeed module&#xA;./configure --add-module=$HOME/ngx_pagespeed-release-1.5.27.2-beta --with-http_ssl_module&#xA;make&#xA;sudo make install&#xA;```&#xA;&#xA;By default, nginx doesn’t bundle in SSL support, but I’m adding it in here because practically every web application I’ve built over the last couple of years has had to be served securely (this is also why we installed libssl-dev from apt-get earlier).&#xA;&#xA;After nginx compiles successfully (which should take a couple of minutes), you’ll have a freshly-cooked nginx binary in /usr/local/nginx/sbin. You can also change the binary location by explicitly specifying `—prefix=` to the `./configure` command.&#xA;&#xA;Let’s double check that everything’s working correctly. Run this…&#xA;&#xA;```python&#xA;sudo /usr/local/nginx/sbin/nginx&#xA;```&#xA;&#xA;…and your terminal should output something like this:&#xA;&#xA;```&#xA;[0508/234323:INFO:google_message_handler.cc(33)] Shutting down ngx_pagespeed root&#xA;```&#xA;&#xA;If this is what you see, ngx_pagespeed is good to go!&#xA;&#xA;## Configuring Amazon CloudFront&#xA;&#xA;You can skip this step if you don’t want or need to serve from a CDN. Also, these instructions are explicitly for Amazon CloudFront, but the process for other services should be similar.&#xA;&#xA;Sign into your Amazon AWS account and go to the CloudFront console. Create a download distribution…&#xA;&#xA;![](/images/pagespeed/amazon-2.png)&#xA;&#xA;…set the origin domain name to the domain serving your static assets…&#xA;&#xA;![](/images/pagespeed/amazon-3.png)&#xA;&#xA;…and create the distribution. This finalization process can take up to 15-30 minutes, so just give it some time.&#xA;&#xA;## Configuring PageSpeed&#xA;&#xA;To make it easier to test things out, I’ve written a simple HTML page with some CSS files to play around with if you don’t already have something handy ([link](https://gist.github.com/dlo/5544622/download)). Unzip these files to the /usr/local/nginx/html directory.&#xA;&#xA;Let’s open up our nginx configuration file (by default, it should be located at /usr/local/nginx/conf/nginx.conf) and clean things up. After removing commented and unnecessary lines, it should look something like the following:&#xA;&#xA;```nginx&#xA;worker_processes 1;&#xA;&#xA;events {&#xA;    worker_connections 1024;&#xA;}&#xA;&#xA;http {&#xA;    include mime.types;&#xA;    default_type application/octet-stream;&#xA;    sendfile on;&#xA;    keepalive_timeout 65;&#xA;&#xA;    server {&#xA;        listen 80;&#xA;        server_name example.com;&#xA;&#xA;        location / {&#xA;            root html;&#xA;            index index.html index.htm;&#xA;        }&#xA;    }&#xA;}&#xA;```&#xA;&#xA;Start nginx if it’s not already running and hit your site. With the example page I’ve included above, the source of the page should look something like this:&#xA;&#xA;```html&#xA;&lt;!doctype &gt;&#xA;&#xA;&lt;html&gt;&#xA;    &lt;head&gt;&#xA;        &lt;title&gt;A Simple Page&lt;/title&gt;&#xA;    &lt;/head&gt;&#xA;&#xA;    &lt;body&gt;&#xA;        Will you remove my whitespace?&#xA;    &lt;/body&gt;&#xA;&lt;/html&gt;&#xA;```&#xA;&#xA;Ok, this is what we expected and is sort of boring. Let’s make it&#xA;exciting and turn on PageSpeed.&#xA;&#xA;After the `server_name` directive, add the following lines.&#xA;&#xA;```nginx&#xA;pagespeed on;&#xA;```&#xA;&#xA;This is just one of many filters available. I’ve added `collapse_whitespace` because it’s easy to verify that it’s working.&#xA;&#xA;```nginx&#xA;pagespeed EnableFilters collapse_whitespace;&#xA;pagespeed FileCachePath /var/ngx_pagespeed_cache;&#xA;pagespeed Domain http://example.com;&#xA;```&#xA;&#xA;(optional) Specify the domain where static media is being served&#xA;&#xA;```nginx&#xA;pagespeed Domain http://static.example.com;&#xA;```&#xA;&#xA;(optional) If possible, have PageSpeed load static files from the filesystem (instead of hitting the server)&#xA;&#xA;```nginx&#xA;pagespeed LoadFromFile http://static.example.com/ /var/www/static/;&#xA;```&#xA;&#xA;Change “example.com” and “static.example.com” to the domains where you’re serving your pages and your static assets, respectively. They can also be the same.&#xA;&#xA;If you’d also like to serve the assets ngx_pagespeed generates through your CDN, add a line with the “MapRewriteDomain” directive. This directive tells ngx_pagespeed to replace the domain serving any static assets with your CDN’s domain. Here’s what it might look like if you’re using CloudFront:&#xA;&#xA;```nginx&#xA;pagespeed MapRewriteDomain dao9e9afbfaaa5.cloudfront.net&#xA;static.example.com;&#xA;```&#xA;&#xA;Finally, we have to create ngx_pagespeed’s cache directory and give&#xA;nginx ownership over it.&#xA;&#xA;```bash&#xA;sudo mkdir -p /var/ngx_pagespeed_cache&#xA;sudo chown -R www-data:www-data /var/ngx_pagespeed_cache&#xA;```&#xA;&#xA;Time to reload nginx and test it all out.&#xA;&#xA;```bash&#xA;$ sudo /usr/local/nginx/sbin/nginx -s reload&#xA;Setting option from (&#34;on&#34;)&#xA;Setting option from (&#34;FileCachePath&#34;, &#34;/var/ngx_pagespeed_cache&#34;)&#xA;Setting option from (&#34;LoadFromFile&#34;, &#34;http://static.example.com/&#34;, &#34;/usr/local/nginx/html/&#34;)&#xA;Setting option from (&#34;MapRewriteDomain&#34;, &#34;dao9e9afbfaaa5.cloudfront.net&#34;, &#34;static.example.com&#34;)&#xA;Setting option from (&#34;EnableFilters&#34;, &#34;collapse_whitespace,inline_javascript&#34;)&#xA;```&#xA;&#xA;Once we hit our page again and view source, we can now see that ngx_pagespeed has combined our two stylesheets and removed all unnecessary whitespace, in addition to attaching some Javascript instrumentation snippets that helps ngx_pagespeed measure how long the page took to load and render (see the [instrumentation docs](https://developers.google.com/speed/docs/mod_pagespeed/filter-instrumentation-add) for more info). Awesome stuff.&#xA;&#xA;```html&#xA;&lt;!doctype&gt;&#xA;&#xA;&lt;html&gt;&#xA;&#xA;&lt;head&gt;&#xA;&#xA;&lt;title&gt;&#xA;&#xA;A Simple Page&#xA;&#xA;&lt;/title&gt;&#xA;&#xA;&lt;/head&gt;&#xA;&#xA;&lt;body&gt;&#xA;&#xA;Will&#xA;you&#xA;remove&#xA;my&#xA;whitespace?&#xA;&#xA;&lt;script type=&#34;text/javascript&#34;&gt;&#xA;&#xA;(function(){var&#xA;e=encodeURIComponent,f=window,g=document,h=&#34;documentElement&#34;,k=&#34;length&#34;,l=&#34;prototype&#34;,n=&#34;body&#34;,p=&#34;&amp;ci=&#34;,q=&#34;&amp;oh=&#34;,r=&#34;,&#34;,s=&#34;Content-Type&#34;,t=&#34;Microsoft.XMLHTTP&#34;,u=&#34;Msxml2.XMLHTTP&#34;,v=&#34;POST&#34;,w=&#34;application/x-www-form-urlencoded&#34;,x=&#34;img&#34;,y=&#34;input&#34;,z=&#34;load&#34;,A=&#34;on&#34;,B=&#34;pagespeed_url_hash&#34;,C=&#34;url=&#34;;f.pagespeed=f.pagespeed\|\|{};var&#xA;D=f.pagespeed,E=function(a,b,c){this.e=a;this.c=b;this.d=c;this.b=this.f();this.a={}};E\[l\].f=function(){return{height:f.innerHeight\|\|g\[h\].clientHeight\|\|g\[n\].clientHeight,width:f.innerWidth\|\|g\[h\].clientWidth\|\|g\[n\].clientWidth}};E\[l\].i=function(a){a=a.getBoundingClientRect();return{top:a.top*.scrollTop),left:a.left*(void&#xA;0!==f.pageXOffset?f.pageXOffset:(g\[h\]\|\|g\[n\].parentNode\|\|g\[n\]).scrollLeft)}};E\[l\].g=function(a){if(0\&gt;=a.offsetWidth&amp;&amp;0\&gt;=a.offsetHeight)return!1;a=this.i(a);var&#xA;b=JSON.stringify(a);if(this.a.hasOwnProperty(b))return![](1;this.a[b]=)0;return&#xA;a.top\&lt;=this.b.height&amp;&amp;a.left\&lt;=this.b.width};E\[l\].h=function(a){var&#xA;b;if(f.XMLHttpRequest)b=new XMLHttpRequest;else&#xA;if(f.ActiveXObject)try{b=new ActiveXObject(u)}catch©{try{b=new&#xA;ActiveXObject(t)}catch(d){}}if(![](b)return)1;b.open(v,this.e);b.setRequestHeader(s,w);b.send(a);return!0};E\[l\].k=function(){for(var&#xA;a=\[x,y\],b={},c=0;c\&lt;a\[k\];+*c)for(var&#xA;d=g.getElementsByTagName(a\[c\]),m=0;m\&lt;d\[k\];m)d\[m\].hasAttribute(B)&amp;&amp;(d\[m\].getBoundingClientRect&amp;&amp;this.g(d\[m\]))&amp;&amp;(b\[d\[m\].getAttribute(B)\]=!0);b=Object.keys(b);if(0!=b\[k\]){a=C+e(this.c);a*=q+this.d;a*p+e(b\[0\]);for(c=1;c\&lt;b\[k\];*+c){d=r+e(b\[c\]);if(131072\&lt;a\[k\]*d\[k\])break;a*=d}D.criticalImagesBeaconData=a;this.h(a)}};D.j=function(a,b,c){if(a.addEventListener)a.addEventListener(b,c,!1);else&#xA;if(a.attachEvent)a.attachEvent(A+b,c);else{var&#xA;d=a\[A+b\];a\[A+b\]=function(){c.call(this);d&amp;&amp;d.call(this)}}};D.l=function(a,b,c){var&#xA;d=new&#xA;E (a,b,c);D.j(f,z,function(){f.setTimeout(function(){d.k()},0)})};D.criticalImagesBeaconInit=D.l;})();pagespeed.criticalImagesBeaconInit(&#39;/ngx_pagespeed_beacon&#39;,&#39;http://example.com/&#39;,&#39;r20W6lmsPP&#39;);&#xA;&lt;/body&gt;&#xA;&#xA;&lt;/html&gt;&#xA;```&#xA;&#xA;### Conclusion&#xA;&#xA;From here, you can do basically anything you want with PageSpeed. I’ve only scratched the surface of what it’s capable of, so you’ll want to check out the [extensive documentation](https://developers.google.com/speed/pagespeed/mod) to read about everything else it can do.&#xA;&#xA;From here, you can do basically anything you want with PageSpeed. I’ve only scratched the surface of what it’s capable of, so you’ll want to check out the [extensive documentation](https://developers.google.com/speed/pagespeed/mod) to read about everything else it can do.&#xA;</content>
    <link href="https://dlo.me//archives/2013/05/14/nginx-pagespeed-cloudfront/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Mike Krieger on Startup Tech</title>
    <updated>2013-04-26T00:00:00Z</updated>
    <id>tag:dlo.me,2013-04-26://archives/2013/04/26/mike-krieger-on-startup-tech/</id>
    <content type="html">Mike Krieger (Instagram cofounder) wrote an article today on&#xA;[Opbeat](http://blog.opbeat.com/2013/04/26/picking-tech-for-your-startup/)&#xA;regarding the technology stack tech entrepreneurs should use for their&#xA;startups. His advice could not ring more true to me (especially in light&#xA;of my [previous&#xA;post](/archives/2013/04/16/there-is-no-right-way-to-develop-software/)).&#xA;&#xA;&gt; Ultimately, the most important thing to keep in mind is that your&#xA;&gt; “stack” shouldn’t define you; while some engineers are attracted to&#xA;&gt; companies working with cutting-edge tech, that shouldn’t be the&#xA;&gt; driving force behind your early technology decisions. Instead,&#xA;&gt; establishing a solid base with mature data storage, and augmenting&#xA;&gt; more exciting recent projects where they can add value further up the&#xA;&gt; stack will let your team stay focused on solving the challenges you’ve&#xA;&gt; set out to solve.&#xA;&#xA;In short, use the right tool for the job, even if it isn’t sexy.&#xA;</content>
    <link href="https://dlo.me//archives/2013/04/26/mike-krieger-on-startup-tech/" rel="alternate"></link>
  </entry>
  <entry>
    <title>There is No Right Way to Develop Software</title>
    <updated>2013-04-16T00:00:00Z</updated>
    <id>tag:dlo.me,2013-04-16://archives/2013/04/16/there-is-no-right-way-to-develop-software/</id>
    <content type="html">TDD is the only way to write bug-free code. Pair programming is the only&#xA;way to work. We’re successful because we only hire remote workers. Blah&#xA;blah blah blah blah.&#xA;&#xA;I see shit like this every single day.&#xA;&#xA;Little tidbits like this are so mesmerizing because they fool us into&#xA;thinking that they’re the only thing separating us from that giant pot&#xA;of gold at the end of a ridiculous awesome double rainbow. And,&#xA;ultimately, that’s what the people writing this sort of thing would like&#xA;you to think.&#xA;&#xA;If you were to take a random sampling of 100 software engineers and&#xA;asked them “what is the first thing you do before starting a new&#xA;software project”, I bet you’d get 100 different answers.&#xA;&#xA;So then why do so many people seem to think that their way is the only&#xA;way?&#xA;&#xA;The answer I think is both really simple and really complicated. Simple,&#xA;because some people just like to talk. Complicated, because the software&#xA;engineering industry is actually a collection of subcultures that&#xA;face-off constantly against each other to defend their job security.&#xA;&#xA;This all started yesterday after I read [an&#xA;article](http://sturgill.github.io/2013/04/15/tests-are-overhyped/) by&#xA;Chris Sturgill about tests and how they’re overhyped. Everything written&#xA;in the article was totally reasonable.&#xA;&#xA;But the comments on the article painted a totally different story. If&#xA;you were only to have read through the comments, you’d think the Chris&#xA;was on crack or something. How dare he question TDD? How dare he even&#xA;think to question the validity of our test-driven culture?&#xA;&#xA;So after seeing some of these comments I felt compelled to write what&#xA;I’m writing right now. I really don’t want this article to have to do&#xA;with testing at all. This is just about being a good software developer.&#xA;&#xA;Let’s cut to the chase.&#xA;&#xA;There is no “right” way to develop software. I repeat: there is no right&#xA;way to develop software. Some people in our industry like to cargo cult&#xA;and don’t want to believe this. They believe that the sweet new hotness&#xA;they learned a couple of hours ago is the only way to make things work&#xA;and build reliable pieces of engineering.&#xA;&#xA;I’m sorry to break it to you. The new hotness is probably not new. It’s&#xA;probably just new marketing on old hotness. At some point you’ve gotta&#xA;learn to stop dropping everything you’re doing and changing everything&#xA;and then bragging to the world about how your new way is the “only” way.&#xA;It’s not. Stop.&#xA;&#xA;Being a good developer means compromise. It means sometimes doing things&#xA;one way for one project, and another way for another. It means balancing&#xA;the needs of your stakeholders with your ideals. Sometimes they’re not&#xA;always going to match up, but that doesn’t mean you should stomp the&#xA;ground with your feet and have a tantrum when things don’t go your way.&#xA;&#xA;[HN discussion](https://news.ycombinator.com/item?id=5562791)&#xA;</content>
    <link href="https://dlo.me//archives/2013/04/16/there-is-no-right-way-to-develop-software/" rel="alternate"></link>
  </entry>
  <entry>
    <title>How To Write The Perfect Bug Report</title>
    <updated>2013-02-11T00:00:00Z</updated>
    <id>tag:dlo.me,2013-02-11://archives/2013/02/11/the-perfect-bug-report/</id>
    <content type="html">Like many other developers, the people who I interact with most often in&#xA;my codebases and issue trackers are not other software engineers—they’re&#xA;my clients.&#xA;&#xA;My partners have generally never been involved in the development of any&#xA;software project at any point in their lives prior to working with me.&#xA;This is not to say that they aren’t technically savvy—on the contrary,&#xA;they usually are. However, there are a certain number of things about&#xA;bug reporting that aren’t obvious to someone who hasn’t been doing it&#xA;for years.&#xA;&#xA;So, I thought about it, and instead of having to give a mini-overview&#xA;every time I want to bring this up, I thought it might be nice to have&#xA;something to refer to that explains what you should do when you&#xA;encounter an issue. Big or small, enterprise or start-up, everything&#xA;applies.&#xA;&#xA;### The Basics&#xA;&#xA;Issue trackers vary widely in terms of features from one to another.&#xA;Whether it’s a simple list of items on an Google Docs spreadsheet or a&#xA;custom JIRA installation with multiple plugins and tens of fields,&#xA;everything I’ll write in this post applies. No matter what, there are&#xA;certain fields that your issue tracker should ALWAYS account for. They&#xA;are:&#xA;&#xA;&lt;ul class=&#34;styled&#34;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Assignee&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Title&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Description&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Milestone&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Labels / Tags / Categories (optional but recommended)&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;/ul&gt;&#xA;&#xA;If your issue tracker doesn’t support the first four, find another one.&#xA;Labels are good but not necessary.&#xA;&#xA;Now that we’ve got a bug tracker, let’s start off with a fairly common&#xA;scenario.&#xA;&#xA;### I Can’t Login! @#\$%!&#xA;&#xA;You woke up this morning to a nice cup of coffee, opened up your&#xA;application to get work done…and you can’t login.&#xA;&#xA;First thing, DON’T PANIC. It’s easy to feel helpless if you’re not the&#xA;developer, since you may think there’s not much you can do.&#xA;&#xA;Wrong! Your role is super important. You speak for the users. The better&#xA;you do this, the quicker the issue will get fixed and the happier&#xA;everyone will be. So if you find that you’re stressed, just take a deep&#xA;breath.&#xA;&#xA;We want to make sure that everything we do from this point forward is&#xA;clearly communicated, straightforward, and easy for the assignee to work&#xA;with.&#xA;&#xA;Let’s handle the low-hanging fruit, shall we? This means verifying that&#xA;you actually have an account and then checking that you’re actually&#xA;putting in the right password. This may sound dumb, but just double&#xA;check. **Always** double check.&#xA;&#xA;After you’ve made sure that you didn’t make a careless mistake, try to&#xA;make whatever broke break again. Depending on the issue, this process&#xA;can take a fair amount of patience and/or creativity. Software can be&#xA;very picky, so make sure to be precise. If you encountered the bug when&#xA;writing your login credentials in ALL CAPS, try to reproduce it with all&#xA;caps. Then, try with lowercase letters. Still working? Now try with a&#xA;mix of uppercase and lowercase. Then try with only spaces. Do whatever&#xA;you need to do to corner it!&#xA;&#xA;After much experimenting, you’ve found that no user can login, even with&#xA;the correct username and password, and no error message is displayed.&#xA;&#xA;Whatever is happening isn’t intended behavior, so it’s confirmed: you’ve&#xA;found a bug.&#xA;&#xA;### The Issue Title&#xA;&#xA;The issue title comes first for a couple of reasons.&#xA;&#xA;&lt;ol class=&#39;styled&#39;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;It’s the first thing other team members will see.&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;A clear issue title helps you decide out who to assign it to.&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Writing the title forces you to describe the problem clearly and&#xA;succintly.&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;/ol&gt;&#xA;&#xA;Before going into details, I’m just going to start off with some&#xA;anti-examples. The following are *bad* issue titles:&#xA;&#xA;&lt;ul class=&#39;styled&#39;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;login&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;login problem&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;login form not working&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;can’t login&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;login page&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;SITE IS BROKEN&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;/ul&gt;&#xA;&#xA;I don’t like these issue titles because they are vague and don’t help&#xA;the assignee understand what is actually wrong. The best issue titles&#xA;are specific and to-the-point. Brevity is *always* appreciated, but the&#xA;sweet spot for length is probably around 6-10 words.&#xA;&#xA;Your issue title should briefly identify two things:&#xA;&#xA;&lt;ol class=&#34;styled&#34;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;The undesired outcome&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;When or how it happens&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;/ol&gt;&#xA;&#xA;With all that in mind, here’s the issue title I would write:&#xA;&#xA;&gt; Login fails silently when user enters correct credentials&#xA;&#xA;Onwards!&#xA;&#xA;### The Assignee&#xA;&#xA;Once we have a title, you should have a good idea for who needs to work&#xA;on this. There are generally two scenarios here.&#xA;&#xA;1\. You know who does what.  &#xA;2. You know who knows who does what.&#xA;&#xA;If you know who is responsible for whatever feature or bug you’ve just&#xA;encountered, make them the assignee. If you don’t know who is&#xA;responsible—even if there is the slightest doubt, assign the issue to&#xA;the person who will know who to assign it to.&#xA;&#xA;### The Description&#xA;&#xA;As with a good title, a description should be short and sweet, but also&#xA;a bit more detailed. There’s actually a straightforward recipe.&#xA;&#xA;&lt;ul class=&#34;styled&#34;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Write in sentence or two what you tried to do and what should’ve&#xA;happened.&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;If the bug seems really specific, give steps on how to reproduce it.&#xA;Note: if *all* logins fail (and not just yours) and there’s not really a&#xA;pattern, you can skip this step.&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;Attach screenshots if applicable.&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;/ul&gt;&#xA;&#xA;Given the above, here’s what I might write as the description for the&#xA;login issue:&#xA;&#xA;&gt; I just tried to log into the site from the login form on&#xA;&gt; /accounts/login and nothing appeared to happen, even when using my&#xA;&gt; correct username and password.&#xA;&#xA;If there was some sort of pattern, or only certain credentials fail,&#xA;this is what I might write:&#xA;&#xA;&gt; I just tried to log into the site from the login form on&#xA;&gt; /accounts/login and nothing appeared to happen, even when using my&#xA;&gt; correct username and password. More specifically, it seems that&#xA;&gt; passwords with weird characters cause the issue (my password has a&#xA;&gt; space).  &#xA;&gt;    &#xA;&gt; 1. Use credential with space in password.  &#xA;&gt; 2. Fill in form with correct credentials.  &#xA;&gt; 3. Submit form and notice that the user is not logged in and no error&#xA;&gt; message is shown.&#xA;&#xA;We’re almost done! On to milestones.&#xA;&#xA;### Milestones&#xA;&#xA;Milestones are like goals. Trying to ship software without goals just&#xA;isn’t going to happen. And if it miraculously does, it’ll take way more&#xA;time than you originally planned, and will probably have costed way more&#xA;money and energy than you had the budget for. This is why you need&#xA;milestones, and why issues need to be placed into them.&#xA;&#xA;Milestones can be numbered, given code names, be descriptive, or&#xA;anything—it really doesn’t matter. All that matters is that it’s clear&#xA;what is high priority (due sooner) and what’s “low” priority (due&#xA;later). I put low in quotes because, in my opinion, everything is high&#xA;priority. It’s just a matter of *how* high.&#xA;&#xA;So file the issue into the appropriate milestone.&#xA;&#xA;Personally, I think users not being able to log in is a pretty big deal,&#xA;so I would file this under the “STOP WHAT YOU’RE DOING AND DO THIS NOW”&#xA;milestone.&#xA;&#xA;### Labels / Tags / Categories&#xA;&#xA;Every bug tracker gives this a different name, but I’m going to call&#xA;them labels for the sake of simplicity.&#xA;&#xA;I have a love-hate relationship with this one. I think labels have a&#xA;tendency to be overused, and are mostly just there to be pretty, but I&#xA;think there’s something to be said for being able to categorize and&#xA;group issues by similarity.&#xA;&#xA;In my projects, I use six labels[^1].&#xA;&#xA;&lt;ul class=&#34;styled&#34;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;question: for features we need to discuss or questions&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;enhancement: improvements, can be design, backend, frontend, anything&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;bug: something isn’t working the way it should&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;tweak: changes to existing features or designs&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;needs more info: not enough information in the issue to make a next step&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;duplicate: issue was already reported&#xA;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;/ul&gt;&#xA;&#xA;Some bug trackers will also have an “invalid” label. I think this is a&#xA;bad label, because it makes the reporter feel stupid if it’s ever used.&#xA;This is bad karma and throws a negative wrench in the relationship&#xA;between everyone involved. Just ask for closer instructions to&#xA;reproduce, and tag “needs more info”. If the reporter can’t do it, close&#xA;it and say it couldn’t be reproduced. Yay, no feelings hurt.&#xA;&#xA;The issue we’re discussing (not being able to log in) should just be&#xA;labeled as “bug” and that’s that.&#xA;&#xA;### Conclusion&#xA;&#xA;There’s nothing else, really. I’m sure that there’s way more I could&#xA;write about this topic, but after reading this, you should feel&#xA;confident enough to write a bug report that is concise,&#xA;information-dense, and makes it as easy as possible for the responsible&#xA;person to resolve.&#xA;&#xA;And remember what I said about not panicking? Well, because you didn’t&#xA;panic, the bug report you wrote was easy to read and contained&#xA;straightforward instructions to reproduce. The developer was able to&#xA;pinpoint the problem in the login logic, run tests to verify he didn’t&#xA;break anything else, and push a fix to production in no more than 15&#xA;minutes.&#xA;&#xA;The most important thing? Everyone did their jobs with a smile on their&#xA;face. :)&#xA;&#xA;I hope this was helpful to you. Have a comment? I’d love to hear your&#xA;thoughts. Send me an [email](mailto:dan@dlo.me) or reach out via&#xA;[Twitter](https://twitter.com/dwlz).&#xA;&#xA;&lt;!-- You can also check out the comments on &#34;Hacker News&#34;:http://news.ycombinator.com/item?id=5205568. --&gt;&#xA;&#xA;[^1]: If you use GitHub and like these labels, you may be interested in&#xA;    the [script](https://gist.github.com/dlo/4760637) I wrote that&#xA;    replaces the default labels with these ones.&#xA;</content>
    <link href="https://dlo.me//archives/2013/02/11/the-perfect-bug-report/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Making Chrome Play Nicely with Compass &amp; Sass</title>
    <updated>2013-01-24T00:00:00Z</updated>
    <id>tag:dlo.me,2013-01-24://archives/2013/01/24/using-source-maps-debug-compass-sass/</id>
    <content type="html">I&#39;m a big fan of [Compass](http://compass-style.org/), the CSS framework that uses [Sass](http://sass-lang.com/) under the hood to make developing stylesheets not nearly as tedious as it might be otherwise. One thing that&#39;s always been a pain, however, is debugging SCSS while using the web inspector in Google Chrome.&#xA;&#xA;Here&#39;s the problem. Compass reads content from a certain file, and converts it into CSS, which the browser then interprets to style the page you&#39;re viewing. When I (the frontend developer) reload the styles for a page and want to see where a rendering mistake might have occurred, I will only see the line number from the _generated_ CSS file, and not the original SCSS file that I&#39;m actually editing. Here&#39;s an example of what you might see below.&#xA;&#xA;![](/images/source-maps/css-example.png)&#xA;&#xA;So, to find the source of a mistake, I have to open that CSS file, find that line number, go to the corresponding line in my SCSS file, and then repeat. Yuck!&#xA;&#xA;Now, here&#39;s the solution: [source maps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/).&#xA;&#xA;Source maps are a rad new feature that has landed (at least with experimental status) in Webkit. Instead of viewing &#34;main.css&#34; above, you would see the source file and the source line number, e.g. &#34;main.scss:352&#34;.&#xA;&#xA;By now you probably want to know how to set this up, so here are the distilled instructions for Chrome 24.&#xA;&#xA;### Enabling Source Maps in Google Chrome&#xA;&#xA;1. Navigate to chrome://flags and search for &#34;Enable Developer Tools experiments&#34;. Enable it and then restart Chrome.&#xA;&#xA;    ![](/images/source-maps/enable-experiments.png)&#xA;&#xA;2. Once Chrome is back up and running, open the inspector and click on the settings icon on the lower right.&#xA;&#xA;    ![](/images/source-maps/open-settings.png)&#xA;&#xA;3. Under the &#34;General&#34; tab, find &#34;Sources&#34; and check &#34;Enable source maps&#34;.&#xA;&#xA;    ![](/images/source-maps/general-tab.png)&#xA;&#xA;4. Under the &#34;Experiments&#34; tab, check &#34;Support for SASS&#34;.&#xA;&#xA;    ![](/images/source-maps/experiments-tab.png)&#xA;&#xA;Congrats, you&#39;ve come far. now Chrome is ready. The next thing you have to do is let Compass know to generate the requisite markup. In your Compass config file, add the following line:&#xA;&#xA;```ruby&#xA;sass_options = { :debug_info =&gt; true }&#xA;```&#xA;&#xA;Once you&#39;ve done this, start Compass (make sure to stop it first if you already have it running).&#xA;&#xA;...and you&#39;re done. You should now be able to open the web inspector for a specific element and view which SCSS file and line number the styles were generated from, à la below:&#xA;&#xA;![](/images/source-maps/scss-example.png)&#xA;&#xA;Enjoy!&#xA;</content>
    <link href="https://dlo.me//archives/2013/01/24/using-source-maps-debug-compass-sass/" rel="alternate"></link>
  </entry>
  <entry>
    <title>How To Serve Static Files in Django</title>
    <updated>2013-01-14T00:00:00Z</updated>
    <id>tag:dlo.me,2013-01-14://archives/2013/01/14/how-to-serve-static-files-django/</id>
    <content type="html">I&#39;ve been lamenting the state of static files in Django for a while now. The app has caused one problem after another for me. In one situation, after running \`./manage.py collectstatic\`, my entire development environment was wiped. In another, I found staticfiles was completely unreliable when syncing static media to remote storage backends, due to the way it calculates whether a file has changed (I posted this problem to the [Django developers mailing list](https://groups.google.com/d/msg/django-developers/vtMVq8jwnf8/xosdFRc_4vAJ) a couple months ago, but nothing came of it).&#xD;&#xA;&#xD;&#xA;After reading a [tweet](https://twitter.com/zedshaw/status/290997963851972608) that perfectly echoed my feelings on the subject, I decided it was time to write down how I solve the “staticfiles problem&#34; in my own apps, in a way that works flawlessly on both development and production.&#xD;&#xA;&#xD;&#xA;&lt;script&gt;&#xD;&#xA;$(function() {  &#xD;&#xA;$.getJSON(&#34;https://api.twitter.com/1/statuses/oembed.json?id=291031387912417282&amp;callback=?&#34;, function(data) {  &#xD;&#xA;$(&#39;div#tweet-1&#39;).html(data.html);  &#xD;&#xA;});  &#xD;&#xA;});&#xD;&#xA;&#xD;&#xA;&lt;/script&gt;&#xD;&#xA;&#xD;&#xA;&lt;div class=&#39;tweet&#39; id=&#39;tweet-1&#39;&gt;&lt;/div&gt;&#xD;&#xA;&#xD;&#xA;&lt;script&gt;&#xD;&#xA;$(function() {  &#xD;&#xA;$.getJSON(&#34;https://api.twitter.com/1/statuses/oembed.json?id=291022623398318080&amp;callback=?&#34;, function(data) {  &#xD;&#xA;$(&#39;div#tweet-2&#39;).html(data.html);  &#xD;&#xA;});  &#xD;&#xA;});&#xD;&#xA;&lt;/script&gt;&#xD;&#xA;&#xD;&#xA;&lt;div class=&#39;tweet&#39; id=&#39;tweet-2&#39;&gt;&lt;/div&gt;&#xD;&#xA;&#xD;&#xA;&lt;div class=&#39;clear&#39;&gt;&lt;/div&gt;&#xD;&#xA;&#xD;&#xA;After following this tutorial, you will be serving **all** static files on your development machine from a folder called “static&#34; in your Django project, and you will easily be able to sync static media to any storage backend quickly and easily.&#xD;&#xA;&#xD;&#xA;This tutorial is for a **base** Django installation. I generally use my own [project template](https://github.com/lionheart/django-template) that takes care of setting sane defaults for me (and in use in tens of production projects), but given the reality here, I know a lot of you reading this are probably reading this with legacy applications in mind. I hope these instructions work for you, but if they don&#39;t, please send me an email (first name at this domain).&#xD;&#xA;&#xD;&#xA;#### Serving Static Files on a Development Server&#xD;&#xA;&#xD;&#xA;To keep it simple, let&#39;s start a Django project using the standard template.&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;$ django-admin.py startproject statictest&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Switch into the folder and start a virtual environment.&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;$ virtualenv .&#xD;&#xA;New python executable in ./bin/python&#xD;&#xA;Installing distribute..........................................................................................................................................................................................................done.&#xD;&#xA;Installing pip................done.&#xD;&#xA;$ . bin/activate&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Set the variables in your \`settings.py\` file to the values below.&#xD;&#xA;&#xD;&#xA;```python&#xD;&#xA;# This import belongs at the top of your settings file&#xD;&#xA;import os&#xD;&#xA;&#xD;&#xA;BASE = os.path.abspath(os.path.dirname(__name__))&#xD;&#xA;&#xD;&#xA;STATICFILES_DIRS = (os.path.join(BASE, &#34;static&#34;),)&#xD;&#xA;ADMIN_MEDIA_PREFIX = &#39;/static/admin/&#39;&#xD;&#xA;STATIC_URL = &#34;/static/&#34;&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Note: in a real-life situation, you will want to split out your local and production settings files, and symlink them as necessary for deployment. The above is purely for illustration purposes.&#xD;&#xA;&#xD;&#xA;Here&#39;s the magic step. To get all files in your “static&#34; folder serving from the “/static/&#34; URL on the development server, add these lines to your \`urls.py\` file.&#xD;&#xA;&#xD;&#xA;```python&#xD;&#xA;from django.contrib.staticfiles.urls import staticfiles_urlpatterns&#xD;&#xA;&#xD;&#xA;# ...&#xD;&#xA;&#xD;&#xA;urlpatterns += staticfiles_urlpatterns()&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;To test it out, just create a folder called “static&#34; in the base of your project directory. If you did everything correctly to this point, you should see the following in your “statictest&#34; folder:&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;$ ls&#xD;&#xA;bin        include    lib        manage.py  static     statictest&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Start the development server, and everything in that folder will be accessible from “http://localhost:8000/static/&#34;. Congrats. You rock.&#xD;&#xA;&#xD;&#xA;#### Serving Static Files in Production&#xD;&#xA;&#xD;&#xA;Frankly, it&#39;s not super useful if all your static files just live locally. I tried to get staticfiles to play nicely with Amazon S3, but I just couldn&#39;t do it.&#xD;&#xA;&#xD;&#xA;Why? Well, firstly, \`./manage.py collectstatic\` will upload files that have already been uploaded if you use a modern DVCS and work with other developers (essentially, this means it will reupload files every time you pull down your code from the remote). It does this because it uses last modified times as the heuristic for deciding whether a file has changed. Unfortunately for every modern developer, we all know that Git and other tools set the last modified time of a time to whenever code was last pulled from the server, not when the code was actually last modified.&#xD;&#xA;&#xD;&#xA;Secondly, the django-storages implementation for retrieving last modified time from Amazon S3 requires sending an HTTP request to AWS for each file you&#39;re syncing. This, needless to say, is **extremely** slow.&#xD;&#xA;&#xD;&#xA;Because [my proposals](https://groups.google.com/d/msg/django-developers/vtMVq8jwnf8/xosdFRc_4vAJ) for getting these issues fixes in Django core were denied, I wrote a Django application called [statictastic](https://github.com/lionheart/django-statictastic). The app adds a new management command called \`syncmedia\` which, well, syncs your static media. It&#39;s fast and works on all storage backends built on the Django API.&#xD;&#xA;&#xD;&#xA;So, how is it so much faster than the collectstatic implementation? Well, it starts by creating a metadata file that contains the md5 checksums of the files that currently exist in your project. When you first run \`./manage.py syncmedia\`, statictastic creates this metadata file and uploads it to the remote backend. Then, every following time you run the command, statictastic compares your local md5 checksums to those on the remote backend. When statictastic notices that a checksum has changed, it re-uploads the outdated file and updates the remote metadata file as well.&#xD;&#xA;&#xD;&#xA;If you&#39;d like to use it, the first thing you&#39;ll want to do is install it via pip:&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;pip install boto&#xD;&#xA;pip install django-storages&#xD;&#xA;pip install django-statictastic==0.6.1&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Next up, in your settings.py:&#xD;&#xA;&#xD;&#xA;```python&#xD;&#xA;INSTALLED_APPS = (&#xD;&#xA;    # ...&#xD;&#xA;    statictastic,&#xD;&#xA;)&#xD;&#xA;&#xD;&#xA;DEFAULT_FILE_STORAGE = &#39;storages.backends.s3boto.S3BotoStorage&#39;&#xD;&#xA;STATICFILES_STORAGE = &#39;statictastic.backends.VersionedS3BotoStorage&#39;&#xD;&#xA;&#xD;&#xA;# Optionally change these to full CDN urls&#xD;&#xA;UPLOAD_URL = &#34;https://s3.amazonaws.com/uploads-statictest/&#34;&#xD;&#xA;STATIC_URL = &#34;https://s3.amazonaws.com/static-statictest/&#34;&#xD;&#xA;&#xD;&#xA;# I like to store my uploads and static media on different buckets.&#xD;&#xA;AWS_STATIC_STORAGE_BUCKET_NAME = &#39;static-statictest&#39;&#xD;&#xA;AWS_STORAGE_BUCKET_NAME = &#39;uploads-statictest&#39;&#xD;&#xA;&#xD;&#xA;# Access Keys, straight from Amazon&#xD;&#xA;AWS_ACCESS_KEY_ID = &#39;XXXXXXXXXXXXXXXXXXXX&#39;&#xD;&#xA;AWS_SECRET_ACCESS_KEY = &#39;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&#39;&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Now, go ahead and test it out.&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;$ python manage.py syncmedia&#xD;&#xA;Updated favicon.ico&#xD;&#xA;1 file updated&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Awesome. You&#39;re done. To reference static files in your HTML files, use the “static&#34; template tag.&#xD;&#xA;&#xD;&#xA;But wait! Not so fast. For some inexplicable reason, Django has two static template tags. The one that&#39;s in your templates by default isn&#39;t the one you want. The one you want you&#39;ll have to manually import.&#xD;&#xA;&#xD;&#xA;```django&#xD;&#xA;{% raw %}&#xD;&#xA;{% load static from staticfiles %}&#xD;&#xA;&lt;link rel=&#39;shortcut icon&#39; href=&#39;{% static &#34;favicon.ico&#34; %}&#39; /&gt;&#xD;&#xA;{% endraw %}&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;The above template code would generate the following HTML:&#xD;&#xA;&#xD;&#xA;```html&#xD;&#xA;&lt;link&#xD;&#xA;    rel=&#34;shortcut icon&#34;&#xD;&#xA;    href=&#34;https://s3.amazonaws.com/static-statictest/favicon.ico&#34;&#xD;&#xA;/&gt;&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;Because I like to break my caches, I added a optional setting for statictastic called \`COMMIT_SHA\` that adds a querystring to all static files referenced using the \`static\` template tag. If you set it to \`123456\` in your settings.py file, you&#39;d see this in your template:&#xD;&#xA;&#xD;&#xA;```&#xD;&#xA;&lt;link rel=&#39;shortcut icon&#39; href=&#39;https://s3.amazonaws.com/static-statictest/favicon.ico?123456&#39; /&gt;&#xD;&#xA;```&#xD;&#xA;&#xD;&#xA;And that concludes my overview of how I make static files work on Django. If you have any questions, ping me on Twitter or send me an email.&#xD;&#xA;&#xD;&#xA;All the code found in this post can be found in my company&#39;s Django template hosted [on GitHub](https://github.com/lionheart/django-template).&#xD;&#xA;</content>
    <link href="https://dlo.me//archives/2013/01/14/how-to-serve-static-files-django/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Belize</title>
    <updated>2013-01-07T00:00:00Z</updated>
    <id>tag:dlo.me,2013-01-07://archives/2013/01/07/belize/</id>
    <content type="html">I got back from Belize last Sunday, and I thought it might be a good&#xA;idea to write a post about it. Plus, I took a ton of pictures and it&#xA;would be a shame if no one else could see them.&#xA;&#xA;So, the family vacation to Belize started on December 23rd. We had an&#xA;early flight to Dallas-Forth Worth, a short layover, and then another&#xA;leg to Belize City. We flew American, and the experience was nothing&#xA;much to speak of. My wife had just been upgraded to Gold Status on&#xA;American, so we got “above average” treatment when checking in and&#xA;getting our tickets. Unfortunately, however, my wife also had a pretty&#xA;bad bout of sickness just hours before we had to leave on our flight.&#xA;We’re blaming it (sorta) on the Thai food we had just hours before, but&#xA;I had it as well, and no symptoms, so it’s still all a mystery.&#xA;&#xA;We tried to reschedule our flight to Belize at 3am in the morning before&#xA;we were set to leave, but outgoing flights to our destination were&#xA;packed to the brim. There was no way we would be able to make it to&#xA;Belize within the week if we didn’t leave with our original flight. We&#xA;rallied, made it to the airport with Rachel’s parents, and made it work.&#xA;&#xA;On the outgoing flight from LAX to DFW, we were lucky enough to have&#xA;been upgraded to First Class. We weren’t able to truly appreciate it (I&#xA;was too worried about Rachel to be relaxed), but for Rachel, it was&#xA;magnitudes better than having to sit in a normal coach seat while having&#xA;food poisoning.&#xA;&#xA;Once we made it to DFW, I loaded myself up with some Popeye’s chicken (I&#xA;think I have that right), and felt a little disgusting afterwards. No&#xA;worries, we’re going to Belize, and it’s a vacation!&#xA;&#xA;Our flight to Belize was fairly non-eventful, other than we made it to&#xA;Belize and it was a smooth ride. Rachel slept on my lap throughout the&#xA;flight, but sadly still hadn’t started to feel better.&#xA;&#xA;![/images/belize/airport.png](. &#34;/images/belize/airport.png&#34;)&#xA;&#xA;The view from the airport&#xA;&#xA;Once we landed in Belize City, our trek was to continue on the&#xA;unmaintained roads of the Belizian countryside, by way of van with&#xA;manual transmission. The ride was about an hour and a half from the&#xA;airport to our resort in the jungle. The most apparent thing while&#xA;driving around this country is that it is very poor. Half-finished&#xA;houses litter the roadside, broken down cars lie in almost every&#xA;driveway, and there is practically no commercial activity (didn’t see&#xA;one McDonald’s or Starbucks in the entire country).&#xA;&#xA;The only restaurants I saw off the side of the road were shut down, with&#xA;wooden planks covering the windows, or stands selling coke and other&#xA;soft drinks. The apparent owners sat outside on plastic chairs waiting&#xA;for a thirsty driver to come buy. There is also basically no commercial&#xA;printing. People just spray-painted the names of their establishments on&#xA;the walls, with letters of the same word painted in varying sizes.&#xA;&#xA;One notable thing, however, was the number of places specializing in&#xA;“tire repair”. Again, these were just people sitting in plastic chairs&#xA;on the grass with a hand-written sign. Sometimes the sign just pointed&#xA;down a dirt-filled driveway, the end of which there lay a pile of&#xA;tires—ostensibly for cars and vans of all sizes. Really, this is not so&#xA;much a surprise, as the roadways throughout the country look totally&#xA;unmaintained.&#xA;&#xA;Once we arrived at our resort, the Sleeping Giant Lodge, we were pretty&#xA;much spent. Rachel and I went straight to our room. We had purchased&#xA;some rice and a banana at DFW (it’s said that stuff is good for stomach&#xA;problems), and not long after settling in, we saw a cute little gecko on&#xA;our wall.&#xA;&#xA;![/images/belize/arrival_rainforest.png](. &#34;/images/belize/arrival_rainforest.png&#34;)&#xA;&#xA;Us getting out of the vans&#xA;&#xA;![/images/belize/room_rainforest.png](. &#34;/images/belize/room_rainforest.png&#34;)&#xA;&#xA;Our room&#xA;&#xA;I went off to dinner while Rachel stayed in bed reading her Kindle.&#xA;&#xA;Dinner was somewhat uneventful—I was mostly worrying about Rachel and&#xA;everyone else was deciding what adventure they were going to go on the&#xA;next day. I had pretty much decided that no matter what, Rachel and I&#xA;would stay in, despite how fun everything seemed to be.&#xA;&#xA;Two groups went out—one went horseback riding and the other went&#xA;spelunking / waterfall jumping. I was told this was stuff they would&#xA;never allow in the U.S., but everyone who went got amazing views of the&#xA;caves of Belize. I wish I could write more about them, but alas. We did&#xA;eat a fair amount at the lodge restaurant (there weren’t any other&#xA;choices, really).&#xA;&#xA;![/images/belize/rainforest_view2.png](. &#34;/images/belize/rainforest_view2.png&#34;)&#xA;&#xA;View from the restaurant&#xA;&#xA;The next couple days went by quickly. A lot of staying indoors and&#xA;reading, relaxing, and having quiet lunches while sipping fresh piña&#xA;coladas. One thing to note was that the service at this place was just&#xA;fantastic. Everyone took the effort to learn our names, which was&#xA;actually really great. The towel-folding skills of the staff was quite&#xA;incredible as well. Here’s a photo of some bunnies they made for us.&#xA;&#xA;![/images/belize/bunnies.png](. &#34;/images/belize/bunnies.png&#34;)&#xA;&#xA;They made pretty good drinks too, and did I mention we had essentially&#xA;an open bar for the entire duration of the stay there? Yeah…that was&#xA;nice. Here’s a pina colada they made for me.&#xA;&#xA;![/images/belize/pina_colada.png](. &#34;/images/belize/pina_colada.png&#34;)&#xA;&#xA;We might have also done some ziplining…&#xA;&#xA;![/images/belize/zipline.png](. &#34;/images/belize/zipline.png&#34;)&#xA;&#xA;Before we left, we also visited a blue hole. If you’re ever in the&#xA;Caribbean, you might want to check one of these things out. Kind of&#xA;creepy, but also pretty cool.&#xA;&#xA;![/images/belize/blue_hole.png](. &#34;/images/belize/blue_hole.png&#34;)&#xA;&#xA;On the way out of the blue hole, we noticed a pretty funny sign for a&#xA;bizarrely-named tree. I had to take a photo for posterity.&#xA;&#xA;![/images/belize/horse_balls.png](. &#34;/images/belize/horse_balls.png&#34;)&#xA;&#xA;The next day, we were off to the beach resort. The drive was pretty&#xA;uneventful, and long. Here’s us getting in the vans, ready to go:&#xA;&#xA;![/images/belize/leaving_rainforest.png](. &#34;/images/belize/leaving_rainforest.png&#34;)&#xA;&#xA;As with the rest of Belize, much of the countryside was again filled&#xA;with abandoned / incomplete homes. It was overcast too, so it actually&#xA;worked out to be a good day to drive.&#xA;&#xA;Once we started getting closer to the beach, the landscape shifted from&#xA;forest to more of a Florida Everglades feel. I grew up in Miami, so the&#xA;sight was actually pretty familiar. As we were driving, our driving&#xA;spotted a crocodile in the water to the left of the road. They hide&#xA;themselves so well, it’s almost impossible to see them if you don’t have&#xA;an eye for it.&#xA;&#xA;The beach resort was exactly what you might imagine. Great view of the&#xA;Gulf of Mexico, plenty of cabanas, tropical drinks, beautiful rooms,&#xA;etc.&#xA;&#xA;![/images/belize/jaguar_reef.png](. &#34;/images/belize/jaguar_reef.png&#34;)&#xA;&#xA;The entrance to the beach resort&#xA;&#xA;We spent the first day at the beach getting acclimated and relaxing. I&#xA;think Rachel and I immediately submerged ourselves in the hot tub. It&#xA;was, after all, a grueling one and a half hour drive on a bumpy highway&#xA;that got us here.&#xA;&#xA;The funny thing about the hot tub was that there was no temperature&#xA;control. As a result, it was either scalding at 110 degrees, or off. So&#xA;we were relegated to leaving our feet in there for a handful of seconds&#xA;until our bodies couldn’t take the heat any more, and then bear the cold&#xA;as long as we could before putting them back in.&#xA;&#xA;The bartender by the side of the pool was apparently the hot tub&#xA;operator as well, so once we told him the problem, he promptly dismissed&#xA;himself and pulled the plug on the jacuzzi. Because, you know, it only&#xA;really is useful when it’s off.&#xA;&#xA;Once it cooled down, it was quite nice, but it took a good 30 to 40&#xA;minutes for that to happen. You can see the hot tub in the photo below,&#xA;just underneath the tree.&#xA;&#xA;![/images/belize/pool_beach.png](. &#34;/images/belize/pool_beach.png&#34;)&#xA;&#xA;Dinner our first night was also pretty unique. There is a restaurant&#xA;about a mile down the street from the Jaguar Beach Lodge called “Love on&#xA;the Rocks.” Their schtick is an “ancient” cooking style whereby diners&#xA;use heated stones to cook their food once it’s served. It’s kind of like&#xA;Benihana, except you do it yourself, and it’s not Japanese.&#xA;&#xA;![/images/belize/love_on_the_rocks.png](. &#34;/images/belize/love_on_the_rocks.png&#34;)&#xA;&#xA;Love on the Rocks&#xA;&#xA;![/images/belize/love_on_the_rocks2.png](. &#34;/images/belize/love_on_the_rocks2.png&#34;)&#xA;&#xA;Love on the Rocks, after the meal&#xA;&#xA;It was quite an experience, and I’d highly recommend it for anyone who&#xA;has a chance to go there. One thing to beware though—the service is slow&#xA;as molasses. Don’t go out to a meal unless you’re willing to spend more&#xA;than two hours at the restaurant. I’m not sure if this is a Belize thing&#xA;or what, but it happened at nearly every place we ate at.&#xA;&#xA;The next day was fairly relaxing—after of course choosing to walk to the&#xA;center of town in 90+ degree heat. We were sweating like none other by&#xA;the time we arrived at our destination—a small restaurant called Iris,&#xA;which was run by an ex-pat South African woman with a very special&#xA;cuisine. I ordered shrimp curry burritos, and it was fantastic. As with&#xA;our meal the night before, the service was extremely slow. It was quite&#xA;good though!&#xA;&#xA;We decided that walking back in the heat wasn’t going to happen, so we&#xA;requested a pickup at the restaurant. Once we got back to the resort, we&#xA;sat on the beach and I read “Game of Thrones: A Song of Fire and Ice”&#xA;while Rachel read a romance novel. I normally don’t read book (my&#xA;attention span needs improvement), but once I get in the zone, I fly&#xA;through them. Here’s a panorama of the beach:&#xA;&#xA;![/images/belize/beach_panorama.png](. &#34;/images/belize/beach_panorama.png&#34;)&#xA;&#xA;While I’ll avoid making this blog post a mini review of Game of Thrones,&#xA;I will say that the book is fantastic, and even with the availability of&#xA;Internet, I was sucked in. Highly recommended vacation / trip reading.&#xA;&#xA;That night we hung out with the family and went to another restaurant,&#xA;“Rob’s”, owned by the same people who run “Love on the Rocks.” The food&#xA;was okand the view from our table of the beach was just amazing, but the&#xA;service was again very slow.&#xA;&#xA;The next day kind of blurred away, but we did a little reading again on&#xA;the beach and ate at the resort restaurant for breakfast and lunch. I&#xA;think we started packing to go back home towards the evening.&#xA;&#xA;For dinner, we returned to Love on the Rocks. A bunch of the family&#xA;wasn’t with us when we went for the first night, but the food was so&#xA;good that I certainly didn’t mind going again. I ordered a Surf and Turf&#xA;(steak and lobster), and it was fantastic. The meal ended up taking&#xA;three hours, though, so by the end everyone was super anxious to leave.&#xA;&#xA;The next day was our departure. Our bus to the airport was scheduled to&#xA;leave at 10:30 or so, and by then we were all ready to go. The ride was&#xA;2 and a half hours, again through a pretty non-remarkable countryside.&#xA;Our flight was straight to Miami, and then L.A. Here’s the restaurant we&#xA;ate at the Belize Airport:&#xA;&#xA;![/images/belize/airport_restaurant.png](. &#34;/images/belize/airport_restaurant.png&#34;)&#xA;&#xA;Conclusion—Belize was a great place to visit. There are, of course, some&#xA;downsides to any place, but I had a great time and was able to really&#xA;relax. It may have been the longest time I have ever gone without access&#xA;to Internet, which was a blessing in many ways.&#xA;&#xA;It’s rare I ever get a chance to ever truly disconnect, hang out with my&#xA;wife with no distractions, and reflect upon things that I never really&#xA;think about. For those reasons alone, our trip to Belize was one of the&#xA;best I’ve ever gone on.&#xA;&#xA;Thanks for reading, and may you all have a fantastic 2013. Do great&#xA;things.&#xA;</content>
    <link href="https://dlo.me//archives/2013/01/07/belize/" rel="alternate"></link>
  </entry>
  <entry>
    <title>My Favorite Tweetbot Mute Filters</title>
    <updated>2012-11-26T00:00:00Z</updated>
    <id>tag:dlo.me,2012-11-26://archives/2012/11/26/favorite-mute-filters/</id>
    <content type="html">&#xA;You can click on any of these to add them to your Tweetbot.&#xA;&#xA;&lt;!-- tapbots.com/tweetbot/mute --&gt;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=%40.%2B.%2A%40.%2B.%2A%40.%2B&#39;&gt;&lt;code&gt;@.+.*@.+.*@.+&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;This mutes tweets that @ reference three or more users.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=%23%5Cw%7B15%2C%7D&#39;&gt;&lt;code&gt;#\w{15,}&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;This mutes tweets with absurdly long hashtags.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=%5E.%7B1%2C16%7D%24&#39;&gt;&lt;code&gt;^.{1,16}$&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;Mutes tweets shorter than 17 characters.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=%5E%28C%7Cc%29urrent+status&#39;&gt;&lt;code&gt;^(C|c)urrent status&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;Mutes tweets starting with the text &#34;Current status&#34;.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=%5E%5B+%5D%2A%5C.%40&#39;&gt;&lt;code&gt;^[ ]*\.@&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;Mutes publicized replies (e.g., tweets that people prepend with a period to show to everyone). I generally don&#39;t find these interesting, as they&#39;re sometimes out of context.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=%5E%5B%5Ea-z%5D%7B10%2C%7D&#39;&gt;&lt;code&gt;^[^a-z]{10,}&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;Mute tweets that consist only of uppercase characters, whitespace, and punctuation.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=%28fuck%7Cshit%7Cpoopin%29&#39;&gt;&lt;code&gt;(fuck|shit|poopin)&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;I don&#39;t like cursing, and the whole poopin&#39; meme is weird.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=0&amp;mentions=0&amp;text=I+just+met+you&#39;&gt;&lt;code&gt;I just met you&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;There was a time when people made &#34;I just met you&#34; jokes on Twitter constantly. It may have cooled down since then, but I wouldn&#39;t know.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=0&amp;mentions=0&amp;text=true+story&#39;&gt;&lt;code&gt;true story&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;I dunno, I just got a little tired of this one.&#xA;&#xA;&lt;a href=&#39;tweetbot:///mute/keyword?regex=1&amp;mentions=0&amp;text=nobody%2C%3F+ever&#39;&gt;&lt;code&gt;nobody,? ever&lt;/code&gt;&lt;/a&gt;&#xA;&#xA;Mutes all tweets of the form &#34;[outrageous statement] - said nobody, ever&#34;.&#xA;&#xA;If you have any other mute filters to recommend, please post them in the comments!&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2012/11/26/favorite-mute-filters/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Blog Refresh</title>
    <updated>2012-11-25T00:00:00Z</updated>
    <id>tag:dlo.me,2012-11-25://archives/2012/11/25/blog-refresh/</id>
    <content type="html">&#xA;I just gave my blog a bit of a refresh--it&#39;s been needing it for a while.&#xA;&#xA;So what changed?&#xA;&#xA;1. Main content is now centered (as opposed to left-aligned).&#xA;2. Permalinks are now of the form :year/:month/:day/:slug (before it was just /:slug).&#xA;3. The archives have now moved to their own page, as opposed to a sticky div on the right side of every post.&#xA;4. The header has been simplified and a lot of defunct social networks were removed.&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2012/11/25/blog-refresh/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Why Four Inches?</title>
    <updated>2012-10-09T00:00:00Z</updated>
    <id>tag:dlo.me,2012-10-09://archives/2012/10/09/why-four-inches/</id>
    <content type="html">I’ve been sitting on this for a couple of weeks now, but I now have a&#xA;theory as to why Apple increased the iPhone’s screen size from 3.5&#xA;inches to 4.&#xA;&#xA;Apple made a short term negative design change for a long term benefit.&#xA;The iPhone 5 is a temporary holdover until they figure out a design&#xA;flaw. Want to know what that flaw is? It’s this:&#xA;&#xA;![](/images/flaw.png)&#xA;&#xA;Yep. It’s that top bar with the camera and speaker. There is a time when&#xA;you have to make a tough decision to pave the way forward. Maybe the&#xA;only way to make this thing go and not make the phone “feel” weird was&#xA;to extend the height of the screen, and then snip off the top.&#xA;&#xA;### iPhone 5:&#xA;&#xA;![](/images/old-iphone.png)&#xA;&#xA;### iPhone 6:&#xA;&#xA;![](/images/new-iphone.png)&#xA;&#xA;I know, I know, my Photoshop skills need some work.&#xA;&#xA;To be frank, I just got the new iPod Touch (which is practically&#xA;identical in size to the iPhone 5), and I’m kinda liking it, so I’m not&#xA;actually sure if this will happen. But it’s a theory, and I think it’s&#xA;plausible. If you don’t think Apple would do this, look no further than&#xA;the iPod nano.&#xA;&#xA;![](/images/ipod-nano.png)&#xA;</content>
    <link href="https://dlo.me//archives/2012/10/09/why-four-inches/" rel="alternate"></link>
    <summary type="html">Why the iPhone 5&#39;s four inch screen ain&#39;t such a problem.</summary>
  </entry>
  <entry>
    <title>Blogging Again</title>
    <updated>2012-09-24T00:00:00Z</updated>
    <id>tag:dlo.me,2012-09-24://archives/2012/09/24/blogging-again/</id>
    <content type="html">&#xA;I&#39;ve been taking a break from writing on this blog for way too long of a time now. Like everyone else, I suppose I can make excuses, like &#34;I just got too busy&#34; or &#34;I didn&#39;t have anything to write about&#34;.&#xA;&#xA;Well, the first thing is true, but the second thing isn&#39;t. I have a ton of things to write about.&#xA;&#xA;I was chatting with a friend about this yesterday, and my theory is this: the busier you are the more you have to write about, but the less time you have to write about it. Well, I&#39;m dead set on making the time. It&#39;s not going to just appear out of nowhere.&#xA;&#xA;I&#39;m already brainstorming a couple articles. Some are programming-related, some are business-related, and others consulting-related. My main wish is to entertain and educate anyone who ends up here. So if you haven&#39;t subscribed to my RSS feed by now, please do.&#xA;&#xA;P.S. I wrote this post on my iPhone while waiting for lunch. You don&#39;t need a computer on your lap to write a blog post!&#xA;</content>
    <link href="https://dlo.me//archives/2012/09/24/blogging-again/" rel="alternate"></link>
    <summary type="html">Why I&#39;m going back to blogging.</summary>
  </entry>
  <entry>
    <title>Box Sizing that Works on Every Browser</title>
    <updated>2012-02-16T00:00:00Z</updated>
    <id>tag:dlo.me,2012-02-16://archives/2012/02/16/box-sizing-every-browser/</id>
    <content type="html">Earlier today, Paul Irish wrote a post on his blog that explains how to deal with an all-too-common issue that all frontend developers face--the div that increases in width and height as soon as you apply padding to it.&#xA;&#xA;An example might be the below.&#xA;&#xA;```html&#xA;&lt;style type=&#34;text/css&#34;&gt;&#xA;  div#example {&#xA;    background: yellow;&#xA;    height: 100px;&#xA;    width: 100px;&#xA;  }&#xA;&lt;/style&gt;&#xA;&lt;div id=&#34;example&#34;&gt;Hello, world.&lt;/div&gt;&#xA;```&#xA;&#xA;What it looks like:&#xA;&#xA;&lt;div style=&#39;background:yellow;height:100px;width:100px&#39;&gt;Hello, world.&lt;/div&gt;&#xA;&#xA;Now that doesn&#39;t look too good! Let&#39;s say the goal is to give that yellow box a padding on all sides, let&#39;s say 10 pixels. The impatient web developer does this:&#xA;&#xA;```html&#xA;&lt;style type=&#34;text/css&#34;&gt;&#xA;  div#example {&#xA;    padding: 10px;&#xA;    background: yellow;&#xA;    height: 100px;&#xA;    width: 100px;&#xA;  }&#xA;&lt;/style&gt;&#xA;&lt;div id=&#34;example&#34;&gt;Hello, world.&lt;/div&gt;&#xA;```&#xA;&#xA;&lt;div style=&#39;padding:10px;background:yellow;height:100px;width:100px&#39;&gt;Hello, world.&lt;/div&gt;&#xA;&#xA;Oh no! Our box is bigger! That&#39;s not what we wanted. So we do some quick mental math and subtract the padding from each side from the width and height, and get this.&#xA;&#xA;```html&#xA;&lt;style type=&#34;text/css&#34;&gt;&#xA;  div#example {&#xA;    padding: 10px;&#xA;    background: yellow;&#xA;    height: 80px;&#xA;    width: 80px;&#xA;  }&#xA;&lt;/style&gt;&#xA;&lt;div id=&#34;example&#34;&gt;Hello, world.&lt;/div&gt;&#xA;```&#xA;&#xA;&lt;div style=&#39;padding:10px;background:yellow;height:80px;width:80px&#39;&gt;Hello, world.&lt;/div&gt;&#xA;&#xA;So, this method works, but is messy and hard to maintain. Every time you need to change that padding, you&#39;ll have to change two other attributes as well to keep the content the same size. Paul Irish &#34;proposes&#34;:http://paulirish.com/2012/box-sizing-border-box-ftw/ using the @box-sizing: border-box@ attribute in your CSS, but there are a few cross-browser compatibility issues and jQuery bugs that affect its use. Additionally, if you ever want to use the &#34;normal&#34; box-sizing in your code, you&#39;ll have to start keeping track of which divs have @content-box@ or @border-box@ set in the @box-sizing@ attribute. That is also hard to keep track of.&#xA;&#xA;My recommendation is 100% portable across every browser known to man, and is extremely flexible. Presenting, the box in a box!&#xA;&#xA;```html&#xA;&lt;style type=&#34;text/css&#34;&gt;&#xA;  div.padder-10 {&#xA;    padding: 10px;&#xA;  }&#xA;  div#example {&#xA;    background: yellow;&#xA;    height: 100px;&#xA;    width: 100px;&#xA;  }&#xA;&lt;/style&gt;&#xA;&lt;div id=&#34;example&#34;&gt;&#xA;  &lt;div class=&#34;padder-10&#34;&gt;Hello, world.&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;```&#xA;&#xA;&lt;div style=&#39;background:yellow;height:100px;width:100px&#39;&gt;&lt;div style=&#39;padding:10px;&#39;&gt;Hello, world.&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;The outer div will maintain its height and width, and the responsibility for the padding is passed to the inner div.&#xA;</content>
    <link href="https://dlo.me//archives/2012/02/16/box-sizing-every-browser/" rel="alternate"></link>
    <summary type="html">How to pad divs without having to change their height and width.</summary>
  </entry>
  <entry>
    <title>Stop SOPA</title>
    <updated>2012-01-18T00:00:00Z</updated>
    <id>tag:dlo.me,2012-01-18://archives/2012/01/18/stop-sopa/</id>
    <content type="html">I am participating in an Internet-wide blackout in protest of two&#xA;dangerous bills, the [Stop Online Piracy Act&#xA;(SOPA)](http://hdl.loc.gov/loc.uscongress/legislation.112hr3261) and the&#xA;[Protect IP Act&#xA;(PIPA)](http://hdl.loc.gov/loc.uscongress/legislation.112s968), that are&#xA;soon up for vote in the United States Senate and House of&#xA;Representatives.&#xA;&#xA;Please take some time to read about how these bills are dangerous and&#xA;why they affect you, even if you don’t live in the U.S., by following&#xA;the links below.&#xA;&#xA;&lt;ul class=&#39;styled&#39;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[A technical examination of SOPA and PROTECT&#xA;IP](http://blog.reddit.com/2012/01/technical-examination-of-sopa-and.html&lt;/li&gt;)&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[Stop American Censorship Website](http://americancensorship.org/&lt;/li&gt;)&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[Google: End Piracy, Not&#xA;Liberty](https://www.google.com/landing/takeaction/&lt;/li&gt;)&#xA;&#xA;&lt;/ul&gt;&#xA;&#xA;Other interesting links:&#xA;&#xA;&lt;ul class=&#39;styled&#39;&gt;&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[Why I’m a Pirate!](http://ploum.net/post/im-a-pirate&lt;/li&gt;)&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[The Day the LOLcats Died&#xA;(video)](http://www.youtube.com/watch?v=1p-TV4jaCMk&lt;/li&gt;)&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[I’m Pirating the Next Version of&#xA;Windows](http://littlebitofcode.com/2012/01/18/im-pirating-the-next-version-of-windows&lt;/li&gt;)&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[Representative Lofgren (CA): do not underestimate the power that you&#xA;have](http://lofgren.house.gov/index.php?option=com_content&amp;view=article&amp;id=698:rep-lofgren-the-battle-over-sopa-pipa-do-not-underestimate-the-power-that-you-have&amp;catid=22:112th-news&amp;Itemid=161)&#xA;&#xA;&lt;li&gt;&#xA;&#xA;[Hollywood Finally Gets A Chance to Break the&#xA;Internet](https://www.eff.org/deeplinks/2011/10/sopa-hollywood-finally-gets-chance-break-internet&lt;/li&gt;)&#xA;&#xA;&lt;/ul&gt;&#xA;&#xA;If you live in the U.S., please take the time to email or call your&#xA;representative or senator so that they know that you are **opposed** to&#xA;these pieces of legislation.&#xA;</content>
    <link href="https://dlo.me//archives/2012/01/18/stop-sopa/" rel="alternate"></link>
  </entry>
  <entry>
    <title>What To Do When Your Site Goes Viral</title>
    <updated>2011-05-05T00:00:00Z</updated>
    <id>tag:dlo.me,2011-05-05://archives/2011/05/05/what-to-do-when-your-site-goes-viral/</id>
    <content type="html">My [last post](/breakup-notifier) told the story of how I made [Breakup&#xA;Notifier](http://www.breakupnotifier.com/) and how it exploded in&#xA;popularity across the Internet. This post is a technical expose on _how_&#xA;I kept it from going down.&#xA;&#xA;I would be lying if I told you the application as I wrote it from day&#xA;one had _any_ semblance to what it looked like just a few days later.&#xA;One thing you **must** accept is that unless you have a crystal ball, it&#xA;will be nearly impossible to forecast the issues that will arise in your&#xA;application. No matter what you do, there will be bugs that you didn’t&#xA;anticipate, features that should have been obvious, and money that you&#xA;could have saved. Be nimble and unafraid to rewrite your code. It will&#xA;happen, so be prepared.&#xA;&#xA;I hope that what I write here today can help you avoid the problems I&#xA;faced if you ever happen to be in the same position.&#xA;&#xA;Let the nerdery begin.&#xA;&#xA;## The Foundations&#xA;&#xA;I automate as much of what I do as I possibly can. I do this to keep my&#xA;development process very fast. It means that I can get a small prototype&#xA;of an idea together in a small fraction of the time that it would take&#xA;were I to build everything from scratch. Additionally I have a small&#xA;store of idioms in my head to take things to an MVP-level very quickly.&#xA;&#xA;Breakup Notifier was based on a [project&#xA;skeleton](https://github.com/lionheart/django-on-appengine) I have for&#xA;[Django](http://www.djangoproject.com/) projects running on [Google App&#xA;Engine](http://code.google.com/appengine/). Hosting the project on&#xA;Google’s infrastructure was the best architectural choice I made. I&#xA;can’t claim too much credit though; the fact is that I didn’t want to be&#xA;bothered with systems administration that weekend. Auto-scaling was just&#xA;a much-appreciated added bonus.&#xA;&#xA;The lessons I write about below are applicable to every site that faces&#xA;a deluge of traffic. Hopefully you won’t have to make the same mistakes&#xA;I did =D.&#xA;&#xA;### Lesson 1: If you can do something on the frontend with the same performance as doing it on the backend, do it on the frontend.&#xA;&#xA;Frontend code scales linearly with the number of users you have. Backend&#xA;code scales linearly with the number of servers you have. It doesn’t&#xA;take a rocket scientist to figure out which is less CPU-intensive from&#xA;the server perspective.&#xA;&#xA;I pulled out an example to illustrate. The following is a snippet of&#xA;code called after a user signs in from Facebook. It’s from the first&#xA;working version of the application.&#xA;&#xA;```python&#xA;    def facebook_oauth_complete(request):&#xA;        if &#39;access_token&#39; in request.session:&#xA;            return HttpResponseRedirect(&#34;/&#34;)&#xA;&#xA;        code = request.GET[&#39;code&#39;]&#xA;        params = {&#xA;                &#34;client_id&#34;: settings.FB_APP_ID,&#xA;                &#34;client_secret&#34;: settings.FB_APP_SECRET,&#xA;                &#34;redirect_uri&#34;: settings.FB_REDIRECT_URI,&#xA;                &#34;code&#34;: code }&#xA;        url = settings.FB_GRAPH_URI + &#34;/oauth/access_token?&#34; + urllib.urlencode(params)&#xA;&#xA;        # Fetch the access token&#xA;        response = urlfetch.fetch(url)&#xA;        data = response.content&#xA;        attributes = cgi.parse_qs(data)&#xA;&#xA;        access_token = attributes[&#39;access_token&#39;][0]&#xA;&#xA;        graph = facebook.GraphAPI(access_token)&#xA;        profile = graph.get_object(&#34;me&#34;)&#xA;        friends = graph.get_connections(&#34;me&#34;, &#34;friends&#34;)&#xA;        friend_ids = map(lambda k: k[&#39;id&#39;], friends[&#39;data&#39;])&#xA;&#xA;        ... code ...&#xA;&#xA;        for friend_id in friend_ids:&#xA;            task = taskqueue.Task(url=&#34;/tasks/create-profile&#34;,&#xA;                    method=&#34;POST&#34;,&#xA;                    params={&#xA;                        &#34;id&#34;: friend_id,&#xA;                        &#34;access_token&#34;: access_token,&#xA;                        &#34;requested_by&#34;: profile[&#39;id&#39;]})&#xA;            task.add(&#34;create-profile&#34;)&#xA;&#xA;        ... code ...&#xA;&#xA;```&#xA;&#xA;Deciding to pull in profile data in the backend unnecessarily (lines&#xA;27-34) was a very bad decision. **For every user** that authenticated&#xA;with the application, I made on average about 150 calls to Facebook! I&#xA;did this so that I could fill out the user’s friend list after they&#xA;authenticated. Why I decided to do this on the backend, I have no idea,&#xA;but it blew me past my task queue allotment more than once. Although&#xA;this didn’t bring down the site, it did prevent relationship status&#xA;notification emails from going out on schedule.&#xA;&#xA;Not long after this error started popping up, it occurred to me that you&#xA;can easily get this information through the Facebook Javascript SDK.&#xA;Instead of making Facebook API calls from App Engine, API calls are now&#xA;made straight from the browser. The result is a speedier experience for&#xA;the user and lower server costs.&#xA;&#xA;### Lesson 2: Simple is better. And faster. And easier to maintain.&#xA;&#xA;The first version of BN had a complicated queue system for checking&#xA;whether a relationship status had changed.&#xA;&#xA;Every set number of minutes, I was running a cronjob that would check&#xA;for relationship status changes. It would go through every single user&#xA;in the Breakup Notifier datastore (5 at a time), add a task to the task&#xA;queue to check for their friends who they wanted updates for, go through&#xA;each of their friends, and then call a method in the model that would&#xA;call Facebook’s API and would send an email to all the people following&#xA;that person saying their relationship status had changed.&#xA;&#xA;The above approach was riddled with issues, very slow, and impossible to&#xA;debug. The code was just a mess.&#xA;&#xA;I spent about an hour figuring out how to optimize this process as much&#xA;as possible. Compare the above to what it does now:&#xA;&#xA;1.  Fetch 1000 users at a time until all the users have been accessed.&#xA;2.  For each user, append a task to a list.&#xA;3.  When that list has 100 tasks, add them all to the task queue and&#xA;    make the list empty. Repeat 2 as necessary.&#xA;4.  For each task, loop through all of the friends they want updates&#xA;    for.&#xA;5.  If the current status is different than the stored status, send an&#xA;    email and update the friend in the database.&#xA;&#xA;This cut down costs by over 90%.&#xA;&#xA;### Lesson 3: Minimize calls to your database, cache, or queue.&#xA;&#xA;This ties in a bit with the changes I made above with the relationship&#xA;update scheme. If you see multiple calls to your database, use your&#xA;cache first. When querying the datastore, use keys. If you see too many&#xA;calls to your cache, group the calls together. If you’re using a queue,&#xA;add tasks to your queue in clumps, not one at a time. Latency is a huge&#xA;issue; don’t discount it. It will save you loads of issues in the&#xA;future.&#xA;&#xA;I’ll go over how you might make these optimizations with App Engine,&#xA;however the general principles apply to all frameworks and languages. If&#xA;you’re building for the web, you should do everything to minimize&#xA;latency and disk access.&#xA;&#xA;#### Memcache&#xA;&#xA;```python&#xA;    # Bad&#xA;    cache.set(&#34;president:1&#34;, &#34;nixon&#34;)&#xA;    cache.set(&#34;president:2&#34;, &#34;obama&#34;)&#xA;    cache.set(&#34;president:3&#34;, &#34;clinton&#34;)&#xA;&#xA;    # Good&#xA;    # This is a lower latency action. Instead of calling memcache three times, you only call it once.&#xA;    cache.set_multi({&#34;president:1&#34;: &#34;nixon&#34;, &#34;president:2&#34;: &#34;obama&#34;, &#34;president:3&#34;: &#34;clinton&#34;})&#xA;```&#xA;&#xA;#### Datastore&#xA;&#xA;```python&#xA;    # Bad&#xA;    query = User.all()&#xA;    query.filter(&#34;email =&#34;, &#34;user@example.com&#34;)&#xA;    user = query.get()&#xA;&#xA;    # Good&#xA;    # Best to retrieve objects by what they are indexed by in the datastore.&#xA;    # I&#39;m saving the User object with the key_name as the email address.&#xA;    user = User.get_by_key_name(&#34;user@example.com&#34;)&#xA;&#xA;    # Best&#xA;    # Use the cache before hitting the datastore. Reading from disk is slow,&#xA;    # reading from memory is fast.&#xA;    user = mc.get(&#34;user:user@example.com&#34;)&#xA;    if not user:&#xA;        user = User.get_by_key_name(&#34;user@example.com&#34;)&#xA;        mc.set(&#34;user:user@example.com&#34;, user)&#xA;```&#xA;&#xA;#### Task Queue&#xA;&#xA;```python&#xA;    # Bad&#xA;    # The user object may change during the interval from when the task is added to&#xA;    # when it executes. Can cause data loss.&#xA;    task = taskqueue.Task(url=&#34;/tasks/my-task&#34;, params={&#34;user&#34;: user1})&#xA;    task.add(&#34;my-queue&#34;)&#xA;&#xA;    task = taskqueue.Task(url=&#34;/tasks/my-task&#34;, params={&#34;user&#34;: user2})&#xA;    task.add(&#34;my-queue&#34;)&#xA;&#xA;    # Good&#xA;    # Retrieve the user object in the task, so as to not save stale data.&#xA;    task = taskqueue.Task(url=&#34;/tasks/my-task&#34;, params={&#34;user_key&#34;: &#34;user1@example.com&#34;})&#xA;    task.add(&#34;my-queue&#34;)&#xA;&#xA;    task = taskqueue.Task(url=&#34;/tasks/my-task&#34;, params={&#34;user_key&#34;: &#34;user2@example.com&#34;})&#xA;    task.add(&#34;my-queue&#34;)&#xA;&#xA;    # Best&#xA;    # Only make one call to the task queue instead of two. App Engine will allow&#xA;    # you to add 100 tasks at a time. Use that to your advantage.&#xA;    tasks = []&#xA;    task = taskqueue.Task(url=&#34;/tasks/my-task&#34;, params={&#34;user_key&#34;: &#34;user1@example.com&#34;})&#xA;    tasks.append(task)&#xA;&#xA;    task = taskqueue.Task(url=&#34;/tasks/my-task&#34;, params={&#34;user_key&#34;: &#34;user2@example.com&#34;})&#xA;    tasks.append(task)&#xA;&#xA;    queue = taskqueue.Queue(&#34;my-queue&#34;)&#xA;&#xA;    # Add the tasks.&#xA;    queue.add(tasks)&#xA;```&#xA;&#xA;### Lesson 4: If all else fails, log absolutely everything.&#xA;&#xA;You cannot possibly know what edge cases will emerge in your application&#xA;and cause you problems down the line. I don’t care how smart you think&#xA;you are or how much test coverage you have. Your site will have issues,&#xA;and you will not be prepared for them.&#xA;&#xA;The best advice I can give you is to log profusely. Let me show you an&#xA;example.&#xA;&#xA;```python&#xA;    def user_update(request):&#xA;        id = request.POST[&#39;id&#39;]&#xA;        logging.info(&#34;id=%s&#34; % id)&#xA;&#xA;        cache = memcache.Client()&#xA;        logging.debug(&#34;memcache client instantiated&#34;)&#xA;&#xA;        user_key = &#34;fb:%s&#34; % id&#xA;        user = cache.get(user_key)&#xA;        logging.debug(&#34;check if user in cache&#34;)&#xA;        if not user:&#xA;            logging.info(&#34;user not in cache&#34;)&#xA;            user = FacebookUserV2.get_by_key_name(id)&#xA;            logging.debug(&#34;user retrieved from database&#34;)&#xA;        else:&#xA;            logging.info(&#34;user retrieved from cache&#34;)&#xA;&#xA;        try:&#xA;            graph = facebook.GraphAPI(user.access_token)&#xA;            logging.debug(&#34;graph api instantiated&#34;)&#xA;            profile = graph.get_object(&#34;me&#34;)&#xA;            logging.debug(&#34;get profile data&#34;)&#xA;        except facebook.GraphAPIError:&#xA;            # Reset the access token of the user&#xA;            user.access_token = None&#xA;            logging.warning(&#34;access token invalid&#34;)&#xA;            user.put()&#xA;            logging.debug(&#34;save user&#34;)&#xA;            return JsonResponse({&#34;status&#34;: &#34;failure&#34;})&#xA;&#xA;        locale = profile.get(&#39;locale&#39;, None)&#xA;        if not locale:&#xA;            logging.warning(&#34;locale not provided&#34;)&#xA;        else:&#xA;            logging.info(&#34;locale=%s&#34; % locale)&#xA;        user.locale = locale&#xA;        user.put()&#xA;        logging.debug(&#34;save user&#34;)&#xA;        cache.set(user_key, user)&#xA;&#xA;        logging.debug(&#34;save user to cache&#34;)&#xA;        return JsonResponse({&#34;status&#34;: &#34;success&#34;})&#xA;```&#xA;&#xA;If you read through the code above, you can get a pretty good sense of&#xA;what I mean by “log everything”. The benefit of it is that you can&#xA;quickly diagnose issues. In App Engine, the logging facility is directed&#xA;to the “Logs” page in your application dashboard. If you use vanilla&#xA;Django on your own server, I recommend using&#xA;[Sentry](http://readthedocs.org/docs/sentry/en/latest/index.html) to&#xA;zero in on your application errors. I’m not too familiar with what you&#xA;might use for other frameworks or languages.&#xA;&#xA;If your app is throwing around 500s, it’s helpful to see what the last&#xA;line logged was before it died. You can easily zone in on the broken&#xA;code and diagnose the issue much more easily than it would be otherwise.&#xA;&#xA;#### Lesson 4.1: Monitor Your Application.&#xA;&#xA;Sometimes it’s just too hard to guess where your bottlenecks are. I&#xA;highly recommend you use whatever monitoring tool you can get your hands&#xA;on.&#xA;&#xA;Here are a few tools that I would recommend. Just keep in mind that the&#xA;open source options will take longer to configure and in a high traffic&#xA;situation might not be the best option to start off with.&#xA;&#xA;##### Performance Monitoring&#xA;&#xA;| Name                                             | What You Can Monitor                                                                                                                                                                                                                                                                                                                                                                                                                    | Notes                      |&#xA;| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |&#xA;| AppStats                                         | [Python](http://code.google.com/appengine/docs/python/tools/appstats.html), [Java](http://code.google.com/appengine/docs/java/tools/appstats.html)                                                                                                                                                                                                                                                                                      | App Engine Only            |&#xA;| [New Relic](http://newrelic.com/)                | [Ruby](http://newrelic.com/rails-app-performance.html), [Java](http://newrelic.com/java-app-performance.html), [.NET](http://newrelic.com/dotnet-app-performance.html), [PHP](http://newrelic.com/php-app-performance.html)                                                                                                                                                                                                             |                            |&#xA;| [Scout](https://scoutapp.com/)                   | [Rails](https://www.scoutapp.com/plugin_urls/181-ruby-on-rails-monitoring), [MySQL](https://www.scoutapp.com/plugin_urls/22-mysql-statistics), [Sphinx](https://www.scoutapp.com/plugin_urls/241-sphinx-monitoring), [MongoDB](https://www.scoutapp.com/plugin_urls/391-mongodb-monitoring), [Redis](https://www.scoutapp.com/plugin_urls/271-redis-monitoring), [Apache](https://www.scoutapp.com/plugin_urls/201-apache-log-analyzer) | Very extensible, open API. |&#xA;| [Union Station](http://www.unionstationapp.com/) | Ruby                                                                                                                                                                                                                                                                                                                                                                                                                                    | Free during open beta.     |&#xA;&#xA;##### Server Monitoring&#xA;&#xA;| Name                                          | Notes               |&#xA;| --------------------------------------------- | ------------------- |&#xA;| [Nagios](http://www.nagios.org/documentation) | Open source.        |&#xA;| [Monit](http://mmonit.com/monit/)             | Open source.        |&#xA;| [CloudKick](https://www.cloudkick.com/)       | Free for one server |&#xA;&#xA;### Lesson 5: Don’t check your email.&#xA;&#xA;This isn’t as “technical” as the above points, but I think it is&#xA;extremely important and relevant to what you’ll be experiencing when&#xA;your site is experiencing lots of traffic.&#xA;&#xA;Yes, I know it’s awesome that your site is going viral. All your friends&#xA;know about it, the media is talking about you, and a random person in&#xA;Idaho farmland heard about your website on the radio (true story).&#xA;&#xA;For the first day after the Breakup Notifier craziness started, I was&#xA;focused way too much on what was going on around me, instead of focusing&#xA;on making Breakup Notifier better. There were too many people talking&#xA;about it to even keep track of. My advice is to spend a moment to&#xA;reflect on the effect you’ve had, and then get back to work. Your work&#xA;does not get easier because the world is watching you!&#xA;&#xA;The key is getting shit done in the face of massive distraction.&#xA;&#xA;If your experience is anything like mine, you will get business&#xA;proposals, friend requests, direct messages, and everything else in&#xA;between. These all present fantastic potential opportunities, but my&#xA;advice is to deal with them later or recruit a friend to manage the&#xA;business-related issues. Otherwise you will explode and will get nothing&#xA;done.&#xA;&#xA;At the time, all of those things may seem really time-sensitive. What’s&#xA;more important though are your users, and the comments that people are&#xA;giving you _right now_. Build them the best product you can while eyes&#xA;are on you. It will make a difference.&#xA;&#xA;Thanks for reading. Be sure to&#xA;[subscribe](http://feeds.feedburner.com/dloewenherz) if you liked this&#xA;post and follow me on Twitter for the latest updates.&#xA;&#xA;Have a comment? Join the&#xA;[discussion](http://news.ycombinator.com/item?id=2519641).&#xA;</content>
    <link href="https://dlo.me//archives/2011/05/05/what-to-do-when-your-site-goes-viral/" rel="alternate"></link>
    <summary type="html">Lessons learned the hard way while scaling a viral web application.</summary>
  </entry>
  <entry>
    <title>Breakup Notifier</title>
    <updated>2011-03-20T00:00:00Z</updated>
    <id>tag:dlo.me,2011-03-20://archives/2011/03/20/breakup-notifier/</id>
    <content type="html">## Introduction&#xD;&#xA;&#xD;&#xA;This is the story of Breakup Notifier: when I made it, what the&#xD;&#xA;inspiration was, how I handled it, and anything else that might be&#xD;&#xA;relevant. I’m writing this not only because a lot of people asked me to,&#xD;&#xA;but also for myself. The past month has been a little out of this world,&#xD;&#xA;and there’s a ton of stuff that’s been sitting in my head just waiting&#xD;&#xA;to be jotted down on paper (or in this case, Vim). On another note, I&#xD;&#xA;still can’t get over that Breakup Notifier was on [Jay&#xD;&#xA;Leno](http://www.nbc.com/the-tonight-show/video/thursday-march-17-2011/1314753/)&#xD;&#xA;(broken link as of 1/29/2016) a few nights ago (skip to 8:42 on the&#xD;&#xA;monologue). Insane.&#xD;&#xA;&#xD;&#xA;So I’m going to go into as much detail as I possibly can. If I left out&#xD;&#xA;something that you’d like to see, shoot me an email at &lt;dan@dlo.me&gt; and&#xD;&#xA;I’ll update the post.&#xD;&#xA;&#xD;&#xA;## Thursday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 0&#xD;&#xA;&#xD;&#xA;It was a pretty normal night. My fiancée, her mom and I spent a few&#xD;&#xA;hours earlier that evening watching the season finale of&#xD;&#xA;[Spartacus](http://www.starz.com/originals/spartacus). After the show&#xD;&#xA;ended, I got back to work. A few minutes after getting back on the&#xD;&#xA;computer (which is also in our TV room—[noise canceling&#xD;&#xA;headphones](http://www.amazon.com/gp/product/B002M38I2U/ref=as_li_ss_tl?ie=UTF8&amp;tag=dansaweblo-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=B002M38I2U)&#xD;&#xA;FTW!), I started listening in on a conversation they were both having&#xD;&#xA;regarding my soon-to-be sister-in-law.&#xD;&#xA;&#xD;&#xA;So there was this great guy who they thought was a great match for her.&#xD;&#xA;They were talking him up to each other, convincing themselves he was&#xD;&#xA;probably the perfect guy. Their hopes were dashed after checking his&#xD;&#xA;relationship status on Facebook (yep, he was “In a relationship”).&#xD;&#xA;&#xD;&#xA;This was a problem just itching for a solution. Since programmers are&#xD;&#xA;kind of like real life superheroes, I decided to do my part. I proposed&#xD;&#xA;a system where they would get an email the instant said guy changed his&#xD;&#xA;relationship status. They absolutely loved it.&#xD;&#xA;&#xD;&#xA;## Friday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 0&#xD;&#xA;&#xD;&#xA;Here’s the first chat I had where I talked about the idea.&#xD;&#xA;&#xD;&#xA;&lt;table&gt;&#xD;&#xA;&lt;thead&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;th&gt;12:07 PM&lt;/th&gt;&#xD;&#xA;&lt;th&gt;&lt;strong&gt;me:&lt;/strong&gt; i’m going to take an hour break&lt;/th&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;/thead&gt;&#xD;&#xA;&lt;tbody&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;i want to make a joke website&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;Christopher:&lt;/strong&gt; haha&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;nice&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;12:08 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;what’s the idea&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; &lt;a&#xD;&#xA;href=&#34;http://www.breakupnotifier.com&#34;&gt;breakupnotifier.com&lt;/a&gt;&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;12:09 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;login through facebook and mark hot girls you’re interested in&#xD;&#xA;who&lt;br /&gt;&#xD;&#xA;are in a relationship&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;it will email you when they break up&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;Christopher:&lt;/strong&gt; thats brilliant&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;im waiting out like 5 relationships&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; haha&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;12:09 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;i’m giving myself a time limit of one hour&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;Christopher:&lt;/strong&gt; you got it&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; will let you know when its done&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;Christopher:&lt;/strong&gt; sweet&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;/tbody&gt;&#xD;&#xA;&lt;/table&gt;&#xD;&#xA;&#xD;&#xA;Somewhere in the middle of this conversation I purchased the domain&#xD;&#xA;breakupnotifier.com (if you’re wondering why the confirmation email says&#xD;&#xA;1:08pm, it’s because GoDaddy is located in Arizona). And no, I didn’t&#xD;&#xA;follow through with my initial time estimate :P.&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;![/images/godaddy-bn.png](. &#34;/images/godaddy-bn.png&#34;)&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;I had enough feedback to actually build something. The git repo was&#xD;&#xA;started at 12:40pm with the intent to actually work on this thing the&#xD;&#xA;next day.&#xD;&#xA;&#xD;&#xA;In the meantime, I thought it would be fun to get some more feedback&#xD;&#xA;from some other friends. I thought the idea was hilarious so I naturally&#xD;&#xA;wanted to tell as many people as possible.&#xD;&#xA;&#xD;&#xA;&lt;table&gt;&#xD;&#xA;&lt;thead&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;th&gt;1:14 PM&lt;/th&gt;&#xD;&#xA;&lt;th&gt;&lt;strong&gt;me:&lt;/strong&gt; &lt;a&#xD;&#xA;href=&#34;http://www.breakupnotifier.com/&#34;&gt;http://breakupnotifier.com/&lt;/a&gt;&lt;/th&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;/thead&gt;&#xD;&#xA;&lt;tbody&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;lol&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;Richard:&lt;/strong&gt; dude i think it can go even further than&#xD;&#xA;that - check out what im&lt;br /&gt;&#xD;&#xA;about to send you&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; haha&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;1:15 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;quick which icon set do you like better&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;a&#xD;&#xA;href=&#34;http://www.istockphoto.com/stock-illustration-10194726-valentine-s-day-icon-set.php&#34;&gt;http://www.istockphoto.com/stock-illustration-10194726-valentine-s-day-icon-set.php&lt;/a&gt;&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;a&#xD;&#xA;href=&#34;http://www.istockphoto.com/stock-illustration-14678717-heart-icons.php&#34;&gt;http://www.istockphoto.com/stock-illustration-14678717-heart-icons.php&lt;/a&gt;&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;Richard:&lt;/strong&gt;&lt;br /&gt;&#xD;&#xA;&lt;a&#xD;&#xA;href=&#34;http://www.istockphoto.com/stock-illustration-10194726-valentine-s-day-icon-set.php&#34;&gt;http://www.istockphoto.com/stock-illustration-10194726-valentine-s-day-icon-set.php&lt;/a&gt;&lt;br /&gt;&#xD;&#xA;gives you more choice&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;1:16 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;right?&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; yeah agreed&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;i’m going with it&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;/tbody&gt;&#xD;&#xA;&lt;/table&gt;&#xD;&#xA;&#xD;&#xA;I purchased $18.50 worth of credits on iStockphoto and downloaded the&#xD;&#xA;Valentine’s Day icon set. After it was downloaded, I spent about 30&#xD;&#xA;minutes inside of Fireworks turning this&#xD;&#xA;&#xD;&#xA;![/images/original-bn-icon.png](. &#34;/images/original-bn-icon.png&#34;)&#xD;&#xA;&#xD;&#xA;into&#xD;&#xA;&#xD;&#xA;![/images/bn-icon.png](. &#34;/images/bn-icon.png&#34;)&#xD;&#xA;&#xD;&#xA;I went with the Facebook color scheme primarily because I figured it’s&#xD;&#xA;what people are accustomed to and comfortable with.&#xD;&#xA;&#xD;&#xA;I think I spent the next few hours working on some other things. At 3 I&#xD;&#xA;started tinkering with the Facebook Graph API. Here’s a chat from around&#xD;&#xA;then.&#xD;&#xA;&#xD;&#xA;&lt;table&gt;&#xD;&#xA;&lt;thead&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;th&gt;3:30 PM&lt;/th&gt;&#xD;&#xA;&lt;th&gt;are you up to anything?&lt;/th&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;/thead&gt;&#xD;&#xA;&lt;tbody&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; making a silly web app&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;a href=&#34;http://breakupnotifier.com&#34;&gt;breakupnotifier.com&lt;/a&gt;&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;my break for the day&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; hahaha&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;what is it?&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;3:31 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; rachel (my fiancée) was talking with her mom&#xD;&#xA;last night about&lt;br /&gt;&#xD;&#xA;her sister, and how they wanted to set her up with this guy&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;they checked on facebook and he was in a relationship&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;so i was like, would you guys want to be notified when his&#xD;&#xA;relationship status&lt;br /&gt;&#xD;&#xA;changes?&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;they were like YESSS&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;and an idea was born&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; hahaha&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;3:32 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;so whenever your friends&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;relationship status changes&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;you get notified&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; well you can pick and choose&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; haha thats funny&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; so you can pick only the hot girls&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;or something&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; i like it&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; hah&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;cool&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;it’ll be on hn in a few hours&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; did you start building yet?&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; i hope&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;yes&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; the fbook api is pretty easy right&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;did you use a library&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;3:33 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; yeah, it’s super simple&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;although it’s being annoying right now&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;with the permissions crap&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; oh&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; i’m asking for certain permissions but the api&#xD;&#xA;isn’t giving me the info&lt;br /&gt;&#xD;&#xA;i asked for&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;pretty weird&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;3:34 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;James:&lt;/strong&gt; thats annoying&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;3:35 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;&lt;strong&gt;me:&lt;/strong&gt; i wonder if its because of my draconian privacy&#xD;&#xA;settings&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;…&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;3:39 PM&lt;/td&gt;&#xD;&#xA;&lt;td&gt;eff this&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;tr&gt;&#xD;&#xA;&lt;td&gt;&lt;/td&gt;&#xD;&#xA;&lt;td&gt;i give up&lt;/td&gt;&#xD;&#xA;&lt;/tr&gt;&#xD;&#xA;&lt;/tbody&gt;&#xD;&#xA;&lt;/table&gt;&#xD;&#xA;&#xD;&#xA;Yep, believe it or not, Breakup Notifier almost never happened. I worked&#xD;&#xA;on [Crate](http://letscrate.com) for the rest of the day.&#xD;&#xA;&#xD;&#xA;## Saturday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 0&#xD;&#xA;&#xD;&#xA;I decided to finish what I started. I knew there was something here and&#xD;&#xA;I was not going to give in so easily. I figured out what my issue was&#xD;&#xA;with the Facebook API pretty quickly (I was asking for the wrong&#xD;&#xA;permissions).&#xD;&#xA;&#xD;&#xA;I finished the web application sometime around mid-afternoon Saturday,&#xD;&#xA;[tweeted about it](https://twitter.com/dwlz/status/39225184854622209)&#xD;&#xA;(here’s [the&#xD;&#xA;one](https://twitter.com/BreakupNotifier/status/39207349247934464) from&#xD;&#xA;the [@BreakupNotifier](http://twitter.com/breakupnotifier) account), and&#xD;&#xA;then submitted the post to&#xD;&#xA;[Forrst](http://forrst.com/posts/Breakup_Notifier-guq). I knew this site&#xD;&#xA;had some major potential, so I was seriously pushing it to my friends&#xD;&#xA;and followers.&#xD;&#xA;&#xD;&#xA;Despite all this, no one really cared.&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;![/images/bn-traffic-saturday.png](. &#34;/images/bn-traffic-saturday.png&#34;)&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;## Sunday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 15&#xD;&#xA;&#xD;&#xA;The next day, I wanted to give Breakup Notifier one last chance.&#xD;&#xA;&#xD;&#xA;I submitted Breakup Notifier to HN at about 4:00pm ([Show HN: Breakup&#xD;&#xA;Notifier](http://news.ycombinator.com/item?id=2243650)). I distinctly&#xD;&#xA;recall that it almost fell off the new page and sat at around 2-3&#xD;&#xA;upvotes for about an hour. I think by the time an hour passed, I’d given&#xD;&#xA;up on it and started watching Batman Begins with my fiancee. I checked&#xD;&#xA;on my computer halfway through the movie, and somehow it made it to the&#xD;&#xA;front page!&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;|\_. 6:35 PM|**Carter:** kudos on the hn post|  &#xD;&#xA;|\_. 6:40 PM|**me:** haha wow|  &#xD;&#xA;||it’s gotten on the front page?|  &#xD;&#xA;||**Carter:** yeah|  &#xD;&#xA;||**me:** oh wow|&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;&lt;hr/&gt;&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;|\_. 7:07 PM|**James:** dude!|  &#xD;&#xA;||breakup notifier|  &#xD;&#xA;||is 3rd on hn|  &#xD;&#xA;||thats awesome|&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;On Sunday night at around 7:30pm PST, had reached the \#1 spot; by&#xD;&#xA;9:19pm, the site had over 12,000 unique visitors.&#xD;&#xA;&#xD;&#xA;At 11:12 PM that night, the impossible happened.&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;![/images/bn-email-tc.png](. &#34;/images/bn-email-tc.png&#34;)&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;Holy. Shit.&#xD;&#xA;&#xD;&#xA;I knew what the problem was. Alexia wasn’t getting emails since I was&#xD;&#xA;only checking for relationship status changes once every 24 hours. As a&#xD;&#xA;test, Alexia broke up with herself, saw no email, and was very sad.&#xD;&#xA;&#xD;&#xA;I promise, this was the closest I’ve probably ever gotten to having a&#xD;&#xA;psychological breakdown. I definitively remember my hands shaking as I&#xD;&#xA;wrote the code to increase the polling frequency. My adrenaline levels&#xD;&#xA;must have been through the roof. I knew the site had viral potential—I&#xD;&#xA;just couldn’t really believe it was actually happening.&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;![/images/bn-traffic-sunday.png](. &#34;/images/bn-traffic-sunday.png&#34;)&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;## Monday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 18,805&#xD;&#xA;&#xD;&#xA;I pushed the new code and waited. And waited. Next morning, at around&#xD;&#xA;11am, it happened…Breakup Notifier was&#xD;&#xA;[crunched](http://techcrunch.com/2011/02/21/stalkbook/). On another&#xD;&#xA;note, this is the point where I was also given introduced to all the&#xD;&#xA;wonderful TechCrunch commenters.&#xD;&#xA;&#xD;&#xA;I was blown away by the malice of some of these people. It’s really kind&#xD;&#xA;of depressing, but that’s the subject of another post. Moving on…&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;|\_. 10:06 AM|**Matthew:**&#xD;&#xA;[http://www.cnn.com/2011/TECH/social.media/02/21/facebook.relationship.status/index.html?iref=NS1](http://www.cnn.com/2011/TECH/social.media/02/21/facebook.relationship.status/index.html?iref=NS1|)  &#xD;&#xA;||nice|  &#xD;&#xA;||**me:** holy shit|  &#xD;&#xA;||hahahaha|  &#xD;&#xA;||CNN!|&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;Shit got real right about now. Within a few hours, I was contacted by&#xD;&#xA;ABCNews.com, MSNBC, GlobeAndMail, Technorati, PC Magazine, The New York&#xD;&#xA;Daily News, Information Week, the Huffington Post, The Washington Post,&#xD;&#xA;The NY Times Magazine, the International Business Times, and the list&#xD;&#xA;goes on.&#xD;&#xA;&#xD;&#xA;Here’s the traffic graph for Monday.&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;![/images/bn-traffic-monday.png](. &#34;/images/bn-traffic-monday.png&#34;)&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;## Tuesday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 116,669&#xD;&#xA;&#xD;&#xA;It may seem crazy, but by Tuesday I was in a state of immense denial. I&#xD;&#xA;truly could not believe what was going on. Every morning for the next&#xD;&#xA;few days, I felt like I was waking up to a dream. It was all too surreal&#xD;&#xA;and odd for me to really explain. It was kind of like a ton of eyes were&#xD;&#xA;on me and what I was going to do. Hell, a bunch of people thought&#xD;&#xA;Facebook would ban me (lulz).&#xD;&#xA;&#xD;&#xA;I teased fate a little bit when I commented that I would feel&#xD;&#xA;[honored](http://news.ycombinator.com/item?id=2243708) if Facebook were&#xD;&#xA;to ban Breakup Notifier. Heh.&#xD;&#xA;&#xD;&#xA;## Wednesday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 164,309&#xD;&#xA;&#xD;&#xA;I woke up to this email in my inbox (sent at 4:38AM).&#xD;&#xA;&#xD;&#xA;&lt;figure&gt;&#xD;&#xA;&#xD;&#xA;![/images/bn-disabled-facebook.png](. &#34;/images/bn-disabled-facebook.png&#34;)&#xD;&#xA;&#xD;&#xA;&lt;/figure&gt;&#xD;&#xA;&#xD;&#xA;I suppose I was overstepping my bounds ;). I would later learn that the&#xD;&#xA;reason the application was disabled was because of a little “Share”&#xD;&#xA;button. When a Facebook application uses the feed dialog to publish a&#xD;&#xA;story through an application, it’s actually a wrapper over the Facebook&#xD;&#xA;API. So when a user hides or blocks content from one of these posts, it&#xD;&#xA;can actually penalize the application that allowed the user to share.&#xD;&#xA;&#xD;&#xA;Apparently, a lot of people were hiding these shares from friends, so&#xD;&#xA;the application was banned automatically. I didn’t have as large a&#xD;&#xA;problem with the reason it was banned as much as the amount of time it&#xD;&#xA;took for Facebook to communicate this to me.&#xD;&#xA;&#xD;&#xA;I immediately sent an email to Alexia at TechCrunch letting her know&#xD;&#xA;what was going on. I figured this would be an interesting story and I&#xD;&#xA;was lucky enough that she decided to write [about&#xD;&#xA;it](http://techcrunch.com/2011/02/23/breakup-notifier/).&#xD;&#xA;&#xD;&#xA;I don’t like sitting around like a lame duck, so I did what any self&#xD;&#xA;respecting hacker would do. I decided to channel the traffic to Breakup&#xD;&#xA;Notifier to another website while all this Facebook stuff was being&#xD;&#xA;sorted out. I would call it [Crush&#xD;&#xA;Notifier](http://www.crushnotifier.com).&#xD;&#xA;&#xD;&#xA;Amidst all the press craziness that was happening about now (and a&#xD;&#xA;packed inbox), I pumped out the site.&#xD;&#xA;&#xD;&#xA;## Friday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 197,999&#xD;&#xA;&#xD;&#xA;Breakup Notifier traffic was really dying down by now, especially since&#xD;&#xA;the site hadn’t been able to do anything for about two days, per&#xD;&#xA;Facebook’s ban of the application.&#xD;&#xA;&#xD;&#xA;With Crush Notifier, I planned to integrate Facebook Credits, write a&#xD;&#xA;revamped backend (to handle a lot more users), and basically make&#xD;&#xA;everything faster. It took a few hours, and by evening, I was ready to&#xD;&#xA;launch the site. I wrote a [blog post](http://blog.crushnotifier.com),&#xD;&#xA;submitted it to [HN](http://news.ycombinator.com/item?id=2264660), and&#xD;&#xA;braced myself.&#xD;&#xA;&#xD;&#xA;TechCrunch soon posted [about&#xD;&#xA;it](http://techcrunch.com/2011/02/25/crushnotifier/). Nothing really&#xD;&#xA;prepared me for the sort of vitriol that I would soon be the target of.&#xD;&#xA;In the TechCrunch post, I was called things that I’d rather not repeat&#xD;&#xA;on this blog. There’s no need to repeat them here.&#xD;&#xA;&#xD;&#xA;I went to bed feeling extremely depressed that night. Rachel told me not&#xD;&#xA;to worry—this is just how some people are, and there’s not really much&#xD;&#xA;one can do about it. It still just stuck with me. There isn’t really&#xD;&#xA;anything much worse than random people accusing you of being a cheat,&#xD;&#xA;liar, and fraud.&#xD;&#xA;&#xD;&#xA;## Saturday&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 203,171&#xD;&#xA;&#xD;&#xA;I could never have predicted what would happen next.&#xD;&#xA;&#xD;&#xA;A fellow HNer, [jarin](http://news.ycombinator.com/user?id=jarin), wrote&#xD;&#xA;[a post](http://news.ycombinator.com/item?id=2265161) entitled *Crush&#xD;&#xA;Notifier backlash: if Dan Lowenherz is a crook so is your favorite&#xD;&#xA;company*. He spelled my last name wrong, but it really didn’t matter. I&#xD;&#xA;know this might sound bizarre, but I never felt more at home in any&#xD;&#xA;community of people. Everyone chimed in about how this happens to&#xD;&#xA;everyone; it’s just how the world works.&#xD;&#xA;&#xD;&#xA;Main thing I learned from this experience is that you just have to&#xD;&#xA;ignore everyone who tries to put you down. It **will** happen. As Chad Etzel [remarked](http://news.ycombinator.com/item?id=2265200): “Haters gonna hate.” Nothing you can do about it.&#xD;&#xA;&#xD;&#xA;## Conclusion&#xD;&#xA;&#xD;&#xA;#### Unique Visitors: 237,851 and counting&#xD;&#xA;&#xD;&#xA;You’re probably wondering how much money I made with all this attention.&#xD;&#xA;&#xD;&#xA;Short answer: nothing. Long answer: had I chosen to put ads on the site as it was exploding in popularity, I believe I would have probably made a few hundred dollars.&#xD;&#xA;&#xD;&#xA;As for Facebook’s ban of the application, it was revoked on Sunday. On Monday morning, I relaunched the application (it goes without saying that I removed the ‘Share button’).&#xD;&#xA;&#xD;&#xA;I also would like to add that I am so incredibly thankful for the HN community and the positive energy that it promotes. There is nothing quite like it. I love all of you.&#xD;&#xA;&#xD;&#xA;My next post is going to be a bit more technical in nature. I’m going to write what the scaling/coding aspect of this entire experience was like, so if you’re at all interested in that, be sure to [subscribe](http://feeds.feedburner.com/dloewenherz)!&#xD;&#xA;&#xD;&#xA;Have a comment? Join the&#xD;&#xA;[discussion](http://news.ycombinator.com/item?id=2348702).</content>
    <link href="https://dlo.me//archives/2011/03/20/breakup-notifier/" rel="alternate"></link>
    <summary type="html">How a harmless conversation led to one of the most viral websites ever.</summary>
  </entry>
  <entry>
    <title>How I Deal With Content Overload</title>
    <updated>2011-02-05T00:00:00Z</updated>
    <id>tag:dlo.me,2011-02-05://archives/2011/02/05/how-i-deal-with-content-overload/</id>
    <content type="html">Over the past couple of years, I&#39;ve been slowly honing a way to manage the deluge of online content that presents itself to me. The problem nowadays isn&#39;t that there isn&#39;t enough interesting content. It&#39;s that there is *way* too much to handle.&#xA;&#xA;Follow these steps, and your free time will open up measurably:&#xA;&#xA;1. Get an account at [Pinboard.in](http://pinboard.in) and [Instapaper](http://instapaper.com). I love Pinboard because the service is easy to use, the developer is on top of his stuff, and it works like a charm. It&#39;s worth the $10 it costs today. I actually have an archival account, which costs $25/year, so if I bookmark something and the site goes down, I will still have a saved copy.&#xA;2. Set up two bookmarklets: the Instapaper &#34;Read Later&#34; one and the Pinboard &#34;popup.&#34;&#xA;3. Add your Instapaper feed URL to Pinboard. This will sync your Instapaper and Pinboard accounts. Also, make sure that new bookmarks are set as &#34;unread.&#34;&#xA;4. Take 5 minutes once each day to browse through [Reddit](http://reddit.com), [HN](http://news.ycombinator.com), [StumbleUpon](http://stumbleupon.com), or whatever it is you use to find content. Click on &#34;Read Later&#34; for blog posts you find interesting and use the Pinboard popup for items of interest, such as tutorials, documentation, or websites you might want to check out. Tag them appropriately.&#xA;5. Put aside an hour at the end of the day to read up on the Instapaper items you marked and your unread bookmarks on Pinboard on your iPad[^1], Kindle, computer, etc.&#xA;&#xA;Can&#39;t say what this will do for you, but I&#39;ve easily gained a couple more hours of productivity each day from this. It&#39;s easier to stay focused on the task at hand when you schedule your procrastination.&#xA;&#xA;[^1]: I&#39;m considering writing a Pinboard.in iPad application. I couldn&#39;t find one that I&#39;d use. Does anyone else have a need for this?</content>
    <link href="https://dlo.me//archives/2011/02/05/how-i-deal-with-content-overload/" rel="alternate"></link>
    <summary type="html">My strategy for dealing with a lot of information. Try it out.</summary>
  </entry>
  <entry>
    <title>Crazy.</title>
    <updated>2011-01-20T00:00:00Z</updated>
    <id>tag:dlo.me,2011-01-20://archives/2011/01/20/crazy/</id>
    <content type="html">&#xA;I&#39;m a &lt;strong&gt;huge&lt;/strong&gt; fan of the &lt;a rel=&#34;nofollow&#34; href=&#39;http://en.wikipedia.org/wiki/Pareto_principle&#39;&gt;Pareto Principle&lt;/a&gt;. Almost everybody, from &lt;a rel=&#34;nofollow&#34; href=&#39;http://www.gladwell.com/&#39;&gt;Malcolm Gladwell&lt;/a&gt; to &lt;a rel=&#34;nofollow&#34; href=&#39;http://www.fourhourworkweek.com/&#39;&gt;Timothy Ferriss&lt;/a&gt;, share my obsession with it. The idea is that a small amount of the inputs are the cause for most of the outputs.&#xA;&#xA;The most productive periods of your life are those where you act crazy. Being unpredictable is a necessary ingredient for serendipity, and it&#39;s always those serendipitous moments where the biggest changes in your life will happen. You&#39;ve gotta shake things up. &lt;strong&gt;As much as you can&lt;/strong&gt;.&#xA;&#xA;If most change comes from being unpredictable, why not embrace it? Make it who you are.&#xA;&#xA;Start doing things you don&#39;t normally do. Talk to people you don&#39;t normally talk to. Get out of your comfort zone.&#xA;&#xA;You&#39;ll be happier because of it.&#xA;</content>
    <link href="https://dlo.me//archives/2011/01/20/crazy/" rel="alternate"></link>
    <summary type="html">Sometimes being unpredictable is the best thing you can do. Try it out sometime.</summary>
  </entry>
  <entry>
    <title>When to Buy Airline Tickets Continued</title>
    <updated>2010-08-01T00:00:00Z</updated>
    <id>tag:dlo.me,2010-08-01://archives/2010/08/01/flying-on-the-cheap/</id>
    <content type="html">First, a brief re-introduction. [A few months&#xA;back](/when-to-buy-airline-tickets), I thought it would be a cool&#xA;experiment to use crowdsourcing to optimize my choices in buying&#xA;airplane tickets.&#xA;&#xA;Unfortunately, though, I didn’t really finish what I started. This post&#xA;is going to fix that.&#xA;&#xA;To spice things up a little bit, I added over 3000 data points to this&#xA;analysis over the last one, so things are not only going to look cooler,&#xA;but they’re going to be more accurate as well. Before I delve into the&#xA;actual statistics of all the information I gathered, I’m going to give a&#xA;little overview of how I actually made this all happen, or you can [skip&#xA;straight to the results](#results).&#xA;&#xA;## A Crash Course in Mechanical Turk&#xA;&#xA;Amazon has a nice and easy interface for adding batches of jobs like&#xA;these. You can specify a description, some keywords (this makes it&#xA;easier for “Turkers” to find your job), and various other variables,&#xA;such as the time alotted per assignment and the amount of time before a&#xA;HIT (Human Intelligence Task) expires.&#xA;&#xA;![](/images/mturk-1.png)&#xA;&#xA;Once you do this, you basically have to figure out a few more things.&#xA;&#xA;- A data set.&#xA;- A way for Turkers to enter in the data they find (using the form&#xA;  interface on the MTurk website).&#xA;&#xA;This is a short snippet of code that generated the random data for the&#xA;trips.&#xA;&#xA;```python&#xA;SECONDS_IN_YEAR = 28425600&#xA;CITIES = [&#34;MIA&#34;, &#34;LAX&#34;, &#34;JFK / LGA&#34;, &#34;SFO&#34;]&#xA;&#xA;def write_random_trips(n):&#xA;    for i in xrange(n):&#xA;        row = random.sample(CITIES, 2)&#xA;        r = random.randint(0, SECONDS_IN_YEAR)&#xA;        d = datetime.fromtimestamp(time.time() + r)&#xA;        row.append(d.strftime(&#34;%B %d %Y&#34;))&#xA;    print &#34;%s,%s,%s&#34; % (row[0], row[1], row[2])&#xA;```&#xA;&#xA;I uploaded it as a CSV, and Amazon automatically divvied up the prompts&#xA;to the Mechanical Turk users.&#xA;&#xA;## The Waiting Game&#xA;&#xA;This is, by far, the most gut-renching part of the Mechanical Turk&#xA;experience. After submitting your batch of HITs to Amazon, it can take&#xA;anywhere from 8 to 12 hours for them all to get finished. As people&#xA;complete the tasks, you have the ability to accept or reject the&#xA;submissions (when you reject a submission, you should give a reason to&#xA;the Turker who completed it). This is a little tedious, but it’s pretty&#xA;important if you want to weed out bad data.&#xA;&#xA;Once the batch is complete, you have the option to export the results to&#xA;a CSV. From there, you can do whatever you’d like with the data. I&#xA;packaged it up nicely so that Mathematica could make some sense of it.&#xA;&#xA;## Results&#xA;&#xA;The first thing I wanted to see once I had everything in hand was pretty&#xA;obvious (at least to me): What is the cheapest time of year to fly?&#xA;&#xA;The following is a chart plotting day-of-year versus average price for&#xA;that day.&#xA;&#xA;![](/images/flights_jagged.png)&#xA;&#xA;The above isn’t so useful. It’s pretty hard to extrapolate a trend from&#xA;really jagged data like this. I decided that a 30-day moving average&#xA;would be a lot smoother and easier to interpret. Below is the result.&#xA;The average ticket price was \$160.97 and the data stayed within a small&#xA;range of the average (the standard deviation was \$13.00).&#xA;&#xA;![](/images/flights_ma_30_day.png)&#xA;&#xA;Another thing I wanted answered was the best **time** of day to order&#xA;airplane tickets. Unfortunately, I didn’t get many data points for&#xA;periods in the middle of the night, so the data is a little skewed. The&#xA;graph below charts time against ticket price, with the size of the&#xA;circle representing the number of data points for that time period.&#xA;&#xA;![](/images/flights_prices_point_count.png)&#xA;&#xA;Here’s a table of average prices for each day of the week. Thursday is&#xA;the most expensive day to fly, and Saturday is the cheapest.&#xA;&#xA;| Day       | Average Price |&#xA;|-----------|---------------|&#xA;| Monday    | \$159.40      |&#xA;| Tuesday   | \$159.36      |&#xA;| Wednesday | \$161.91      |&#xA;| Thursday  | \$174.45      |&#xA;| Friday    | \$163.28      |&#xA;| Saturday  | \$148.03      |&#xA;| Sunday    | \$166.98      |&#xA;&#xA;And here I have it split up by city.&#xA;&#xA;### Miami&#xA;&#xA;| Day       | From     | To       |&#xA;|-----------|----------|----------|&#xA;| Monday    | \$167.12 | \$164.45 |&#xA;| Tuesday   | \$169.38 | \$169.66 |&#xA;| Wednesday | \$172.03 | \$166.51 |&#xA;| Thursday  | \$191.08 | \$195.11 |&#xA;| Friday    | \$172.70 | \$169.38 |&#xA;| Saturday  | \$158.23 | \$162.29 |&#xA;| Sunday    | \$169.63 | \$199.33 |&#xA;&#xA;### LA&#xA;&#xA;| Day       | From     | To       |&#xA;|-----------|----------|----------|&#xA;| Monday    | \$156.99 | \$149.67 |&#xA;| Tuesday   | \$151.45 | \$148.48 |&#xA;| Wednesday | \$155.18 | \$156.65 |&#xA;| Thursday  | \$161.57 | \$160.00 |&#xA;| Friday    | \$158.27 | \$155.47 |&#xA;| Saturday  | \$139.68 | \$136.49 |&#xA;| Sunday    | \$162.16 | \$161.00 |&#xA;&#xA;### NYC&#xA;&#xA;| Day       | From     | To       |&#xA;|-----------|----------|----------|&#xA;| Monday    | \$161.94 | \$161.81 |&#xA;| Tuesday   | \$161.35 | \$163.19 |&#xA;| Wednesday | \$161.08 | \$160.20 |&#xA;| Thursday  | \$170.15 | \$170.40 |&#xA;| Friday    | \$169.09 | \$166.63 |&#xA;| Saturday  | \$144.02 | \$149.75 |&#xA;| Sunday    | \$172.48 | \$152.12 |&#xA;&#xA;### SFO&#xA;&#xA;| Day       | From     | To       |&#xA;|-----------|----------|----------|&#xA;| Monday    | \$151.42 | \$160.71 |&#xA;| Tuesday   | \$156.91 | \$155.49 |&#xA;| Wednesday | \$158.25 | \$165.31 |&#xA;| Thursday  | \$175.48 | \$173.05 |&#xA;| Friday    | \$152.99 | \$161.57 |&#xA;| Saturday  | \$146.91 | \$144.94 |&#xA;| Sunday    | \$162.94 | \$162.26 |&#xA;&#xA;## Interpretation and Conclusion&#xA;&#xA;Without a doubt, the fact that LA and San Francisco are so close to each&#xA;other definitely had an effect on the final results. The cities I chose&#xA;were, more or less, based on my personal travel habits, so it’s highly&#xA;likely that someone with a wildly different grouping of “home airports”&#xA;would get different results.&#xA;&#xA;No matter what, though, it seems that Saturday is the cheapest day to&#xA;fly, and Thursday is the most expensive. For the best deals, you should&#xA;buy your tickets from 6:00-8:00am PST. For the cheapest trip, plan for&#xA;something in mid-May.&#xA;&#xA;If you’d like to check out the dataset, or have any questions about how&#xA;I created these graphs, please don’t hesitate to send me an email.&#xA;</content>
    <link href="https://dlo.me//archives/2010/08/01/flying-on-the-cheap/" rel="alternate"></link>
    <summary type="html">Looking to go on a flight soon? I crowdsourced data using Mechanical Turk to figure out the best times to purchase tickets. Check out the results in this post.</summary>
  </entry>
  <entry>
    <title>Introspection in Vim</title>
    <updated>2010-07-13T00:00:00Z</updated>
    <id>tag:dlo.me,2010-07-13://archives/2010/07/13/introspection-python-vim/</id>
    <content type="html">As much as I like my [Vim](http://www.vim.org), there are times when I&#xA;want it to be just a little cooler. So in XCode, for example, there’s&#xA;this nice introspection feature that I’ve been dying to have for a while&#xA;now.&#xA;&#xA;![](/images/autocomplete.png)&#xA;&#xA;Needless to say, I couldn’t stop myself from bringing this functionality&#xA;to Vim. Here is the finished result:&#xA;&#xA;![](/images/autocomplete-vim.png)&#xA;&#xA;If pictures aren’t enough, you can also [watch a&#xA;movie](http://vimeo.com/13312423) if you’re still not convinced :).&#xA;&#xA;## Instructions&#xA;&#xA;1\. Install&#xA;[snippetsEmu](http://www.vim.org/scripts/script.php?script_id=1318) to&#xA;`~/.vim/plugins/`.&#xA;&#xA;2\. Download&#xA;[introspection.vim](http://dlo.s3.amazonaws.com/downloads/introspect.vim)&#xA;and also move it to `~/.vim/plugins/`.&#xA;&#xA;3\. (optional) Add `autocmd BufEnter *.py call FilterFile()` to your&#xA;`.vimrc`. This calls the introspection function whenever you open a&#xA;buffer containing a Python file.&#xA;&#xA;## Usage&#xA;&#xA;1\. Type up a function. E.g.&#xA;&#xA;    &lt;code&gt;&#xA;    def insert_into_db(name, company):&#xA;        pass&#xA;    &lt;/code&gt;&#xA;&#xA;2\. Save the file and type `:edit!`.&#xA;&#xA;3\. Now you can type `insert_into_db&lt;tab&gt;` and a snippet will appear.&#xA;Use `&lt;tab&gt;` to navigate to the next field.&#xA;&#xA;4\. [Fork the code on Github](http://github.com/dlo/introspect.vim) and&#xA;get it working for other languages!&#xA;</content>
    <link href="https://dlo.me//archives/2010/07/13/introspection-python-vim/" rel="alternate"></link>
    <summary type="html">A little Vim plugin I wrote that adds introspection to Python code.</summary>
  </entry>
  <entry>
    <title>Distribution of Airline Ticket Prices</title>
    <updated>2010-05-23T00:00:00Z</updated>
    <id>tag:dlo.me,2010-05-23://archives/2010/05/23/flight-ticket-distribution/</id>
    <content type="html">A few months back I did some research into ticket prices from and to&#xA;four major cities in the U.S.: Miami, San Francisco, Los Angeles, and&#xA;New York. I promised to release the results but just never got around to&#xA;it. Because I want to give you all **something** to look at, behold this&#xA;plot I made with the aggregate information between the four cities.&#xA;&#xA;The y-axis is the ticket price in dollars.&#xA;&#xA;I’ll get some more detailed analysis out of the data by the end of the&#xA;week. Promise promise promise.&#xA;&#xA;&lt;img src=&#34;/images/flights-prices-dates-1.png&#34; class=&#34;bigimage&#34; alt=&#34;&#34; /&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2010/05/23/flight-ticket-distribution/" rel="alternate"></link>
    <summary type="html">A small chart that shows the best times to buy airline tickets.</summary>
  </entry>
  <entry>
    <title>Relative Line Numbers in Vim</title>
    <updated>2010-05-20T00:00:00Z</updated>
    <id>tag:dlo.me,2010-05-20://archives/2010/05/20/vim-with-relative-line-numbers/</id>
    <content type="html">![](https://www.vim.org/images/vim_header.gif)&#xA;&#xA;I’ve been waiting for ages for relative line numbering (a patch that’s&#xA;been available for quite a while) to be officially merged into&#xA;[Vim](http://code.google.com/p/vim/). Well, that day has come :).&#xA;&#xA;Why would you want relative line numbers for your editor? Well, for one,&#xA;it makes those commands dependent upon line movement exponentially&#xA;easier. Instead of having to guess how many lines up or down you may&#xA;want to perform a command over, you’ve got the answer right in front of&#xA;you. Check it out!&#xA;&#xA;![](/images/relative.png)&#xA;&#xA;I’m running Snow Leopard, hence the odd `ARCHFLAGS=&#39;-arch x86_64&#39;`&#xA;prefix. I have a few libraries that have been compiled for both 32-bit&#xA;and 64-bit architectures, and if I don’t tell Vim which ones to use, it&#xA;throws a hissy fit.&#xA;&#xA;```bash&#xA;$ hg clone https://vim.googlecode.com/hg/ vim  &#xA;$ cd vim  &#xA;$ hg checkout vim73  &#xA;$ env ARCHFLAGS=’-arch x86_64’ ./configure —enable-pythoninterp \\  &#xA;—enable-rubyinterp —disable-gui  &#xA;$ env ARCHFLAGS=’-arch x86_64’ make  &#xA;$ sudo make install  &#xA;```&#xA;&#xA;This installs your vim in `/usr/local/bin/vim` by default. To turn on&#xA;relative line numbering, just run `:set rnu`. Predictably, to turn it&#xA;off, use `:set nornu`.&#xA;</content>
    <link href="https://dlo.me//archives/2010/05/20/vim-with-relative-line-numbers/" rel="alternate"></link>
    <summary type="html">Vim (the text editor) now support relative line numbering. This post will explain how to use it.</summary>
  </entry>
  <entry>
    <title>OpenProvider</title>
    <updated>2010-05-07T00:00:00Z</updated>
    <id>tag:dlo.me,2010-05-07://archives/2010/05/07/openprovider-a-distributed-social-protocol/</id>
    <content type="html">## Intro&#xA;&#xA;Airplane rides are always a good time for reflection—there aren’t really&#xA;any distractions (unless you’re unlucky enough to be sitting next to a&#xA;crying baby) and the white noise of the engines provides for a nearly&#xA;perfect brainstorming environment.&#xA;&#xA;The main thing on my mind last weekend was the current state of the&#xA;social web—how it’s broken and how best to “fix” it.&#xA;&#xA;## Facebook&#xA;&#xA;One of the main problems that people have with the system as it is today&#xA;is that the state of your information is currently in flux—there is no&#xA;way to tell if the service that you’re using today will have the same&#xA;privacy policy as it will tomorrow. [Facebook](http://www.facebook.com/)&#xA;is especially guilty of this alarming trend and others are likely to&#xA;follow. For an illustrative timeline of Facebook’s privacy policy, check&#xA;out the [post from the&#xA;EFF](http://www.eff.org/deeplinks/2010/04/facebook-timeline/).&#xA;&#xA;The [F8 conference](http://www.facebook.com/f8/) two weeks ago unveiled&#xA;Facebook’s intention to own everything you do on the Internet, from the&#xA;movies you watch on&#xA;[IMDB](http://developers.facebook.com/showcase/entertainment?p=imdb) to&#xA;the music you listen to on&#xA;[Pandora](http://developers.facebook.com/showcase/entertainment?p=pandora).&#xA;Understandably, a lot of people involved in the tech industry have been&#xA;[deleting their Facebook&#xA;accounts](https://ssl.facebook.com/help/contact.php?show_form=delete_account)&#xA;left and right. There is a lot of misunderstanding regarding *why* this&#xA;is happening, and no, it isn’t because of the lack of privacy. The&#xA;reason is that Facebook was sold to us on the promise that we would just&#xA;be sharing our information with our friends and family, and not with&#xA;advertisers and “partners”.&#xA;&#xA;## The Plan&#xA;&#xA;For all the reasons mentioned in addition to many others, there has been&#xA;an enormous push to change the way we think about social networking over&#xA;the past few days. One project that’s gotten a lot of traction is&#xA;[diaspora](http://www.joindiaspora.com/), a soon-to-be open source&#xA;project being championed by 4 computer science students at NYU. No one&#xA;has any real idea on what it is or how it will work, but I’m sure that&#xA;we’ll be hearing more about them soon.&#xA;&#xA;I do think what they’re doing is awesome, and I hope it works. I just&#xA;wanted to get my thoughts down on paper before I forgot about them (as I&#xA;frequently do). What follows is my plan for a distributed social&#xA;protocol.&#xA;&#xA;First of all, I’m going to go over the “must haves”—things that the&#xA;ideal social network should have, whether it be distributed or&#xA;centralized.&#xA;&#xA;1.  Accessibility (or Virality)&#xA;2.  Privacy&#xA;3.  Low barrier to entry&#xA;4.  Portability&#xA;&#xA;### Accessibility and Virality&#xA;&#xA;The first step in using a social network appropriately is finding your&#xA;friends, and there’s no way to do this if all the searchable information&#xA;is hidden. Complying with \#4 above makes this extra difficult.&#xA;&#xA;Well, there’s a way to solve it at the expense of losing “fuzzy search”,&#xA;and that solution is hashing. A hash function maps a piece of data into&#xA;a smaller, more manageable chunk, and may or may not be irreversible.&#xA;E.g.&#xA;&#xA;```&#xA;&gt;&gt;&gt; sha1(&#34;dloewenherz@gmail.com&#34;).hexdigest()&#xA;c4080eb3969b7c95c5e38d563e15bd2407e35153&#xA;```&#xA;&#xA;That value you see is not reversible. There is no function that will&#xA;take `c4080eb3969b7c95c5e38d563e15bd2407e35153` as a parameter and will&#xA;return dloewenherz@gmail.com. It’s never been done, and I don’t suspect&#xA;it will be. Hashing email addresses in this manner will hide them to&#xA;spammers while at the same time allowing people to search for them.&#xA;&#xA;“How can you search for something that you can’t decode?”, you ask.&#xA;Well, the cool thing is that when people search for you, the search&#xA;query is itself encoded in the same way, so if the encoded search query&#xA;is equivalent to the encoded email address, then there’s a match! For&#xA;more info on hashing, check out&#xA;[Wikipedia](http://en.wikipedia.org/wiki/Hash_function) on the subject.&#xA;&#xA;This leads into my proposal that there be a global identity database&#xA;that is accessible and downloadable by anyone (addresses \#3 above). It&#xA;will store the encoded email (as described), plus two other pieces of&#xA;information necessary to complete the puzzle.&#xA;&#xA;(code)\* `email`&#xA;&#xA;- `provider`&#xA;&#xA;So what’s a “provider”, you may ask? A provider is a social web service,&#xA;like Facebook or Twitter, that controls a user’s information.&#xA;&#xA;If the maintainers of the central node start turning to the dark side,&#xA;well, everyone can and will abandon them, as others already have access&#xA;to the data. `provider` is in itself worthless to a spammer.&#xA;&#xA;### Connections&#xA;&#xA;A connection is a one-way link between two users, regardless of the&#xA;provider. So if I have an account with provider A and my friend has an&#xA;account with provider B, I will still be able to connect with him. In&#xA;essence, all profiles function like public Twitter profiles. Private&#xA;profiles sound like a good idea but I’m at a total loss as to how they&#xA;might be implemented.&#xA;&#xA;A user can only have an account with one provider at a time.&#xA;&#xA;After I make a connection with someone, that connection is stored in my&#xA;browser. Whenever I log into my provider, it pulls the follower info&#xA;from the browser and updates my profile with the appropriate&#xA;information. For browsers that aren’t compatible, you can simply store&#xA;all the information on a flat file on your computer, and upload it to&#xA;your provider whenever you decide to change. Providers themselves can&#xA;store this info. Providers should allow for export and import of&#xA;follower data. If they don’t, the users can leave.&#xA;&#xA;The great thing about this system is that it prevents any chance of a&#xA;bait-and-switch. Users will already have access to their connection data&#xA;and can just start using another provider in the unlikely chance that it&#xA;does happen.&#xA;&#xA;### Privacy&#xA;&#xA;So all we’ve got now are connections. Now what?&#xA;&#xA;Connections link profiles to profiles. It’s up to each provider&#xA;regarding what information to expose to the world at large, but it all&#xA;follows the same basic protocol. So *no matter what*, if I go to&#xA;&#xA;```&#xA;http://www.example.org/profile/?id=1234567&#xA;&amp;format=json&#xA;&amp;key=6b7894e44e440f83e3fa4be3a6d98961c70d6f2f&#xA;```&#xA;&#xA;I’ll get back something like&#xA;&#xA;```json  &#xA;{&#34;profile&#34;: {&#34;first_name&#34;: &#34;Dan&#34;, &#34;last_name&#34;: &#34;Loewenherz&#34; }}  &#xA;```&#xA;&#xA;The `key` is my email address (dloewenherz@gmail.com) plus a&#xA;[cryptographic salt](http://en.wikipedia.org/wiki/Salt_(cryptography)).&#xA;The salt to use is something that the community as a whole could agree&#xA;upon. Using an access key like the one above ensures that only people&#xA;who know my email address can access my profile information. Nifty, huh?&#xA;&#xA;“So that all sounds cool, but hold on a second,” you say. “Didn’t you&#xA;just say that it’s a huge problem that certain providers control our&#xA;profile info?”.&#xA;&#xA;Well, the answer is yes, I did. Kind of. But I left something out.&#xA;Today, if you leave Facebook, you lose all of your connections to your&#xA;friends. The connections you’ve spent time “making” online are really&#xA;what’s valuable—not the personal information you post to your profile.&#xA;Let’s not kid ourselves, Facebook can guess most of what we think is&#xA;private *just* from our connections (see [Project&#xA;Gaydar](http://www.boston.com/bostonglobe/ideas/articles/2009/09/20/project_gaydar_an_mit_experiment_raises_new_questions_about_online_privacy/)&#xA;for a real example).&#xA;&#xA;The power to control the connections is the only power that really&#xA;matters. And that’s already been addressed. If a provider chooses to not&#xA;let you export profile info, then that’s their prerogative. And it’s&#xA;also the user’s prerogative to leave them.&#xA;&#xA;## Conclusion&#xA;&#xA;Social networking is still in its infancy, and it shows. Even though&#xA;most of what I went over in this post was extremely “high-end” and&#xA;abstract, I think I make some valid points and hope it leads into more&#xA;detailed discussion of how something like this might work. I think&#xA;there’s something to it.&#xA;</content>
    <link href="https://dlo.me//archives/2010/05/07/openprovider-a-distributed-social-protocol/" rel="alternate"></link>
    <summary type="html">An ill-fated plan on mine to revamp the way social networking works on the Internet today.</summary>
  </entry>
  <entry>
    <title>Why Piracy is Sometimes Good For the Software Industry</title>
    <updated>2010-02-27T00:00:00Z</updated>
    <id>tag:dlo.me,2010-02-27://archives/2010/02/27/why-piracy-is-sometimes-good-for-the-software/</id>
    <content type="html">This morning at the gym I started thinking about how nice it would be to be a Photoshop guru.&#xA;&#xA;The ability to just have an idea of an image of design in your head, open up Photoshop and come out with a snazzy-looking result after a few minutes of hard work. It would be frickin&#39; awesome. Unfortunately all the tools I really have at my disposal for design work are HTML + CSS. Bummer.&#xA;&#xA;And then there&#39;s the cost.&#xA;&#xA;A copy of [Photoshop CS4](http://www.adobe.com/products/photoshop/compare/) (the latest version in Adobe&#39;s suite) goes for $699 if you&#39;re buying it fresh off the presses. The extended version will burn a grand in your pocket.&#xA;&#xA;That&#39;s way too expensive. Especially if you just want to play around (like I do). Which leads to why so many people download Photoshop off of Bittorrent. Free is free. Honestly, how many Photoshop gurus actually bought the damn thing when they were first curious about it? I doubt very many.&#xA;&#xA;I don&#39;t know why this is what first came to me, but I thought for a second that maybe Adobe wants its software to be pirated. Look at it this way: maybe Adobe charging such an exorbitant amount for its software is just a form of [price discrimination](http://en.wikipedia.org/wiki/Price_discrimination).&#xA;&#xA;They know that businesses will fork over the price as long as its not ridiculous. Hell, if you&#39;re a design firm you need Photoshop. You bite your lip, buy the software, hire a great designer (who probably learned Photoshop off of a pirated version), and move on. It sucks, but no company wants to be using pirated software. It&#39;s just not a good idea.&#xA;&#xA;If you don&#39;t have the money, you&#39;ll just download it.&#xA;&#xA;Here&#39;s a little example to illustrate why Adobe wants people to pirate their software. [Fixed costs](http://en.wikipedia.org/wiki/Fixed_cost) for software production is very, very high (programmers are expensive ;) ). On the other hand, [variable costs](http://en.wikipedia.org/wiki/Variable_Costs) are basically nil (you&#39;re basically paying for the cost of bandwidth if its download only, and the cost of shipping otherwise). A software company can charge nearly nothing for software given that people will continue to buy it forever into the future.&#xA;&#xA;Other products, like jet engines, have a high variable cost, so it would be pretty stupid to charge a penny for one. You&#39;d go out of business in the blink of an eye.&#xA;&#xA;Now let&#39;s imagine that Adobe charged $9.99 for Photoshop CS4. I would buy it without a second thought. So would maybe other people who wanted to get their feet wet in design. Flipside: Adobe now needs to ship 70 times more copies.&#xA;&#xA;This is a bad business decision if more than 1 of 70 Photoshop installations are legal. I&#39;m actually inclined to say that the number is higher. In the end, it&#39;s better business sense to keep the price at an astronomically high value and let the rest of us download the thing on Bittorrent. It&#39;s a small price to pay, but Adobe makes more money in the end.&#xA;&#xA;What do you think?</content>
    <link href="https://dlo.me//archives/2010/02/27/why-piracy-is-sometimes-good-for-the-software/" rel="alternate"></link>
    <summary type="html">Thoughts on why piracy might be a good thing for the software industry after all.</summary>
  </entry>
  <entry>
    <title>Is the iPad a mistake?</title>
    <updated>2010-02-09T00:00:00Z</updated>
    <id>tag:dlo.me,2010-02-09://archives/2010/02/09/is-ipad-mistake/</id>
    <content type="html">[The iPad](http://www.apple.com/ipad/) was announced about a week and a half ago to great fanfare amongst both the tech community and the world at large. It has been hailed as the computer for people who don&#39;t know how to use computers.&#xA;&#xA;What a thought.&#xA;&#xA;I really applaud [Apple](http://apple.com/) for this achievement. They&#39;ve really gotten close to pushing something out to market what I would like to call the &#34;sexagenarian-proof computer&#34;. This will be the computer that will not longer be called a computer by those who use it. That&#39;s probably a good thing, because mostly everything about the iPad is antithetical to everything that computers have been up until today.&#xA;&#xA;If you&#39;re around my age and ever had any interest in the Internet, you probably remember when you made your first website. You opened up your [text editor](&lt;http://en.wikipedia.org/wiki/Notepad_(Windows)&gt;), searched [Altavista](http://altavista.com/) or [Yahoo](http://yahoo.com/) for &#34;how to make a website&#34;, and were up on Geocities within the week.&#xA;&#xA;I did this when I was younger, and one of the reasons I did do it was because the barrier to entry was so incredibly low. There was nothing stopping me. If I was a kid with an iPad, the barrier to actual development would be so high that I&#39;d most likely not consider it.&#xA;&#xA;If the iPad is a harbinger of things to come, I&#39;ll be very uncomfortable, mostly because the kid in me probably wouldn&#39;t have made that first website. From that first website came my first interest in programming, and then my interest in Java, etc, etc. Each step of the way requires that your computer be incrementally more capable. And tinkerable. Hell, I&#39;d be a frustrated kid if I couldn&#39;t figure out how to write a program on my iPad. I&#39;d be even more frustrated since it would cost [$99 for my parents to fork over](http://developer.apple.com/iphone/program/) just so I could &#34;play around&#34; (that is the actual cost of the SDK for those who are interested in whether I&#39;m simply pulling numbers out of my head).&#xA;&#xA;I believe that computers are meant to be democratic devices -- machines that anyone can use and do anything with. If it ever gets to the point when our computers are owning us rather than the other way around, the iPad will have won. Let&#39;s hope it doesn&#39;t.&#xA;</content>
    <link href="https://dlo.me//archives/2010/02/09/is-ipad-mistake/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Random Queries on the Appengine Datastore</title>
    <updated>2010-01-25T00:00:00Z</updated>
    <id>tag:dlo.me,2010-01-25://archives/2010/01/25/querying-for-n-random-entities-using/</id>
    <content type="html">First, this post is 100% programming related. So if you don&#39;t care about programming, just walk away.&#xA;&#xA;Chances are that if you&#39;re a Python or Java developer, you&#39;ve run into Google Appengine at one point or another. A perennial issue with the datastore is that there&#39;s no officially documentation on how to pull random entities from your database. Well friends, I&#39;d like to let you in on a little secret for how you can do this.&#xA;&#xA;It&#39;s quite simple, actually. Let&#39;s say you have a simple model, called Users. E.g.&#xA;&#xA;```python&#xA;from google.appengine.ext import db&#xA;&#xA;class User(db.Model):&#xA;    first_name = db.StringProperty()&#xA;    last_name = db.StringProperty()&#xA;    email = db.EmailProperty()&#xA;```&#xA;&#xA;Make another model called UserCount (this model will only have one entity, and the count will be equivalent to the number of Users that have been inserted into the database). Instantiate a UserCount entity with an initial count of 0 before you do anything.&#xA;&#xA;```python&#xA;class UserCount(db.Model):&#xA;    count = db.IntegerProperty(default = 0)&#xA;```&#xA;&#xA;Simple so far. Do something similar to this when you insert a User into the database.&#xA;&#xA;```python&#xA;uc = UserCount.get_by_key_name(&#34;1&#34;)&#xA;uc.count += 1&#xA;uc.put()&#xA;&#xA;u = User(key_name = str(uc.count), first_name = &#34;Steve&#34;, last_name = &#34;Jobs&#34;,&#xA;        email = &#34;sjobs@apple.com&#34;)&#xA;```&#xA;&#xA;To get random data, use Python&#39;s random module, and call the User&#39;s by their key names. Caching is recommended. You may want to do something similar to the below.&#xA;&#xA;```python&#xA;from random import sample&#xA;from google.appengine.api import memcache&#xA;&#xA;uc = UserCount.get_by_key_name(&#34;1&#34;)&#xA;N = 5&#xA;&#xA;ids = sample(range(1, uc.count+1), N)&#xA;&#xA;users = []&#xA;for id in ids:&#xA;    user = memcache.get(&#34;user_%d&#34; % id)&#xA;    if not user:&#xA;        user = User.get_by_key_name(str(id))&#xA;    users.append(user)&#xA;```&#xA;&#xA;And that is how to get random entities from the Google Appengine datastore.&#xA;</content>
    <link href="https://dlo.me//archives/2010/01/25/querying-for-n-random-entities-using/" rel="alternate"></link>
    <summary type="html">Need a way to get random entities out of the Google Appengine Datastore? Here&#39;s a solution that should get you running in no time.</summary>
  </entry>
  <entry>
    <title>When to Buy Airline Tickets</title>
    <updated>2010-01-24T00:00:00Z</updated>
    <id>tag:dlo.me,2010-01-24://archives/2010/01/24/when-to-buy-airline-tickets/</id>
    <content type="html">This weekend I&#39;ve been mulling over how annoying it was that there isn&#39;t really freely available data on prices for airline tickets. [Farecast](http://farecast.com), the one site that I was aware of that did *something* like this, was purchased by Microsoft and is now a part of [Bing Travel](http://bing.com/travel).&#xA;&#xA;So what separates the market for airline tickets and others is that ticket prices are extremely sporadic from day-to-day and even by the hour. This makes getting a really good deal on airline tickets very difficult for the average consumer. There&#39;s only one participant in the transaction that gets a good deal, and that is the airline. Their pricing schemes are actually quite ingenious when you begin to think about it.&#xA;&#xA;Typically, an airline will have multiple pricing ranges for seats on the same flights. That is, 10% of seats will be $50-100, 40% will be $100-$150, and 50% will be &gt;$150.&#xA;&#xA;The gist of price discrimination is that there are usually people who are willing to pay more than you are for the same product or service. Instead of charging everyone a flat rate, a company will vary the price based on the customer. Simply put, movie theaters don&#39;t have senior and student discounts because they&#39;re particularly nice, they do it because they make more money this way :). See [Wikipedia](http://en.wikipedia.org/wiki/Price_discrimination) for more information.&#xA;&#xA;Alright, back to the meat and potatoes.&#xA;&#xA;After some thought, I decided [Mechanical Turk](http://mturk.com) would be the best way to gather this info. So I got down and wrote a Python script that randomly generated 500 origin cities, destination cities, and dates within the next 329 days (it seems most airlines don&#39;t give price information past this amount of time). The cities were only the ones I was interested in (Miami, LA, NYC, and San Francisco).&#xA;&#xA;I set up the job, uploaded the batch of data, and salivated throughout the night. When I woke up today, I got down to analyzing the data.&#xA;&#xA;Some interesting patterns emerged.&#xA;&#xA;First of all, Wednesday is (on average), the most expensive day to travel, unless you&#39;re flying from LA, where a Thursday ticket will cost you on average $500 to Miami, NYC, or SF. Miami was the worst city to fly from, with the most expensive day costing a traveler about 564 bucks.&#xA;&#xA;![Average prices by city and day of the week](http://chart.apis.google.com/chart?chd=t:181.97,130.41,563.89,161.72,186.17,337.64,394.90|191.78,295.23,231.84,126.45,487.75,308.84,216.64|154.47,208.92,433.63,190.5,160.23,179.96,211.60|145.98,223.57,307.36,261.38,166.19,246.40,213.23&amp;cht=bvg&amp;chds=0,600&amp;chl=Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday&amp;chxt=y&amp;chxr=0,0,600,100&amp;chco=468966,FFB03B,B64926,8E2800&amp;chdl=Miami|Los%20Angeles|NYC|San%20Francisco&amp;chs=650x200&amp;chbh=10,5,15)&#xA;&#xA;Here are average weekly ticket prices between the four cities over this year.&#xA;&#xA;![Average weekly ticket prices](http://chart.apis.google.com/chart?chd=t:288.00,172.25,225.99,161.50,130.50,158.38,169.00,109.13,156.40,198.00,138.43,214.09,167.31,110.59,188.17,118.14,156.45,150.99,183.25,163.00,139.61,211.20,212.42,162.13,180.33,159.75,168.99,187.56,152.27,146.78,201.54,154.20,179.33,148.21,132.62,191.88,219.86,187.63,165.88,169.75,206.58,162.15,185.50,197.83,185.14,193.30,176.30,186.2&amp;cht=ls&amp;chl=Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec&amp;chxt=y&amp;chxr=0,0,400,100&amp;chco=4D89F9&amp;chs=400x75&amp;chds=0,350&amp;chm=B)&#xA;&#xA;Prices appear to be highest for the next week, then hit a yearly low in late February, when the average ticket price hits $109.13. Cheap. Prices are a little unpredictable over the summer, hit highs in early July and late September, and plateau over the holiday season. We&#39;ll see how much of this is due to the day that this data was gathered.&#xA;&#xA;How about cheapest airlines? Well, you may be surprised about this as well.&#xA;&#xA;| Airline            | Number of tickets |&#xA;|--------------------|-------------------|&#xA;| Delta Air Lines    | 100               |&#xA;| United Airlines    | 62                |&#xA;| AirTran Airways    | 54                |&#xA;| US Airways         | 36                |&#xA;| Alaska Airlines    | 27                |&#xA;| Virgin America     | 20                |&#xA;| Continental Airlines | 10              |&#xA;| Frontier Airlines  | 7                 |&#xA;| JetBlue Airways    | 7                 |&#xA;| Spirit Airlines    | 3                 |&#xA;| Midwest Airlines   | 1                 |&#xA;| Other              | 1                 |&#xA;&#xA;Southwest didn&#39;t make it *once*! Now that was a surprise.&#xA;&#xA;This is only the tip of the iceberg. Over the next week, I&#39;ll keep listing HITs on Mechanical Turk, and should have some more robust data on *when* the best times to buy tickets are. Stay tuned...</content>
    <link href="https://dlo.me//archives/2010/01/24/when-to-buy-airline-tickets/" rel="alternate"></link>
    <summary type="html">Looking to go on a flight soon? I crowdsourced data using Mechanical Turk to figure out the best times to purchase tickets. Check out the results in this post.</summary>
  </entry>
  <entry>
    <title>My Social News Escape: Day Four</title>
    <updated>2010-01-09T00:00:00Z</updated>
    <id>tag:dlo.me,2010-01-09://archives/2010/01/09/my-social-news-escape-day-4/</id>
    <content type="html">&#xA;Four days has gone by and I haven&#39;t visited [Reddit](http://reddit.com/), [Digg](http://digg.com/), or HN.&#xA;&#xA;When I originally started this experiment, I thought that by this time I would be struggling to make it through the day without a burning desire to start typing &#34;redd&#34; into my address bar. Strangely enough, this hasn&#39;t yet happened to me. In fact, I have basically forgotten about social news altogether and am have shifted over primarily to Twitter and Google Reader.&#xA;&#xA;I&#39;ve noticed that if you follow reliable people and subscribe to good blogs, your sources of information will usually be well-vetted and there isn&#39;t much of a need to &#34;filter&#34; the useless junk. I think that by the time this experiment ends, I may end up sticking to it.&#xA;</content>
    <link href="https://dlo.me//archives/2010/01/09/my-social-news-escape-day-4/" rel="alternate"></link>
  </entry>
  <entry>
    <title>My Social News Escape: Day Two</title>
    <updated>2010-01-06T00:00:00Z</updated>
    <id>tag:dlo.me,2010-01-06://archives/2010/01/06/my-social-news-escape-day-two/</id>
    <content type="html">I vowed on Monday not to visit either Hacker News or Reddit.com for a week starting from yesterday. And my god has it been painful.&#xD;&#xA;&#xD;&#xA;I can hardly believe it&#39;s already been almost 48 hours. What&#39;s most unbelievable to me is how much more &#34;stuff&#34; I have gotten done without the constant urge to check out what&#39;s going on in the social-news-o-sphere. In many ways I feel like I&#39;m a little disconnected, but in other ways I feel completely free.&#xD;&#xA;&#xD;&#xA;The Internet is addicting. Social news is addicting. The reason is simple. People want to be entertained and spoon-fed. We have become very dependent on having other people give us information...ANY information, even if it&#39;s completely irrelevant to our daily life.&#xD;&#xA;&#xD;&#xA;Some may argue (with good reason, might I add) that almost everything is relevant in some sense. We always have room to learn about the people around us and the events that are occurring. Unfortunately, though, the difference between news and entertainment has begun to blur beyond recognition.&#xD;&#xA;&#xD;&#xA;This is one of the problems with social news. Posts such as &#34;[What is something really embarrassing you have done, but never](http://www.reddit.com/r/AskReddit/comments/ad1n5/what_is_something_really_embarrassing_you_have/)&#34; are put right alongside posts like &#34;[*IRAN*: Video shows gunman opening fire on demonstrators, who fight](http://www.reddit.com/r/worldnews/comments/akurc/iran_video_shows_gunman_opening_fire_on/)&#34;. Who is there to separate the crap from the good stuff? That&#39;s right, YOU! BTW, I am not allowing myself to click those links. I found them through Google.&#xD;&#xA;&#xD;&#xA;Reading these submissions is time consuming, and so what usually happens is that even if you don&#39;t particularly care about an IAmA about a zookeeper, you&#39;ll probably find yourself clicking on the link just to see what all the fuss is about. It&#39;s so easy, right?&#xD;&#xA;&#xD;&#xA;Right, but there&#39;s a cost. There is an almost constant distraction and urge to just &#34;click&#34;.&#xD;&#xA;&#xD;&#xA;Stop the clicking and start the doing. I guess that&#39;s the lesson for today.</content>
    <link href="https://dlo.me//archives/2010/01/06/my-social-news-escape-day-two/" rel="alternate"></link>
  </entry>
  <entry>
    <title>OpenID, Clickpass, web.py, and Python</title>
    <updated>2010-01-03T00:00:00Z</updated>
    <id>tag:dlo.me,2010-01-03://archives/2010/01/03/openid-clickpass-webpy-and-python/</id>
    <content type="html">I just spent a few hours working on implementing [Clickpass](http://clickpass.com/) with my new project built on [web.py](http://webpy.org/), so I figured I could save everyone else some time by giving some tips on how to fast forward to the actual development of what it is you want to make instead of tweaking configuration files.&#xA;&#xA;[OpenID](http://openid.net/) is nearly ubiquitous at this point. If you don&#39;t think you have an OpenID login, you probably do and just don&#39;t know it. [Google](http://www.readwriteweb.com/archives/google_is_now_an_openid_provider.php), [Yahoo](http://openid.yahoo.com/), [Facebook](http://developers.facebook.com/news.php?blog=1&amp;story=246), and others have already implemented the protocol into their backends.&#xA;&#xA;What does that mean?&#xA;&#xA;Well, for one, as someone who&#39;s starting a new website or a web application, you don&#39;t need to worry about storing passwords or causing potential users a small annoyance by having to fill in yet another registration form.&#xA;&#xA;This is how login works in an OpenID world: a user goes to a website and clicks a &#34;Login with OpenID&#34; button. They are then redirected to a choice of providers (e.g. Google, Yahoo) that run all of their user accounts as OpenID accounts as well. You click your provider (let&#39;s say Google) and are then asked one more time whether it&#39;s OK to login with Google. You click sure, and you&#39;re brought back to the website and, &#34;ta da&#34;, you&#39;re now logged in.&#xA;&#xA;This all seems really dandy from the user&#39;s perspective. Unfortunately there&#39;s a ton of developer mumbo-jumbo on the backend side that will make most developers puke. You can read the [specs](http://openid.net/specs/openid-authentication-2_0.html) if you&#39;d like, but I recommend that you don&#39;t. Chances are that you won&#39;t be able to finish.&#xA;&#xA;As a consumer (website owner), you will need a way to authenticate users with OpenID. I chose Clickpass. They offer a nice interface with a couple of choices for providers. With [Clickpass](http://clickpass.com/), you will need to make (at minimum) two URLs to facilitate the logins. One will begin the authentication process, and another will complete it.&#xA;&#xA;Now for the fun part: implementation. I can&#39;t give an example use in every language (that would just be a waste of time), but I can show you how this entire process is done in [Python](http://python.org/), [web.py](http://webpy.org/), and the [python-openid library](http://www.openidenabled.com/openid/libraries/python). If you want me to walk you through the entire installation process, let me know in the comments and I&#39;ll write a follow up post.&#xA;&#xA;Without further adieu, here is the working code. Be sure that you sign up with Clickpass and receive a site key. Replace all the variables with the appropriate values.&#xA;&#xA;```python&#xA;#!/usr/bin/env python&#xA;&#xA;import web&#xA;from openid.consumer.consumer import *&#xA;&#xA;# Store OpenID session variables in memory&#xA;store = openid.store.memstore.MemoryStore()&#xA;&#xA;# Get this from the Clickpass website&#xA;clickpass_site_key = YOUR_SITE_KEY&#xA;&#xA;# Can be something like &#34;/openid/complete_login&#34;&#xA;complete_login_url = YOUR_COMPLETE_LOGIN_URL&#xA;&#xA;# Your base domain, e.g., &#34;http://google.com&#34;&#xA;realm = YOUR_REALM&#xA; &#xA;urls = (&#xA;    &#34;/&#34;, &#34;home&#34;,&#xA;    &#34;/begin_openid_login&#34;, &#39;begin_openid_login&#39;,&#xA;    &#34;/complete_openid_login&#34;, &#39;complete_openid_login&#39;&#xA;    )&#xA; &#xA;app = web.application(urls, locals())&#xA; &#xA;class home():&#xA;  def GET(self):&#xA;    return &#34;&#34;&#34;&lt;style&gt; @import&#xA;    &#39;http://www.clickpass.com/stylesheets/container.css&#39;; &lt;/style&gt; &lt;div&#xA;    id=&#34;clickpass_button&#34; style=&#34;width: 136px; height: 18px; position:&#xA;    relative; z-index : 9999 ;&#34;&gt; &lt;iframe&#xA;    src=&#34;http://www.clickpass.com/embedded_buttons/login/%s&#34; width=&#34;136&#34;&#xA;    height=&#34;18&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; scrolling=&#34;no&#34;&#xA;    style=&#34;z-index:9999;position: absolute; top: 0; left: 0;&#34;&gt;&lt;/iframe&gt; &lt;/div&gt;&#xA;    &lt;script type=&#34;text/javascript&#34;&#xA;    src=&#34;http://www.clickpass.com/javascripts/ClickpassPanel.class.external.js?v1&#34;&gt;&lt;/script&gt;&#xA;    &lt;script type=&#34;text/javascript&#34;&gt;  var clickpassPanel = new&#xA;    ClickpassPanel(&#39;clickpass_button&#39;);&lt;/script&gt;&#34;&#34;&#34; % clickpass_site_key&#xA; &#xA;class complete_openid_login():&#xA;  def GET(self):&#xA;    data = web.input()&#xA;    c = Consumer(web.ctx.session, store)&#xA;    result = c.complete(dict(data), current_url = complete_login_url)&#xA;    if result.status == SUCCESS:&#xA;      return result.identity_url&#xA;    elif result.status == FAILURE:&#xA;      return &#34;FAILURE&#34;&#xA;    elif result.status == CANCEL:&#xA;      return &#34;CANCEL&#34;&#xA; &#xA;class begin_openid_login():&#xA;  def GET(self):&#xA;    data = web.input()&#xA;    openid_url = data[&#39;openid_url&#39;]&#xA;    c = Consumer(web.ctx.session, store)&#xA;    auth = c.begin(openid_url)&#xA;    raise web.seeother(auth.redirectURL(realm, return_to = complete_login_url))&#xA;```</content>
    <link href="https://dlo.me//archives/2010/01/03/openid-clickpass-webpy-and-python/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Things that will save you time and money in Cancun</title>
    <updated>2010-01-02T00:00:00Z</updated>
    <id>tag:dlo.me,2010-01-02://archives/2010/01/02/save-time-money-cancun/</id>
    <content type="html">&#xA;I&#39;m vacationing in Cancun right now, a place where more than a million people a year visit year-round to get away from the daily grind. Cancun is a [ecological](http://www1.american.edu/TED/cancun.htm) disaster, so I don&#39;t really recommend you come here, but if you do, I have a few tips that you might enjoy.&#xA;&#xA;First of all, Cancun isn&#39;t off-the-wall expensive (at least nowhere near as expensive as Miami, LA, or Vegas). On the other hand, there are definitely times you&#39;ll have when you *know* that you&#39;re being ripped off. For example, discounts are everywhere if you look for them. After my mom and I reserved a fishing trip, the concierge handed us 15% discount cards for every single restaurant in the hotel (we stayed at the Marriott Casamagna). I can&#39;t really tell you if this will happen in every single hotel on the strip, but hey, it never hurts to ask ;).&#xA;&#xA;Another thing that will save you money is if you book in advance or on your own. Depending upon the travel agents in the hotels you stay at will cost you an extra 10-15% on top of the base price (this is considered a convenience fee). Do some Google searches for whatever it is that you&#39;ll want to do (swimming with the dolphins, deep-sea fishing, Chichen Itza). Most of the tours and trips have websites and will take reservations online.&#xA;&#xA;Also, if you&#39;re planning on doing a fishing trip, see if you can find a few other people who can join up with your group. You can then charter a boat on your own, which will cost considerably less than letting the company setting up the groups for you.&#xA;&#xA;Next, food. I recommend you drink the water, it&#39;s really OK. If you *really* don&#39;t trust the tap water, just remember to ask for no ice at restaurants (they don&#39;t make bottled water ice in case you&#39;re wondering). But seriously, no one I&#39;ve talked to has had any problems with the tap water. And it tastes fine too.&#xA;&#xA;Another thing. I stayed at the Hotel Casamagna. I had a few bad experiences here, if you want to know what they are, I&#39;ll write about them in a follow up.&#xA;&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2010/01/02/save-time-money-cancun/" rel="alternate"></link>
  </entry>
  <entry>
    <title>柚子 and 红牛 — the best complements to late night studying</title>
    <updated>2007-12-04T00:00:00Z</updated>
    <id>tag:dlo.me,2007-12-04://archives/2007/12/04/柚子-and-红牛-—-the-best-complements-to-late-night-studying/</id>
    <content type="html">&#xA;&lt;p&gt;In America, it seems that the idea of fruit vendors has in some ways died out–but here in China, it can be below freezing, with the wind hitting to the core of your bones, and the loyal sellers will still be outside selling their signature products. A few of these are pretty interesting, not things that you could find in a Gourmet Heaven near you.&lt;/p&gt;&#xA;&lt;p&gt;Think of a citrus four times the size of a grapefruit, that takes a butcher knife to cut open, and requires both hands to tear apart what I will refer to as its “hide”. This is a 柚子 (you4 zi), and until this very minute I had no idea that in English this is called a “pomelo” (if you’ve ever heard of this before, I’d be quite impressed, and maybe disappointed that you have no life). This fruit is pretty sweet, and according to my ex-roommate (another story), the hide keeps bugs away (upon his stating this fact, it was almost instantaneously proved false, as multiple flies swarmed us the moment we started eating these things).&lt;/p&gt;&#xA;&lt;p&gt;Day by day, my gut is being filled with an inexplicable urge to want to stay at this place that I’ve called home for the past six months. Everything here just seems to “work” so well. Maybe I’m overlooking some things, but life in China is pretty sweet.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/12/04/柚子-and-红牛-—-the-best-complements-to-late-night-studying/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Tired</title>
    <updated>2007-11-20T00:00:00Z</updated>
    <id>tag:dlo.me,2007-11-20://archives/2007/11/20/tired/</id>
    <content type="html">&#xA;&lt;p&gt;I can’t believe I only have one more month here, more or less. It’s crazy that I’ve basically been here since July, save for a week in Miami and Chicago.&lt;/p&gt;&#xA;&lt;p&gt;The days are getting a lot shorter and I am getting a lot more tired. This is probably because studying characters for hours on end has somehow become a “fun” way to spend my time over here. And ever with hours of studying every day for the past several months, I’ve yet to even come close to being “fluent” in Mandarin. Sure, I can communicate, and read a lot, but I’m still not at the point of near-total comprehension.&lt;/p&gt;&#xA;&lt;p&gt;Short post, but my dad and sister just came, gotta go.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/11/20/tired/" rel="alternate"></link>
  </entry>
  <entry>
    <title>The Computer is Working Again</title>
    <updated>2007-10-27T00:00:00Z</updated>
    <id>tag:dlo.me,2007-10-27://archives/2007/10/27/the-computer-is-working-again/</id>
    <content type="html">&#xA;&lt;p&gt;Whew. Excepting for the fact that I’ve been AWOL for the past two weeks, everything here is going great. My computer is finally working again after I bought a new hard drive at 中关村 across the street, although I did somehow manage to lose the video of Kirk talking abous 辫子s–that was truly an amazing clip. Unfortunately it was written over by another, less important, clip of a tour of a bathroom. Actually, I take that back, the bathroom clip is pretty sweet. I didn’t take it though, 新侃 did, so he gets all the credit.&lt;/p&gt;&#xA;&lt;p&gt;Anyways, the last week has been getting a hell of a lot colder, which especially sucks being that I don’t have that much cold weather clothing here. Looks like I’m going to need to buy some.&lt;/p&gt;&#xA;&lt;p&gt;Well, yesterday, 新侃 and I went to 师大 to meet up with one of my PiB teachers–刘安敏. We walked around the campus a bit and my roommate might have thrown too much information to her face about exactly the type of Chinese I had been learning lately. In fact I don’t even know the characters for these words so even if I wanted to write about them here I wouldn’t be able to. We went to her dorm, the entrance of which was the home of a prominent sign which forbade men from entering the women’s rooms. Damn. In any case, we sat down in the common room, if it could be called that, and talked for a little bit.&lt;/p&gt;&#xA;&lt;p&gt;We went to lunch not long after (actually, it was kind of long after, because I was the one asked to find a restaurant, and unfortunately the only restaurants I really know in the 师大 area are those that we all went to during the summer with our teachers for Chinese language table–this is excepting McDonald’s, of course). Then 新侃 forgot about some meeting that he had agreed to go to at 2, so we had to run out a bit early. Whatevs.&lt;/p&gt;&#xA;&lt;p&gt;The rest of the day was good, with another excursion off-campus (eating some good ol’ 菠萝饭). And then after dinner I had this PKU-Yale meeting which was explaining the program to some PKUers–yeah, I was the only white guy there. Although towards the end of the meeting, I was asked to say something in Chinese, to which I promptly stated the unofficial motto of 元培 college: 猥琐是一种气质，自恋是一种美格。&lt;/p&gt;&#xA;&lt;p&gt;And with that, I am going.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/10/27/the-computer-is-working-again/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Yunnan and the Tales of a Broken Computer</title>
    <updated>2007-10-13T00:00:00Z</updated>
    <id>tag:dlo.me,2007-10-13://archives/2007/10/13/yunnan-and-the-tales-of-a-broken-computer/</id>
    <content type="html">&#xA;So, almost two weeks ago, I set off to 云南 and 贵州 as a part of the PKU Program, half because we wanted to teach middle-schoolers some English and participate in the Yale Club of Beijing’s book-giving project, and half because we wanted to explorer a part of China beyond the borders of 北京. The trip was incredible.&#xA;&#xA;The first day started with an early wakeup at around 6 in the morning to catch a bus that was supposed to pick us up at the Southeast Gate at Peking University…unfortunately the bus missed its planned appointment and we all took taxis to the airport. We got to the gate with barely enough time, we were actually lucky that they printed our tickets. Kirk forgot his passport so he had to go back to the dorm and take the next flight which was planned to take the other girls who had decided that they couldn’t miss their Friday classes to travel. My roommate did.&#xA;&#xA;&lt;!-- Oh my god, Kirk and Jueby are playing some horrendous sounding instruments in the common room, and although I really need to write this post, I honestly cannot bear this. I’ve tried for the past five minutes (as you can see above), but I feel like I’m going to tear my ears out at this point. I mean, I enjoy traditional Chinese music, and all, but even 赵新侃  and 韩光 closed the door coming into the room. Like, this is miserable. Don’t ever get locked in a room with beginning players of these instruments. You’ll want to kill yourself. --&gt;&#xA;&#xA;Tomorrow I will continue, because right now I am simply about to pass out.&#xA;</content>
    <link href="https://dlo.me//archives/2007/10/13/yunnan-and-the-tales-of-a-broken-computer/" rel="alternate"></link>
  </entry>
  <entry>
    <title>A cold night…in Beijing? What?</title>
    <updated>2007-09-26T00:00:00Z</updated>
    <id>tag:dlo.me,2007-09-26://archives/2007/09/26/a-cold-night-in-beijing/</id>
    <content type="html">&#xA;&lt;p&gt;Amidst a seemingly endless gust that is sweeping throughout my dorm and the slight sound of leaves scraping against each other as they only do in Autumn, I ran outside to grab myself some 煎饼, because, well, dinner tonight wasn’t really enough (it consisted of 2 bowls of rice and a plate of this nasty chicken dish that went mostly uneaten–totally undercooked). In any case 煎饼 is this crepe-like creation that is a concoction of eggs, lettuce, and crispy “stuff”–I don’t know what it is. This object has been a favorite of mine ever since I’ve come over here…to 北大. They didn’t have these things around the 北师大 campus, which would have irked me at the time had I known that such a marvelous fruit of human creativity actually existed.&lt;/p&gt;&#xA;&lt;p&gt;In any case, the weather today is the coldest that it has been during my entire time in China, hands down (just to keep this in perspective, I’m not a strong supporter of the “hands down” construction, but this time it just calls for it due to the extremeness of the situation). On another note, Google spell-check thinks that extremeness is a word, yet awesomeness is not. This bothers me.&lt;/p&gt;&#xA;&lt;p&gt;Some people have asked me for my address here, so here it is (for all the world to see):&lt;/p&gt;&#xA;&lt;p&gt;Dan Loewenherz&lt;br&gt;&#xA;中华人民共和国北京&lt;br&gt;&#xA;北京大学廖凯原楼240室&lt;br&gt;&#xA;北大耶鲁联合本科项目&lt;br&gt;&#xA;邮编：100871&lt;/p&gt;&#xA;&lt;p&gt;If you can’t read it, comment.&lt;/p&gt;&#xA;&lt;p&gt;Dan&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/09/26/a-cold-night-in-beijing/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Sorry about the hiatus</title>
    <updated>2007-09-25T00:00:00Z</updated>
    <id>tag:dlo.me,2007-09-25://archives/2007/09/25/sorry-about-the-hiatus/</id>
    <content type="html">&#xA;&lt;p&gt;Hey Everybody,&lt;/p&gt;&#xA;&lt;p&gt;So yes, I have been blamed for allowing this beautiful site to sink into a state of morbidity, and yes, I do have to admit, it’s my fault. I can’t say that I haven’t had the time, nor can I say that I’ve had nothing to write about, because those two things are just outright false.&lt;/p&gt;&#xA;&lt;p&gt;Anyways, onto the news: a couple of weeks ago, the PKU-Yale program started on the 北大 campus just as it was expected to. My roommate is awesome–not only does he speak nearly fluent English, BUT, he is also a pretty cool guy. He’s teaching me slang non-stop, which is good for my Chinese. It’s definitely helping me do things (like actually understanding people) in ways that I would have never thought possible. In fact, it’s great to ask him random things like if something can actually be said in a sentence, which was something roommate’s at PiB just had no way to do. I mean, how is asking any old American how to use 反而 going to help my Chinese? ON TOP of that, I can learn sweet things like RP黑洞 (RP black hole, a person who has so much bad karma that he’s pretty much hopeless) and 豁然开朗 (achieving a moment of epiphany).&lt;/p&gt;&#xA;&lt;p&gt;There’s so many random things to say, but I just can’t condense everything into a short paragraph. To do that would demerit any experience of its true effect. So, as a result, I am going to continue my blog from hereon as I did during the summer.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/09/25/sorry-about-the-hiatus/" rel="alternate"></link>
  </entry>
  <entry>
    <title>a light in the attic</title>
    <updated>2007-08-14T00:00:00Z</updated>
    <id>tag:dlo.me,2007-08-14://archives/2007/08/14/a-light-in-the-attic/</id>
    <content type="html">&#xA;&lt;p&gt;I don’t know how this happened, but I somehow have made it to the last week here at Princeton in Beijing. Although I love this program (and yes, I have to admit that the last post was just me complaining, I wrote it out of a fit of rage, and this fit of rage happened to take place during a time when I was feeling relatively angry with my progress in Chinese), there are things that aren’t perfect about China itself.&lt;/p&gt;&#xA;&lt;p&gt;Last week I had to get a new visa, because the one I currently have only allowed for a single-entry, and thus, if I had wanted to come back into China, I would have had a major problem. The building I went into was probably the nicest I had walked into in all of Beijing. It was bustling with (seemingly) important people (I certainly felt set apart from everyone in this building, everyone else was wearing suits and had a “job” to do, I guess…I was just wearing a T-shirt and jeans, and had white skin).&lt;/p&gt;&#xA;&lt;p&gt;One thing that does bother me about the culture here in China is that people will immediately start talking to you in English if you look white–even before one has even spoken. I’m not sure how I feel about this. I know I’m not in the United States, but if the situation were to be reversed, the same thing would not transpire. For instance, everyone in the U.S. is expected to speak English, regardless of race. There are no exceptions. If I see someone who looks Chinese in the U.S., I’m not going to walk up to him and try to start a conversation in Chinese–this seems too presumptuous of me, and a little rude. However, this sort of thing happens all the time in China. It’s certainly happened to me on numerous occasions. Do Chinese not understand that white skin is not an instant sign that I speak English? On top of that, every time I walk into a restaurant, I feel like I’m being stared at the instant I walk in and all the way through my meal.&lt;/p&gt;&#xA;&lt;p&gt;I will just flat out say that this is absolutely inappropriate, and yes, I’ll say it, racist. Just because I look different doesn’t mean that you can treat me differently or use different languages when talking with me. If China is ever going to succeed in the new economy, or the world that is increasingly becoming internationalized, its people are going to have to realize that it can’t live in a bubble anymore, and that it is not the center of everything. I feel like this is a problem that has plagued China for time immemorial, and it will undoubtedly take generations to get rid of.&lt;/p&gt;&#xA;&lt;p&gt;I hate how this is going…my posts are getting increasingly critical. But this isn’t exactly the message I want to send. There are things about China that I absolutely love, and there are a lot of myths that go around in the states that are flat-out not true. For example, there’s a prevalent belief that has been promulgated by various sources that people here don’t wait in lines, that the people here spit everywhere, that there are huge differences in politeness and dealing with people, and that the government is looking over every little thing I do.&lt;/p&gt;&#xA;&lt;p&gt;Well, despite what our textbook says and what I heard before I came here, I have to say that Americans probably spit on the streets just as much as Chinese do, have about the same rules when it comes to politeness, (…get ready for the big ones) Chinese in fact do wait in lines, and from my eyes the government here is absolutely less intrusive (at least outwardly) than that in the U.S. I don’t know why China is always being made out to be so different. The fact of the matter is that people are just people, and that you just have to be open.&lt;/p&gt;&#xA;&lt;p&gt;罗文瀚&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/08/14/a-light-in-the-attic/" rel="alternate"></link>
  </entry>
  <entry>
    <title>哎呀！</title>
    <updated>2007-08-05T00:00:00Z</updated>
    <id>tag:dlo.me,2007-08-05://archives/2007/08/05/哎呀/</id>
    <content type="html">&#xA;&lt;p&gt;Hi y’all,&lt;/p&gt;&#xA;&lt;p&gt;This was the end of my sixth (!) week in Beijing, and I can’t seem to believe that I’m going to be here for another five months. Alright, enough of that.&lt;/p&gt;&#xA;&lt;p&gt;I want to keep talking about the classroom situation, because it’s pretty much my life over here at PiB. In the last post, I discussed a fair amount of problems that lie within the system that is currently being implemented at this language program. Right now I’m gonna try and give some examples of what I think would be much more effective ways of teaching this language. I’m going to try to be concise, but not too specific. Here goes…&lt;/p&gt;&#xA;&lt;p&gt;Every day should begin with a teacher discussing the meaning of the words in the lesson, giving the definitions in Chinese, and making these explanations as simple as possible, using already-learned vocabulary. This way, in our minds, the words will be cemented into our heads as Chinese words, &lt;em&gt;NOT&lt;/em&gt; Chinese translations of English words (which, by the way, are totally different–there are some words in Chinese that have the same English&lt;em&gt; &lt;/em&gt;definition, but have absolutely no relationship when it comes to usage).&lt;/p&gt;&#xA;&lt;p&gt;After this, two teachers will go in front of the class and use the words that we have just learned in conversation. By hearing the words being used in a context &lt;em&gt;other&lt;/em&gt; than a textbook, we’ll be able to know in our heads when it is appropriate to use the newly-learned words. A problem at PiB (and I’m sure it’s similar at many other language programs) is that you’re expected to know how to use the word you learned last night perfectly despite the fact that you’ve never used it before, or heard it before, in your life, which is a problem. If there is real-life conversation, then it’s easier to assimilate the vocabulary because there is a link in your mind between the meaning of the word and its usage. This is &lt;em&gt;extremely&lt;/em&gt; important in language acquisition. It goes without saying that grammar examples would be given in a similar fashion.&lt;/p&gt;&#xA;&lt;p&gt;After this, students will be asked to provide sample sentences using the words. This could go on for a few minutes, or an hour, but the main point is that the teacher could correct the student if he or she makes a mistake, and then explain why the specific usage was wrong. Slowly but surely students will start to notice their mistakes and will stop making them.&lt;/p&gt;&#xA;&lt;p&gt;We can then move onto the one-on-one sessions, where the student is engaged in conversation. This provides a way to practice everything that was just learned during the day, and everything before, in a very low-key and stress-free environment. Notice how the entire method is focused on focusing on the listening &lt;em&gt;first &lt;/em&gt;and &lt;em&gt;then&lt;/em&gt; the speaking ;).&lt;/p&gt;&#xA;&lt;p&gt;Maybe I’m too idealistic. Maybe I’m a complainer. Maybe I’m just saying this because I still can’t speak Chinese that well. 哈哈.&lt;/p&gt;&#xA;&lt;p&gt;Yours truly,&lt;/p&gt;&#xA;&lt;p&gt;罗文瀚&lt;/p&gt;&#xA;&lt;p&gt;p.s. new photos are up&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/08/05/哎呀/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Pictures!</title>
    <updated>2007-07-27T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-27://archives/2007/07/27/pictures/</id>
    <content type="html">&#xA;&lt;p&gt;This is where I put all my pictures…probably should have told y’all this earlier.&lt;br&gt;&#xA;&lt;a href=&#34;http://picasaweb.google.com/dloewenherz&#34;&gt;&lt;span&gt;http://picasaweb.google.co&lt;/span&gt;&lt;span&gt;&lt;/span&gt;m/dloewenherz&lt;/a&gt;&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/27/pictures/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Cont’d (sp?)</title>
    <updated>2007-07-27T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-27://archives/2007/07/27/contd-sp/</id>
    <content type="html">&#xA;&lt;p&gt;And the last post is incomplete.&lt;/p&gt;&#xA;&lt;p&gt;The party was pretty awesome. I met up with some other Yalies (it was crazy, yeah, I have no idea how they found out about this thing either) and we spent pretty much the entire party with each other. It was fun.&lt;/p&gt;&#xA;&lt;p&gt;Now, I kind of want to talk about what exactly is going down with this entire language immersion business.&lt;/p&gt;&#xA;&lt;p&gt;I have to say that I am in some ways very disappointed with how foreign languages are taught. Yeah, I thought about it for a while and decided that my disapproval is not just limited to how I’m learning Chinese right now.  I’m gonna list the problems I see one by one.&lt;/p&gt;&#xA;&lt;p&gt;第一个:  I know that during class, us 2nd year students mess up a lot on our tones and/or pronunciations of words. However, from my point of view, we are not being correctly nearly as much as we should be. Instead of this, our classes are more focused on pounding in new vocabulary into our heads, and not enough time is spent nailing the fundamental issues. And every class comes with new vocabulary, so oftentimes we don’t get to practice the vocabulary we learned the day before!&lt;/p&gt;&#xA;&lt;p&gt;第二个: We do too much talking, and not enough listening–listening to our classmates (and ourselves) say the wrong thing in class, over and over and over. This &lt;em&gt;cannot&lt;/em&gt; be the right way to learn a language. The system at PiB is: teacher asks a question, student responds. Thus, the emphasis is on &lt;em&gt;talking&lt;/em&gt;, when I think &lt;em&gt;listening &lt;/em&gt;is the more important aspect. When one is asked a question, he is obviously listening. But I don’t think this is enough. This is basically all the language interaction that we get during class. On top of that, we are often asked to use the newly learned vocabulary in complex sentences when we haven’t even learned how to properly say the words. Of course there are going to be problems when your sole means of studying the night before is listen to a CD. But you can’t ask a CD questions, like, “in what other case can you use this word” or “why is there a 了 in this sentence?”. It’s just not the same.&lt;/p&gt;&#xA;&lt;p&gt;第三个:  Our interactions with native speakers is lacking. The only chances we really have to have “meaningful” and uncut discussions with our teachers is during 中文桌子 (chinese table, zhong1wen2 zhuo1zi) and our individual sessions. I say meaningful with quotes because I think our thoughts are being shaped by the vocabulary that we have, in many ways. We are only taught to use the words we know in certain constructions, and these sentences come straight from a book that has come to the conclusion that the real reason US-Sino relations are so good is because China sent a ping-pong team on an official visit to the U.S. in 1978. Yeah… I mean, of course we have our own thoughts, but what I’m saying is that it’s hard to ask the teachers during class what the other word you want to say is, etc.&lt;/p&gt;&#xA;&lt;p&gt;Ok, I’m wiped, and I could make a long list, but I’m too tired right now.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/27/contd-sp/" rel="alternate"></link>
  </entry>
  <entry>
    <title>What does it take?</title>
    <updated>2007-07-27T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-27://archives/2007/07/27/what-does-it-take/</id>
    <content type="html">&#xA;&lt;p&gt;I just finished the fifth major exam, and I have to say that they’re getting progressively harder as the weeks go by. But that’s enough about academics.&lt;/p&gt;&#xA;&lt;p&gt;So there are a lot of things that I have left out in the past few posts, and I feel like a terrible person for denying all of you the pleasure of hearing about some of the absolutely insane things that I have done. I’ll start with what I think is by far the most crazy. Three weeks ago, I was talking with a Chinese friend of mine (on Skype) and she let me know about a party that was going to go down right by the end of the Great Wall of China (basically, the area where the Great Wall meets the sea). I was doubtful beyond belief that a thing like this was actually lawful, but feeling peer pressure and an intense overwhelming sense of adventure, I decided to go for it and buy tickets. I told a few other people here at PiB about the party, and contrary to what I expected, there wasn’t too much interest. This might have been because the very day of the Great Wall (learn chinese! 长城 - chang2 cheng2) party PiB was also arranging a trip to another tourist site. All in all, that day (and night? I guess) I ended up sitting in buses for a total of about 11 hours. In any case, three other people in PiB stated that they wanted to come, so I went ahead and bought them tickets. The ticket place was at this really random cafe at this international university–in the U.S. I’m definitely not used to buying party tickets at random Chinese restaurants. When I warily stepped into the cafe, like a lost child, they immediately made it clear to me that this was the place to buy tickets. I don’t know why that was the first thing they said to me, but I’m guessing that it’s because I’m white.&lt;/p&gt;&#xA;&lt;p&gt;The tickets were 200 kuai each, which comes around to about $26.44, which appeared as a steal in my eyes for a night full of partying right next to 长城. So, when that fateful Saturday came around, we all frantically congregated in the lobby of 新松公路 (our dorm) because we all thought that we would be late for the bus pickup (the ticket said that the pickup stopped at 6 o’clock, and by the time Princeton-in-Beijing brought us back from our little trip, it was already about 5:30. We were dying because of the thought that we would miss this awesome opportunity. We flew into a taxi cab and yelled out (probably totally meaningless) instructions to take us to our destination. After about 20 minutes of driving, we arrived at a location. Problem: no buses.&lt;/p&gt;&#xA;&lt;p&gt;We thought that we had messed up. By the time that we got to the place, it was already 6:07, and besides the public buses, there was nothing that appeared to have anything to do with a 长城 party. We walked around aimlessly for about 20 minutes–in the end, I called my Chinese friend to see what the deal was. Luckily, we screwed up, and went to the wrong place. After 5 minutes of searching, we found the right area, of course covered with hordes of foreign people. 除了 me and my Chinese friend, I don’t think anyone was speaking Chinese.&lt;/p&gt;&#xA;&lt;p&gt;The best part of this trip was the fact that the bus driver didn’t know where we were going.&lt;/p&gt;&#xA;&lt;p&gt;Let that soak in for a bit.&lt;/p&gt;&#xA;&lt;p&gt;…&lt;/p&gt;&#xA;&lt;p&gt;We had to stop over 3 times because the driver had to ask for directions. It was pretty pitiful. We also got lost once, but that problem was swiftly corrected. You have no idea how awesome it is to be trapped in a bus for over four hours when you have no idea where you are or where you’re going, all the while listening to drunk Americans singing classic rock ballads. It was an experience I’ll never forget.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/27/what-does-it-take/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Ok</title>
    <updated>2007-07-19T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-19://archives/2007/07/19/ok/</id>
    <content type="html">&#xA;&lt;p&gt;Alright, I lied. When I said “later tonight”, I really meant “two days later after my midterm exam”. In any case, I just finished it, and 没想到, it wasn’t that bad at all. I guess that’s because I’ve been locked up in my room for the past two days eating takeout Gongbaojiding and writing characters. Woohoo.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/19/ok/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Continued</title>
    <updated>2007-07-16T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-16://archives/2007/07/16/continued/</id>
    <content type="html">&#xA;&lt;p&gt;Alright, so I changed my name from 罗丹 to 罗文瀚 earlier on this week. Since there were already 2 other 罗丹’s in the PiB program. My new name actually means something too–I guess the closest English translation would be “vast knowledge”, which in my opinion is a pretty cool name. Also, what’s perhaps most important is that I’m not constantly being asked by Chinese people if I’ve heard of 罗丹, the artist. It happened ALL THE TIME when I introduced myself to people. It was really starting to tick me off, but now I’m not gonna have that problem!&lt;/p&gt;&#xA;&lt;p&gt;So to continue where I left off last night, after talking with the two 北师大 students, the thought truly came to my mind that being able to communicate in Chinese will open up a whole new world. I know it sounds corny, but I had an awesome time talking with people who had never been to the United States before and who have been speaking Chinese their entire lives. I don’t know how to explain it, but whatever happened to my mindset gave me a huge push. I think the realization was that the majority of my Chinese learning is not going to come from my textbook, but rather from the people who I meet here. Chinese is a living language, with meanings and usage changing constantly–for example: at Yale, the word fazi was used to mean “method” (as in “study method”) but when I got here, I learned that fazi isn’t often used for that meaning, the word I should use is banfa. The grammar, as I’m slowly learning, is also one of the most messy I’ve ever encountered. Of course there are constant rules, but as there is no confusion related to verb tense, noun declensions, prepositions, or articles, Chinese grammar makes up for it in other ways. Ok, back to studying…will repost later tonight!&lt;/p&gt;&#xA;&lt;p&gt;罗文瀚&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/16/continued/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Burning the Midnight Oil</title>
    <updated>2007-07-15T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-15://archives/2007/07/15/burning-the-midnight-oil/</id>
    <content type="html">&#xA;&lt;p&gt;BIG NEWS: I changed my Chinese name. I’ll explain in tomorrow’s post…&lt;/p&gt;&#xA;&lt;p&gt;The past week here at PiB was definitely our most intense week thus far. I do have to say, though, that I’m beginning to become somewhat frustrated with the learning process here at PiB. On one hand, since I’ve come here, my abilities to communicate in Chinese have escalated more quickly than I could have possibly imagined–and what’s harder for me to totally soak in is that I’ve only been here for three weeks. On the other, I am annoyed by the fact that the language pledge, in my opinion, has not been that effective. The issue I have is that, as a second-year student, I’m mostly just communicating with other people who can’t speak Chinese all that well. As a result, if we say something wrong, make a grammar mistake, or don’t know what word to use, well, yeah, the pledge doesn’t help. I’ve learned far more during my time here talking to local Beijing ren, picking up some slang words and getting a better grip on how to understand what people are saying when it sounds like they have towels in their mouths while they’re talking (during class, our teachers do NOT talk like how the Beijing locals do….at all). And there’s no other way to learn that in 普通话 that the word for pistachio (开心果) is just a synonym for “a happy person” ;), because our teachers purposely (at least in my opinion) restrict the vocabulary they use in class to solely the lesson-based vocabulary. Time and time again I have listened to my teachers nearly say something that they would normally have said, and then quickly correct themselves. Although PiB’s classes are great, they are by no means all-encompassing. If you want to learn Chinese, you MUST go out in Beijing and talk to strangers.&lt;/p&gt;&#xA;&lt;p&gt;Which leads me to some great things that happened last week. 马森茂 (Sam Massie) met a 北师大 student who was studying in our dorm last week, and we ended up getting together with him and his girlfriend at the student cafeteria. Prior to this event, I had been feeling really down about the entire language-learning process–after talking with two other people who had grown up here, my entire viewpoint changed drastically. They spoke frankly with us about any issue that we brought up, including shows that they liked (Prison Break is SO popular here, I just don’t get it!) and opinions on Mao. They asked us about George Bush, and we thought it would be funny to bring up the fact that he always says “nucular” instead of “nuclear”. Heh, they didn’t seem to get the humor that the President of the United States mispronounced words in our own language. I think this stems from the fact that in China, there are so many different dialects that one is almost expected to speak with one’s own twist.&lt;/p&gt;&#xA;&lt;p&gt;Aight, it’s almost two in the morning, I gotta go to sleep. 晚安!&lt;/p&gt;&#xA;&lt;p&gt;罗文瀚&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/15/burning-the-midnight-oil/" rel="alternate"></link>
  </entry>
  <entry>
    <title>a promise is kept</title>
    <updated>2007-07-03T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-03://archives/2007/07/03/a-promise-is-kept/</id>
    <content type="html">&#xA;&lt;p&gt;Alas, I said earlier that I would upload photos, and hence I will keep my word. Of course I will provide appropriate captions to keep your imaginations occupied. Actually, just enjoy the pictures. P.S. They’re all real. P.P.S. I would love to write more but I have a lot of work ahead of me. If I don’t want to get pwned in class tomorrow it would be best if I got it done.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;http://onehundredeightydays.com/wp-content/uploads/2007/07/china-2-004.JPG&#34; alt=&#34;Chang Cheng&#34; height=&#34;255&#34; width=&#34;364&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;http://onehundredeightydays.com/wp-content/uploads/2007/07/china-2-006.JPG&#34; alt=&#34;thrice&#34; height=&#34;255&#34; width=&#34;364&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;http://onehundredeightydays.com/wp-content/uploads/2007/07/china-2-005.JPG&#34; alt=&#34;numba 2&#34; height=&#34;255&#34; width=&#34;364&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;you get a prize if you can tell me who’s in the right-hand side of this picture.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/03/a-promise-is-kept/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Let the destruction begin.</title>
    <updated>2007-07-03T00:00:00Z</updated>
    <id>tag:dlo.me,2007-07-03://archives/2007/07/03/let-the-destruction-begin/</id>
    <content type="html">&#xA;&lt;p&gt;Before I begin, I’d like to share something that one of my fellow Yalies, Niè Wànlǐ (a.k.a. Scott Nelson) sent to me and other Yalie PiBers: &lt;a href=&#34;http://www.nytimes.com/2007/07/01/nyregion/01yale.html?_r=1&amp;amp;ref=movies&amp;amp;oref=slogin&#34; title=&#34;This Place Looks Familiar, but Where&amp;#39;s Starbucks&#34;&gt;This Place Looks Familiar, but Where’s Starbucks?&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Alright, so I’ll try my best to let you know what exactly has transpired in the past week and a half–I apologize for the long delay, getting into the groove here as been a little more difficult than I thought, and again, money was kind of an issue, mostly because I had intelligently entered the wrong PIN three times in Bank of China ATM. Yes, I know. I am a genius.  So, as a result of this, Wachovia blocked my card, and since they were unwilling to reach me by email, I was unaware of this…so I get an email this morning from my dad saying that Wachovia had mailed BY POST a letter to my house. Now, if they had seen where this particular ATM was located, they should have had the foresight to realize that I would not get the information that my card was blocked until after I got back home, which would be in six months. Luckily my dad was kind enough to let me know what was going on. Anyways I talked to the Wachovia people today and everything was sorted out, and I happily withdrew from the ATM so conveniently placed next to the McDonald’s by Dongmenr (East Gate).&lt;/p&gt;&#xA;&lt;p&gt;This is one thing I love about out living situation. Xinsong Gongyu (the dorm where all the PiB 2nd years live) is in an excellent location–if only I could 降价 (jiang4jia4 - haggle over prices) a little better in Chinese, I think I would have it set. As still as of yet I’m pretty incapable of ordering off of menus without the help of friends–and I can’t say that since I’ve gotten here that aspect of shenghuo (life) has gotten much easier. It is, although, relatively easy to buy things from the very nearby chaoshi (supermarket). They sell nearly everything: shampoo, sodas, bread, snacks, you name it. And there is an extra bonus because you get to laugh at all the retarded English translations on nearly everything in the store. If you ever come to China and don’t speak English, expect to do a lot of deciphering, because signs and labels sometimes aren’t so clear.&lt;/p&gt;&#xA;&lt;p&gt;Another added bonus is that I’ve found that things are extremely cheap. After my fiasco at the bank, where I managed to huan (change) about US$100 into Yuan, I managed to buy a SIM card, eat out every day of the week, buy toiletries, go out twice during the weekend, travel to Changcheng (the Great Wall), and visit Tiananmen Square. Mind you, this is for about eight full days of living here. And it’s not like I’m living on the cheap. I can imagine, though, that if you’re a tourist you’ll be herded into expensive touristy areas with American-like prices. I’ve only been eating at restaurants with only Chinese-speaking fuyuyuans (waitresses) and haven’t really encountered many English speakers.&lt;/p&gt;&#xA;&lt;p&gt;So, let’s get down to business (i.e. exactly how this intensive Chinese thing is going).&lt;/p&gt;&#xA;&lt;p&gt;So far, PiB has been incredible. It is the best language-teaching I have ever had, hands down. We have classes five days a week, four hours a day, from 7:30 to 11:30 in the morning. Technically, it’s really four classes every day. The first two classes are fifty minutes long and are the closest that PiB comes to to a lecture. It’s really not even close, because during class we (the students) are constantly kept on our toes, and have to remain very attentive, otherwise quickly falling behind is inevitable. In the first few minutes we are given a tingxie (literally “listen write”) which, for all you French students out there, is basically a Dictee, except a million times harder, because you don’t exactly have the option of sounding words out to spell them. Our teacher speaks, we write characters. Easier said then done, especially when you have more than thirty new characters every night.&lt;/p&gt;&#xA;&lt;p&gt;After these two dabanke (big classes) we have two “drill” classes, at which point we are split up until groups of four students and are reinforced with the material covered in the earlier sections. Classes move very fast and we typically cover more than twenty grammar points every day (today I think it was more like thirty). On top of this we are forced to use the vocabulary from the previous night’s lesson in class.&lt;/p&gt;&#xA;&lt;p&gt;Every afternoon (except Friday) we have one-on-one tutorials (gebietanhua) that last 50 minutes…in addition to this we are given to option to take on an extra 50-minute tutorial sometime during the week if we so desire. I chose to do this and I believe it was an excellent idea. These gebietanhua provide excellent opportunities to talk with your teacher about pretty much anything–yesterday I talked to my teacher about TV Shows (dianshi jiemu) in the U.S. and was quite surprised to hear that she was a fan of Pengyou (friends) and Er Shi Si (24). Heh.&lt;/p&gt;&#xA;&lt;p&gt;On Fridays we are tested on the material from the week–despite my greatest fears, last Friday’s test was not as bad as I expected it would be. It was divided into several sections–one was fill in the blank, another was giving directions, another was essay-like in its wording. All in all not too bad. We are also expected to write an essay once per week, and from what I can tell, they’re gonna get harder. In fact, I think pretty much everything is gonna get harder. Last week our assignment was to write a letter to one of our American friends: 200 characters. This week we have to interview 3 Chinese people about the traffic in Beijing: 350 characters.&lt;/p&gt;&#xA;&lt;p&gt;On Mondays and Wednesday we also have a Chinese table (Zhongwen zhuozi). We basically go out to lunch with our Xiaobanke (”drill class”) teachers and talk about anything. Although yesterday I have to say I wasn’t too interested in what we were talking about. At our table there were 4 girls/2 guys, and the guys being in the minority, the topic of conversation quickly deteriorated into a chat about hair. Yes, hair. And we are only 2nd year students.&lt;/p&gt;&#xA;&lt;p&gt;Alas, I brought the issue up with everyone at the table and the conclusion was quickly made that the only way I’ll ever get a girlfriend is if I learn to love to talk about hair. I was not happy.&lt;/p&gt;&#xA;&lt;p&gt;I have my gebietanhua in 10 minutes, so I should probably go. I’m gonna upload some pictures pretty soon.&lt;/p&gt;&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2007/07/03/let-the-destruction-begin/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Ni hao, Zhongguo!</title>
    <updated>2007-06-23T00:00:00Z</updated>
    <id>tag:dlo.me,2007-06-23://archives/2007/06/23/ni-hao-zhongguo/</id>
    <content type="html">&#xA;&lt;!-- &lt;a rel=&#34;attachment wp-att-13&#34; href=&#34;http://onehundredeightydays.com/?attachment_id=13&#34; title=&#34;the meal&#34;&gt;&lt;/a&gt; --&gt;&#xA;&#xA;Wow!! What a trip.&#xA;&#xA;I left 迈阿密 yesterday (?) morning at 6:50 to go to Newark, where my plane to Beijing was scheduled to leave at 12:50 or thereabouts. There was nothing spectacular about the plane ride to Newark, besides the two fifty-something lovers who were obviously not aware that I was in the row right next to them.&#xA;&#xA;&lt;!-- &lt;a rel=&#34;attachment wp-att-14&#34; href=&#34;http://onehundredeightydays.com/?attachment_id=14&#34; title=&#34;Our Bags&#34;&gt;&lt;/a&gt; --&gt;&#xA;&#xA;&lt;!-- &lt;a rel=&#34;attachment wp-att-12&#34; href=&#34;http://onehundredeightydays.com/?attachment_id=12&#34; title=&#34;Map to China&#34;&gt;&lt;/a&gt; --&gt;&#xA;&#xA;When I got to Newark I went straight to the gate for the direct flight to Beijing, which wasn’t that far of a walk and not a hassle at all, considering that I was expecting some sort of regulatory mob to track me down because it was an international flight. In any case, I met up with a bunch of other people doing the program. We got a little hungry/antsy and a few of us thought that it would be a good idea to pick up some food before we left on the plane. As fate would have it, the last meal I had in the U.S. was from McDonald’s and the first I got in China was from Starbucks.&#xA;&#xA;The plane was huge and overbooked, and a couple of times over the loudspeaker at the gate repeated attempts were made to sway some passengers into foregoing their flight for a day’s delay and $800 in Continental Airlines credits. The offer seemed really enticing, but I didn’t want to miss my placement test and be forced to find my way to Beijing Normal University on my own–at this point I’m having extreme difficulty even ordering food at a restaurant. Clearly I have my limits.&#xA;&#xA;The plane trip was really really really long. Like, really really really really long.&#xA;&#xA;It was a fun trip though, being that I’d never been to the North Pole. When I looked outside midway through the trip (see picture)&#xA;&#xA;&lt;!-- &lt;a rel=&#34;attachment wp-att-12&#34; href=&#34;http://onehundredeightydays.com/?attachment_id=12&#34; title=&#34;Map to China&#34;&gt;&lt;img width=&#34;196&#34; src=&#34;http://onehundredeightydays.com/wp-content/uploads/2007/06/sv100727.JPG&#34; alt=&#34;Map to China&#34; height=&#34;140&#34; style=&#34;width:196px;height:140px&#34;&gt;&lt;/a&gt; --&gt;&#xA;&#xA;All I could see was white, which at first I thought was clouds, but then I quickly realized that what I was seeing was ice. Siberia was really similar–the air was really clear and the scenery looked beautiful.&#xA;&#xA;We got three meals during the flight, the first of which were not bad, but the third one (see picture), left a lot to be desired.&#xA;&#xA;&lt;!-- &lt;a rel=&#34;attachment wp-att-13&#34; href=&#34;http://onehundredeightydays.com/?attachment_id=13&#34; title=&#34;the meal&#34;&gt;&lt;img width=&#34;211&#34; src=&#34;http://onehundredeightydays.com/wp-content/uploads/2007/06/sv100726.JPG&#34; alt=&#34;the meal&#34; height=&#34;152&#34; style=&#34;width:211px;height:152px&#34;&gt;&lt;/a&gt;  mmMMM--&gt;&#xA;&#xA;For some reason, ketchup was included, and I couldn’t figure out why. In addition, we were given what looked like a random scrap piece of wood to eat the ice cream. I couldn’t quite understand what was going on so I gave my ice cream to the little kid sitting in the row next to me so that he would have to deal with it.&#xA;&#xA;During the flight, I also tried to review some Chinese, which didn’t turn out to be such a great success. I gave up after about an hour because it dawned upon me that it would be fruitless to attempt to study for the placement test (this soon turned out to be the correct judgment on my part, details to follow).&#xA;&#xA;When we got to Beijing a whole thirteen or so hours later following our departure out of Newark, the scenery outside was beautiful, but the weather was not. What I first though was overcast clouds were simply conglomerations of dense smog. This was not a happy revelation.&#xA;&#xA;Not much talking went on when I entered the airport in Beijing, as most of the Customs agents and border guards just took our little slips and pointed us in the direction we needed to go. I was surprised that there was no hint of air conditioning in the main concourse, but it’s ok, because it didn’t bother me much. I was really impressed with the speed at which we received our luggage–I couldn’t have been waiting for more than five minutes to get all my luggage.&#xA;&#xA;When I stepped out the baggage-collection area, there was, lo and behold, and Starbucks. Needless to say I was quite wiped out by all of this traveling, and so getting coffee was a high priority of mine. I also have to say that the latte was not bad at all, in fact it was pretty damn good.&#xA;&#xA;After a half hour or so we were somewhat herded out of the airport by the PiB people and were led to our bus waiting for us outside. The air definitely had a smoggy feel to it, but it wasn’t enough for me to want to ball my eyes out because of irritation. It is my first day here, though, so time will tell if I’m going to have and eat my words. I forgot to change my money at the airport (I’ll explain the importance of this soon enough). Our luggage was hauled away in separate vehicles–this didn’t feel too kosher–see picture and you’ll understand why I felt this way:&#xA;&#xA;&lt;!-- &lt;a rel=&#34;attachment wp-att-14&#34; href=&#34;http://onehundredeightydays.com/?attachment_id=14&#34; title=&#34;Our Bags&#34;&gt;&lt;img width=&#34;220&#34; src=&#34;http://onehundredeightydays.com/wp-content/uploads/2007/06/sv100728.JPG&#34; alt=&#34;Our Bags&#34; height=&#34;165&#34; style=&#34;width:220px;height:165px&#34;&gt;&lt;/a&gt; --&gt;&#xA;&#xA;The ride to Beijing Normal University took about a half hour, and was pretty smooth. I was surprised to see so many Volkswagens on the trip–I had no idea they were so popular over here. When we got to the Beijing Normal Dorms, we were immediately told that we’d need 200 Yuan to reserve our room–this is where my little problem of money surfaced. A few other people also forgot to change their money, so we all went together to try and find a place to exchange our dollars. This turned out to be quite the task.&#xA;&#xA;We first went across the street to a place that looked like a working Bank, but the ATM wasn’t usable (it turns out that this was the &lt;em&gt;Agriculatural&lt;/em&gt; Bank of China, which is different from just the “Bank of China”, which is what we were looking for, and turned out to be on the same side of the street). Anyways, it took us a good forty minutes or so of diligent searching to find this place, which had an ATM right in front that seemed to work, as luck would have it, for EVERYBODY else except for me. So I had to walk inside the Bank of China. With my laptop case and everything else that was stuffed inside of it (remember, I couldn’t get my room yet, because I didn’t have the money to pay for the key deposit, and thus, I had no place to put my bag). &#xA;&#xA;I went to the little machine at the entrance to the Bank, where you’re supposed to press a button and take the slip of paper out that marks your place in line. This was the first thing I wasn’t used to doing at banks in the U.S. however, this wasn’t the worst of it. I waited for over an hour, and just like the airport, this place wasn’t air-conditioned.&#xA;&#xA;I was handed a slip that I’m guessing foreigners must use to exchange money, and was asked to fill it out. Somehow I got my Yuan, and I was happy, and was soon to be much happier when I found out how far our dollars go in terms of purchasing power.&#xA;&#xA;When I got back to the dorm, I managed to get my key, booklet, etc, and went straight to my room and met Chris, my roommate. The room is spectacular–we have a TV, air conditioning, bathroom, closet, maid-service, the list goes on and on. It’s basically luxurious, and I feel a little bizarre when I walk past the Chinese dorms and see that they’re nothing in comparison to this.&#xA;&#xA;A little after getting into my room, a few people were getting together for dinner. Not only was finding a place to eat a little difficult, but the act of dining was quite the experience in itself. We had no idea what the menu said, besides some 汉字 (hanzi, characters) like 鸡 (ji, chicken) and 肉 (rou, meat). We got food though, and it was good. And cheap. I mean really cheap. Like two bucks a person. And I was truly full. After dinner we walked around a little bit to explore, and when we got back to the room I was exhausted. After a few minutes of taking a shower and shaving the day was over for me. I had been awake for over 28 hours and wasn’t about to stay up any more–not that I was even capable of doing this. So I went to bed at 9ish and woke up to take my placement test…this is a good place to stop ;).&#xA;&#xA;</content>
    <link href="https://dlo.me//archives/2007/06/23/ni-hao-zhongguo/" rel="alternate"></link>
  </entry>
  <entry>
    <title>I got it!</title>
    <updated>2007-06-08T00:00:00Z</updated>
    <id>tag:dlo.me,2007-06-08://archives/2007/06/08/i-got-it/</id>
    <content type="html">&#xA;&lt;p&gt;My days of mourning are over! I have finally gotten a 90-day F visa to China! Woohoo.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/06/08/i-got-it/" rel="alternate"></link>
  </entry>
  <entry>
    <title>This Is Spectacular</title>
    <updated>2007-05-25T00:00:00Z</updated>
    <id>tag:dlo.me,2007-05-25://archives/2007/05/25/this-is-spectacular/</id>
    <content type="html">&#xA;&lt;p&gt;Whilst perusing Wikipedia, I stumbled upon this &lt;a href=&#34;http://en.wikipedia.org/wiki/Wok_racing&#34;&gt;surprise&lt;/a&gt;. Enjoy!&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/05/25/this-is-spectacular/" rel="alternate"></link>
  </entry>
  <entry>
    <title>Visa Things</title>
    <updated>2007-05-25T00:00:00Z</updated>
    <id>tag:dlo.me,2007-05-25://archives/2007/05/25/visa-things/</id>
    <content type="html">&#xA;&lt;p&gt;Alright, I’ll admit it, that last post wasn’t necessary.&lt;/p&gt;&#xA;&lt;p&gt;In any case, today I had an appointment with my pulmonologist so he could take a look at my lung. Everything seems good to go for travel (to make a long story short, I collapsed my lung–thrice, in the past six months, had surgery a couple of weeks ago, and needed a checkup to make absolutely sure that going to China would be a-ok).&lt;/p&gt;&#xA;&lt;p&gt;Also, today I got the JW-202 forms from Beijing Normal University. Now I just need to figure out how exactly to manage to get a visa out of the mess that is me and my collection of documents. It’s so confusing. Oh well, I shall call the New York consulate tomorrow to figure things out. They must have answers. If anyone who reads this has Chinese-visa-applying experience, please let me know–here’s my schedule (again…yes, i know, I am redundant):&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;June 22-August 18: Beijing, studying with PiB at Beijing Normal&lt;/li&gt;&#xA;&lt;li&gt;August 19-August 26ish: USA! Miami and Chicago&lt;/li&gt;&#xA;&lt;li&gt;August 27ish-December 15: Beida&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;What type do I apply for?&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/05/25/visa-things/" rel="alternate"></link>
  </entry>
  <entry>
    <title>an introduction? (part 2)</title>
    <updated>2007-05-22T00:00:00Z</updated>
    <id>tag:dlo.me,2007-05-22://archives/2007/05/22/an-introduction-part-2/</id>
    <content type="html">&#xA;&lt;p&gt;Alright, here comes the rest. So, as I wrote before (I think), I’m going to be spending my first 2 months in China learning Mandarin. My next few months (up to December 15th) will be spent at Beida, where I’m going to be taking Yale classes with Chinese students. It’s gonna be insane.&lt;/p&gt;&#xA;&lt;p&gt;I wish I could elaborate more, but I really don’t know where to start. Ask me questions.&lt;/p&gt;&#xA;&lt;p&gt;Oh, today I also turned in my Visa health form to my doctor. Hopefully it should be filled out by Thursday (the receptionist was very confused by the form, as it was predominantly in Chinese–I thought that was funny).&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/05/22/an-introduction-part-2/" rel="alternate"></link>
  </entry>
  <entry>
    <title>They’re (almost) Here!</title>
    <updated>2007-05-21T00:00:00Z</updated>
    <id>tag:dlo.me,2007-05-21://archives/2007/05/21/theyre-almost-here/</id>
    <content type="html">&#xA;&lt;p&gt;I was getting a little worried about my formal invitation documents for China, since I hadn’t heard anything about them, and I need them to get into China for my stay, but Teng-Kuan (the program coordinator for Princeton-in-Beijing) just let me know his office just received them! He said that he’d send them straight to Miami, priority mail. Awesome.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/05/21/theyre-almost-here/" rel="alternate"></link>
  </entry>
  <entry>
    <title>an introduction? (part 1)</title>
    <updated>2007-05-20T00:00:00Z</updated>
    <id>tag:dlo.me,2007-05-20://archives/2007/05/20/an-introduction-part-1/</id>
    <content type="html">&#xA;&lt;p&gt;Ok, here’s the story, the introduction, the pretty much everything you need to know what’s going on in this little blog of mine, etc. I’ll probably leave out a lot of important stuff though, so if you have any questions, just shoot a little comment onto this post and I’ll get back to ya.&lt;/p&gt;&#xA;&lt;p&gt;So here’s the short story.&lt;/p&gt;&#xA;&lt;p&gt;I’m a rising junior at Yale, and am going to spend the next six months of my life in Beijing, PRC to learn Chinese and to learn what living in China is all about. From June 22 to August 18, I’m going to be at Beijing Normal University, being immersed in Mandarin with a program called Princeton in Beijing, and from August 27 to December 15, I’ll be studying abroad with the PKU-Yale Joint Undergraduate Program.&lt;/p&gt;&#xA;&lt;p&gt;At the moment, I can’t say that I know much about what I’m getting myself into, but what I do know is that I won’t be speaking a word of English for the eight weeks following June 22. Despite the fact that I currently speak Mandarin at the level of a 1-year old, I think that over the next few months my ability to communicate will get much better. Hopefully by the time the program’s over, I’ll be able to audit a math course I really want to take at Beida in the Fall. Beida (北大), short for Beijing Daxue (北京大学), means Beijing University in English.&lt;/p&gt;&#xA;&lt;p&gt;Ok, I need to cut this post short because I’m in Gainesville right now visiting some friends at the University of Florida, and I need to eat some lunch.&lt;/p&gt;&#xA;</content>
    <link href="https://dlo.me//archives/2007/05/20/an-introduction-part-1/" rel="alternate"></link>
  </entry>
</feed>