<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>esmithy.net</title>
	<atom:link href="http://esmithy.net/feed/" rel="self" type="application/rss+xml" />
	<link>http://esmithy.net</link>
	<description>Stuff Hammered Out by Eric Smith</description>
	<lastBuildDate>Thu, 09 May 2013 23:06:49 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>Dressing the Database in Big-Boy Pants, Part 2</title>
		<link>http://esmithy.net/2013/04/13/dressing-the-database-in-big-boy-pants-part-2/</link>
		<comments>http://esmithy.net/2013/04/13/dressing-the-database-in-big-boy-pants-part-2/#comments</comments>
		<pubDate>Sat, 13 Apr 2013 20:45:34 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[database]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=678</guid>
		<description><![CDATA[Last time I wrote about using dbdeploy to easily create local databases and to automatically apply changes to the development, staging and production databases. The other change I made recently was to add stored procedure unit tests. I looked briefly &#8230; <a href="http://esmithy.net/2013/04/13/dressing-the-database-in-big-boy-pants-part-2/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="/2013/04/04/dressing-the-database-in-big-boy-pants-part-1/">Last time</a> I wrote about using dbdeploy to easily create local databases and to automatically apply changes to the development, staging and production databases. The other change I made recently was to add <strong>stored procedure unit tests</strong>.</p>
<p>I looked briefly at frameworks specifically for testing databases, but didn&#8217;t see a ton of value beyond just invoking the database from JUnit via JDBC, so that&#8217;s what I did. Like the use of dbdeploy, stuff gets started from Ant, with a <code>test</code> target.</p>
<p></p><pre class="crayon-plain-tag">&lt;target name=&quot;test&quot; depends=&quot;create-test-database&quot;
        description=&quot;Run tests against database procedures&quot;&gt;
        &lt;path id=&quot;test.lib.path&quot;&gt;
          &lt;fileset dir=&quot;lib&quot;&gt;
            &lt;include name=&quot;**/*.jar&quot;/&gt;
          &lt;/fileset&gt;
          &lt;fileset dir=&quot;test/lib&quot;&gt;
            &lt;include name=&quot;**/*.jar&quot;/&gt;
          &lt;/fileset&gt;
        &lt;/path&gt;
        &lt;!-- Compile tests --&gt;
        &lt;mkdir dir=&quot;${target.dir}/classes&quot;/&gt;
        &lt;javac srcdir=&quot;test/src&quot; destdir=&quot;${target.dir}/classes&quot; includeantruntime=&quot;false&quot;&gt;
            &lt;classpath&gt;
                &lt;path refid=&quot;test.lib.path&quot;/&gt;
            &lt;/classpath&gt;
        &lt;/javac&gt;
        &lt;!-- Run them --&gt;
        &lt;junit printsummary=&quot;on&quot; haltonfailure=&quot;on&quot;&gt;
            &lt;sysproperty key=&quot;dbhost&quot; value=&quot;${db.host}&quot;/&gt;
            &lt;sysproperty key=&quot;dbuser&quot; value=&quot;${db.user}&quot;/&gt;
            &lt;sysproperty key=&quot;dbpass&quot; value=&quot;${db.password}&quot;/&gt;
            &lt;sysproperty key=&quot;dbname&quot; value=&quot;${test.db.name}&quot;/&gt;
            &lt;classpath&gt;
                &lt;path refid=&quot;test.lib.path&quot;/&gt;
                &lt;pathelement location=&quot;${target.dir}/classes&quot;/&gt;
            &lt;/classpath&gt;
            &lt;batchtest&gt;
               &lt;fileset dir=&quot;test/src&quot;&gt;
                    &lt;include name=&quot;**/*Test*&quot; /&gt;
               &lt;/fileset&gt;
            &lt;/batchtest&gt;
        &lt;/junit&gt;
    &lt;/target&gt;</pre><p></p>
<p>Since it is trivial to create a fresh database, I create a separate one just for testing in the dependent <code>create-test-database</code> target. This lets passing tests be a precondition to updating the real database. In other words, on the build server, the <code>test</code> target has to succeed before the <code>update-database</code> target will be run that will apply the current set of database deltas to the real database.</p>
<p>The <code>sysproperty</code> Ant task is handy here because it lets you pass information to the JUnit tests. This means you can set all the database connection information once in the Ant script, but have access to it in the JUnit tests, keeping things <a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a>.</p>
<p>In JUnit, the properties set with the <code>sysproperty</code> task are available via <code>System.getProperty</code>, as you can see from this setup function:</p>
<p></p><pre class="crayon-plain-tag">@BeforeClass    
public static void classSetUp() throws Exception {
    // Get database connection info from system properties set by Ant.
    String host = System.getProperty(&quot;dbhost&quot;);
    String database = System.getProperty(&quot;dbname&quot;);
    String user = System.getProperty(&quot;dbuser&quot;);
    String password = System.getProperty(&quot;dbpass&quot;);

    Class.forName(&quot;com.microsoft.sqlserver.jdbc.SQLServerDriver&quot;);
    String connectionUrl = String.format(&quot;jdbc:sqlserver://%s;database=%s;user=%s;password=%s&quot;,
                host, database, user, password);
    conn = DriverManager.getConnection(connectionUrl);

    String sql = &quot;DECLARE @RC int; EXEC @RC = [dbo].[UpdateCamera2] ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?; SELECT 'Return Value' = @RC&quot;;
    spStatement = conn.prepareStatement(sql);
}</pre><p></p>
<p>This gets everything set up to call the stored procedure (UpdateCamera2 in this case) as well as save the return value, with cleanup in a tear down method:</p>
<p></p><pre class="crayon-plain-tag">@AfterClass
public static void classTearDown() throws Exception {
    spStatement.close();
    conn.close();
}</pre><p></p>
<p>There&#8217;s a helper method to call the stored procedure too. This stored procedure is pretty unwieldy because it was specifically introduced to reduce trips to the database for a performance critical part of the system, and it needs a lot of parameters to do its big pile o&#8217; work.</p>
<p></p><pre class="crayon-plain-tag">/**
 * Call the stored procedure with the unwieldy list of parameters. Sorry.
 * @return A bit field whose value depends on whether the site and camera were new or already existing.
 * @throws SQLException
 */
private int execUpdateCamera2(String cameraMac,
                              String cameraName,
                              int cameraProductId,
                              String cameraFirmware,
                              String cameraInternalIPAddress,
                              String cameraIPAddress,
                              String cameraIPCountry,
                              boolean cameraAlertsEnabled,
                              String cameraJidResource,
                              String siteId,
                              String siteName,
                              int siteAlertFilter,
                              int siteAlertFrequency) throws SQLException {
    spStatement.setString(1, &quot;testuser@dvsopstest.com&quot;);
    spStatement.setString(2, &quot;unit test&quot;);
    spStatement.setString(3, cameraMac);
    spStatement.setString(4, cameraName);
    spStatement.setInt(5, cameraProductId);
    spStatement.setString(6, cameraFirmware);
    spStatement.setString(7, cameraInternalIPAddress);
    spStatement.setString(8, cameraIPAddress);
    spStatement.setString(9, cameraIPCountry);
    spStatement.setBoolean(10, cameraAlertsEnabled);
    spStatement.setString(11, cameraJidResource);
    spStatement.setString(12, siteId);
    spStatement.setString(13, siteName);
    spStatement.setInt(14, siteAlertFilter);
    spStatement.setInt(15, siteAlertFrequency);

    spStatement.execute();

    ResultSet results = spStatement.getResultSet();
    results.next();
    int returnValue = results.getInt(1);
    results.close();

    return returnValue;
}</pre><p></p>
<p>With all that set up, an individual unit test (that references a couple of other helper methods) looks like this:</p>
<p></p><pre class="crayon-plain-tag">@Test
/**
 * A new site is created if the provided site info doesn't match an existing site.
 */
public void newSiteCreated() throws Exception {
    String siteId = UUID.randomUUID().toString();
    String siteName = &quot;Test Site #1&quot;;
    int siteAlertFilter = 111;
    int siteAlertFrequency = 1111;

    int result = execUpdateCamera2(&quot;11-11-11-11-11&quot;, &quot;Unit Test Camera&quot;, 16, &quot;1.0&quot;, &quot;&quot;, &quot;&quot;, &quot;-&quot;, true, &quot;TestJID&quot;,
            siteId, siteName, siteAlertFilter, siteAlertFrequency);

    assertFalse(siteExisted(result));
    HashMap&lt;String, Object&gt; site = queryForSite(siteId);
    assertEquals(siteName, site.get(&quot;Name&quot;));
    assertEquals(siteAlertFilter, site.get(&quot;AlertFilter&quot;));
    assertEquals(siteAlertFrequency, site.get(&quot;AlertFrequency&quot;));
}</pre><p></p>
<p>I recently had to update this stored procedure, and let me tell you, I didn&#8217;t get it right the first time. Or even the third or fourth time. So it was fantastic to have a procedure that took just a few seconds to spin up a clean test database with my latest attempt and run though all the tests. When I finally worked through my SQL mistakes such that the tests passed, I was confident to check in my new delta script.</p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2013/04/13/dressing-the-database-in-big-boy-pants-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Dressing the Database in Big-Boy Pants, Part 1</title>
		<link>http://esmithy.net/2013/04/04/dressing-the-database-in-big-boy-pants-part-1/</link>
		<comments>http://esmithy.net/2013/04/04/dressing-the-database-in-big-boy-pants-part-1/#comments</comments>
		<pubDate>Thu, 04 Apr 2013 22:38:14 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[database]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=670</guid>
		<description><![CDATA[I&#8217;ve mentioned previously that I&#8217;m in no danger of being mistaken for a DBA, but I&#8217;ve recently made a few changes in how we work with out database that have upped the maturity level a bit. Previously we had a &#8230; <a href="http://esmithy.net/2013/04/04/dressing-the-database-in-big-boy-pants-part-1/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>I&#8217;ve mentioned <a href="/2006/02/15/object-oriented-vs-database-oriented/">previously</a> that I&#8217;m in no danger of being mistaken for a DBA, but I&#8217;ve recently made a few changes in how we work with out database that have upped the maturity level a bit.</p>
<p>Previously we had a single development database shared by all. This is an inflexible working arrangement because messing up, even in the very short-term, can impact other people. Also, throwing the whole thing out and starting over isn&#8217;t an option. So change number one was <strong>private databases for database development</strong>.  Not doing this is akin to checking your source code into the main repository in order to see if it works.</p>
<p>To facilitate this, we&#8217;re using <a href="http://dbdeploy.com/">dbdeploy</a>, which isn&#8217;t perfect, but good enough. You start with a baseline schema (just a SQL script that creates all the needed tables and other objects), then add deltas (also SQL scripts) when changes are needed. The simple job of dbdeploy is, given a bunch of deltas, to make sure they&#8217;re applied once in the appropriate order. It keeps track of the applied deltas with a table in the database being maintained. You can also try to have a roll-back script for any change, but that tends to get dicey.</p>
<p>In addition to the baseline script and the deltas, I also have a &#8220;development&#8221; script that does things to make life easier for developers that aren&#8217;t necessarily appropriate for a non-private database, like pre-populating the User table with some users for testing.</p>
<p>Everything is controlled by an Ant script, using both the built-in &#8220;sql&#8221; task as well as the <a href="https://code.google.com/p/dbdeploy/wiki/UsingTheAntInterface">task provided as part of dbdeploy</a>. Here&#8217;s what the &#8220;create-database&#8221; target looks like:</p><pre class="crayon-plain-tag">&lt;target name=&quot;create-database&quot;        
  description=&quot;Create a database from scratch, including the baseline, changelog and deltas&quot;&gt;
    &lt;antcall target=&quot;echo-settings&quot;/&gt;
    &lt;sql 
        driver=&quot;${db.driver}&quot; 
        url=&quot;${db.create.url}&quot;
        userid=&quot;${db.user}&quot; 
        password=&quot;${db.password}&quot; 
        classpathref=&quot;dbdriver.classpath&quot;
        encoding=&quot;UTF-8&quot; 
        delimiter=&quot;GO&quot;
        &gt;
        &lt;!-- Create the database from scratch with the baseline schema. --&gt;
        &lt;fileset file=&quot;${db.baseline.dir}/baseline.sql&quot;/&gt;
        
        &lt;!-- Additional setup for development purposes. --&gt;
        &lt;fileset file=&quot;${db.baseline.dir}/baseline-development.sql&quot;/&gt;
    &lt;/sql&gt;
    &lt;antcall target=&quot;create-changelog&quot; /&gt;
    &lt;antcall target=&quot;update-database&quot; /&gt;
&lt;/target&gt;</pre><p></p>
<p>The &#8220;create-changelog&#8221; target adds the dbdeploy tracking table to the database using a script included with dbdeploy:</p>
<p></p><pre class="crayon-plain-tag">&lt;target name=&quot;create-changelog&quot;
    description=&quot;Create the changelog table in an existing database&quot;&gt;
    &lt;!-- Note that we have to specify the database on the url for this script or the database 
    will be whatever is default for ${db.user}, which might be &quot;master&quot;, for example. --&gt; 
    &lt;antcall target=&quot;echo-settings&quot;/&gt;
    &lt;sql 
        driver=&quot;${db.driver}&quot; 
        url=&quot;${db.url}&quot;
        userid=&quot;${db.user}&quot; 
        password=&quot;${db.password}&quot; 
        classpathref=&quot;dbdriver.classpath&quot; 
        delimiter=&quot;GO&quot;
        &gt;
        &lt;fileset file=&quot;dbdeploy/scripts/createSchemaVersionTable.mssql.sql&quot;/&gt;
    &lt;/sql&gt;
&lt;/target&gt;</pre><p></p>
<p>The &#8220;update-database&#8221; target called from &#8220;create-database&#8221; uses the custom dbdeploy task to apply any deltas:</p>
<p></p><pre class="crayon-plain-tag">&lt;target name=&quot;update-database&quot;
    description=&quot;Apply all available, but not yet applied deltas to a database&quot;&gt;
    &lt;antcall target=&quot;echo-settings&quot;/&gt;
    &lt;dbdeploy
        driver=&quot;${db.driver}&quot;
        url=&quot;${db.url}&quot;
        userid=&quot;${db.user}&quot;
        password=&quot;${db.password}&quot;
        dir=&quot;${db.deltas.dir}&quot;
        delimiter=&quot;GO&quot;
        /&gt;    
&lt;/target&gt;</pre><p>The result is the ability to create a complete database from scratch in just a couple of seconds. It is very liberating because I can mess up in various and sundry ways while trying to get a stored procedure just right, and the consequences are minimal.</p>
<p>Once I&#8217;m satisfied that a delta script is good, I check it in and <a href="http://www.jetbrains.com/teamcity/">TeamCity</a> (our build server) automatically applies the deltas to the development database using the &#8220;update-database&#8221; Ant target.</p>
<p>An important part of this is publishing <strong>all</strong> of the deltas in existence at that moment in the artifacts for the build. That means that our operations guys can take any build, run dbdeploy against the attached deltas, and know that the database will be in the correct state for the application generated by that build. This is true even if many intermediate builds (potentially containing database changes) are never installed in staging or production.</p>
<p>Our workflow for database schema changes is then:</p>
<ol>
<li>Create a .sql file in the &#8220;deltas&#8221; directory that makes the needed change. You can use SQL Management Studio to figure out the script, but don&#8217;t actually make the changes there. Create a script instead.</li>
<li>The .sql file should have a name following the pattern:<br />
&lt;sequence number&gt;&lt;description&gt;.sql<br />
For example:<br />
001_create_firmware_table.sql</li>
<li>Optionally back up your database.</li>
<li>Run &#8220;ant update-database&#8221; which will apply your script to your local database.</li>
<li>Test your code that depends on the new database structure.</li>
<li>If your script wasn&#8217;t right, restore from backup or drop your whole local database and rebuild it.</li>
<li>Once you&#8217;re sure everything works, check in your .sql script and code changes at the same time.</li>
<li>The continuous build server will update the dev database and test your code.</li>
<li>If you discover something that needs changing after this, you need to create a new delta .sql script.</li>
</ol>
<p>Of course, manual schema changes in any environment are against the rules.</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2013/04/04/dressing-the-database-in-big-boy-pants-part-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A word is worth a thousand pictures</title>
		<link>http://esmithy.net/2013/04/01/a-word-is-worth-a-thousand-pictures/</link>
		<comments>http://esmithy.net/2013/04/01/a-word-is-worth-a-thousand-pictures/#comments</comments>
		<pubDate>Tue, 02 Apr 2013 00:05:57 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Misc]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=665</guid>
		<description><![CDATA[I found this sticker inside one of a pair of new shoes: &#160; I didn&#8217;t have any idea what this was saying. My wife suggested it meant that I ought to watch out for diamonds&#8230; and maybe nets. I found &#8230; <a href="http://esmithy.net/2013/04/01/a-word-is-worth-a-thousand-pictures/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>I found this sticker inside one of a pair of new shoes:</p>
<p><a href="http://esmithy.net/content/shoe-1.jpg"><img class="aligncenter size-full wp-image-666" alt="shoe-1" src="http://esmithy.net/content/shoe-1.jpg" width="285" height="288" /></a></p>
<p>&nbsp;</p>
<p>I didn&#8217;t have any idea what this was saying. My wife suggested it meant that I ought to watch out for diamonds&#8230; and maybe nets.</p>
<p>I found this sticker in the other shoe. I think it means the same thing:</p>
<p><a href="http://esmithy.net/content/shoe-2.jpg"><img class="aligncenter size-full wp-image-667" alt="shoe-2" src="http://esmithy.net/content/shoe-2.jpg" width="283" height="285" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2013/04/01/a-word-is-worth-a-thousand-pictures/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>UI Horror: Windows 8 BSOD</title>
		<link>http://esmithy.net/2013/01/24/ui-horror-windows-8-bsod/</link>
		<comments>http://esmithy.net/2013/01/24/ui-horror-windows-8-bsod/#comments</comments>
		<pubDate>Fri, 25 Jan 2013 04:46:11 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[User Interface]]></category>
		<category><![CDATA[Windows]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=650</guid>
		<description><![CDATA[My PC is sad. I know this because of the frowny face. Unfortunately, I know not much more, because my PC won&#8217;t boot, and Microsoft decided to provide as little information about the error as possible on their new BSOD &#8230; <a href="http://esmithy.net/2013/01/24/ui-horror-windows-8-bsod/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>My PC is sad. I know this because of the frowny face. Unfortunately, I know not much more, because my PC won&#8217;t boot, and Microsoft decided to provide as little information about the error as possible on their new BSOD screen.</p>
<p>Hmm&#8230; scary (but potentially useful) hexidecimal-laden error messages or cute emoticon?</p>
<p>Thanks for that.</p>
<p><img class="aligncenter size-full wp-image-652" alt="bsod" src="http://esmithy.net/content/bsod.jpg" width="425" height="319" /></p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2013/01/24/ui-horror-windows-8-bsod/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Amazon Glacier and CloudBerry Online Backup</title>
		<link>http://esmithy.net/2013/01/07/glacier-cloudberry/</link>
		<comments>http://esmithy.net/2013/01/07/glacier-cloudberry/#comments</comments>
		<pubDate>Tue, 08 Jan 2013 01:13:00 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Misc]]></category>
		<category><![CDATA[Applications]]></category>
		<category><![CDATA[Windows]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=639</guid>
		<description><![CDATA[ Offsite backup for critical files This task has been lingering undone on my to-do list for months. I imagined that I&#8217;d figure out some way to use Dropbox, Google Drive, Sky Drive, or something to keep my data safe in &#8230; <a href="http://esmithy.net/2013/01/07/glacier-cloudberry/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><img alt="checkbox-0" src="http://esmithy.net/content/checkbox-0.png" width="19" height="19" /> Offsite backup for critical files</p>
<p>This task has been lingering undone on my to-do list for months. I imagined that I&#8217;d figure out some way to use Dropbox, Google Drive, Sky Drive, or something to keep my data safe in case something catastrophic happened at the house. All those solutions seem so expensive, though. When Amazon Web Services announced <a href="http://aws.amazon.com/glacier/">Amazon Glacier</a> not long ago, with storage rates at 1 cent per gigabyte per month, I figured I&#8217;d found my storage location, but it is just an API. Finding <a href="http://www.cloudberrylab.com/amazon-s3-microsoft-azure-google-storage-online-backup.aspx">CloudBerry Backup</a> completed the solution.</p>
<p><span id="more-639"></span></p>
<p>CloudBerry is a client for a plethora of online storage locations, including S3, Azure, Google Storage, and about a dozen providers I&#8217;d never heard of in addition to Glacier. You&#8217;re responsible for establishing an account and paying the bill for the storage, but the CloudBerry client takes care of getting your stuff there and back.</p>
<p>While Glacier support was enough to catch my interest, I was also happy to see that I could use it to back up to my NAS. I was dismayed when switching from Vista Ultimate to Windows 7 Home Premium that I&#8217;d lost the ability to use Windows Backup to backup to a network location, so I&#8217;ve just been using an external USB drive. It was pretty easy to set up the network backup using CloudBerry, with plenty of good options to control what got backed up.</p>
<p>Aside from the missing network support in Windows Backup (for the Home variety of Windows), there a couple of things that bug me about it:</p>
<ol>
<li><span style="line-height: 15px;" data-mce-mark="1">It&#8217;s not always clear what your&#8217;re backing up. You can pick certain paths, but does it include hidden and temporary files? Is there anything that I actually want that is being excluded? What if I don&#8217;t want to include some fat file in the backup?</span></li>
<li>Compounding the first problem is that the backed-up data itself is somewhat opaque without the backup client.</li>
</ol>
<p>CloudBerry gives you a lot of fine-grain control over what gets backed up (I think I&#8217;ll survive if my Linux VM isn&#8217;t backed up, thanks), and the backed-up data is perfectly navigable when I ssh into my NAS. You can compress and/or encrypt the data as well if you want.</p>
<p>I used the NAS backup to fine-tune my settings. The first run took all night for about 122 GB of data. Presumably backups will be incremental going forward, so much speedier. That will be especially important for Glacier. Having gotten some settings I liked for the NAS, I created a configuration for Glacier, and kicked that off. This takes a <strong>long</strong> time, as in many days, owing to the generally slow upload speeds from ISPs. CloudBerry provides some bandwidth throttling that can come in handy during the days-long upload.</p>
<p>For the Glacier configuration, I had to manually copy my settings from the NAS configuration. It would have been nice to copy a configuration, but I didn&#8217;t see a way to do that. Also, when you edit a configuration, you have to go through the entire wizard. All the values remain as they were, but you have to fully navigate through the dozen wizard screens before you can get to the end and commit the change.</p>
<p>I also ran into a bug when trying to enable the storage cost estimation tool. I got an index out of range exception displayed in a message box, without any further indication of what was wrong or how to fix it. I&#8217;ve also seen a couple of NullReferenceExceptions reported from the application, so there are a few rough edges.</p>
<p>Overall, though, I&#8217;m happy to call it good.</p>
<p><img alt="checkbox-1" src="http://esmithy.net/content/checkbox-1.png" width="18" height="19" />  Offsite backup for critical files</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2013/01/07/glacier-cloudberry/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Coding Standards and Orthography</title>
		<link>http://esmithy.net/2012/11/06/coding-standards-and-orthography/</link>
		<comments>http://esmithy.net/2012/11/06/coding-standards-and-orthography/#comments</comments>
		<pubDate>Tue, 06 Nov 2012 23:30:17 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=535</guid>
		<description><![CDATA[Sometimes when I bring up the subject of coding standards, I get an eye-rolling, aren&#8217;t-we-all-adults-here kind of reaction from my fellow programmers &#8212; or a fearful look anticipating endless debates about where the braces should go. Of course, &#8220;coding standards&#8221; &#8230; <a href="http://esmithy.net/2012/11/06/coding-standards-and-orthography/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>Sometimes when I bring up the subject of coding standards, I get an eye-rolling, aren&#8217;t-we-all-adults-here kind of reaction from my fellow programmers &#8212; or a fearful look anticipating endless debates about where the braces should go. Of course, &#8220;coding standards&#8221; can cover a gamut of subjects &#8212; from techniques to avoid shooting yourself in the foot to parenthesis placement &#8212; but even the little stuff matters, because writing code is largely about communicating with humans.</p>
<p><span id="more-535"></span></p>
<p>The adage says, &#8220;code is read more than it is written.&#8221; If this isn&#8217;t completely obvious, try this experiment:</p>
<ol>
<li>Turn off your monitor</li>
<li>Write some code</li>
</ol>
<div>It&#8217;s pretty hard to write code without at least reading it <em>once</em>, so if you ever read it again you&#8217;ve satisfied the adage&#8217;s assertion. Chances are that you or someone else will read the code many more times.</div>
<div></div>
<div>Flippancy aside, a <a href="http://users.jyu.fi/~koskinen/smcosts.htm" target="_blank">survey of software maintenance cost studies</a> shows that greater than 90% of the cost of software is in maintaining and enhancing it, and &#8220;Studies of software maintainers have shown that approximately <strong>50% </strong>of their time is spent in the process of <em>understanding the code </em>that they are to maintain.&#8221;</div>
<div></div>
<div>If you accept the premise that code is a form of written communication with people as a primary audience, then it makes sense to draw from the ideas of similar systems with hundreds of years of experience behind them, like say, written English.</div>
<p>Orthography (literally &#8220;correct writing&#8221;) are the rules of writing a language, with special emphasis on standardized spelling. There was an <a href="http://www.wired.com/magazine/2012/01/st_essay_autocorrect/" target="_blank">argument</a> and <a href="http://www.wired.com/magazine/2012/01/st_essay_autocorrect_rebuttal/" target="_blank">rebuttal</a> a little while back in Wired Magazine for doing away with standardized spelling, which was also covered on <a href="http://www.npr.org/2012/03/01/147741215/duz-prawper-speling-mader-nemor" target="_blank">NPR</a>.</p>
<p>In the NPR interview, Anne Trubek says:</p>
<blockquote><p>So my basic argument is that if you look at the history of the English language, a lot of people think there are sort of immutable laws that are, you know, God-given or laws of nature. But actually, it&#8217;s a bunch of manmade prescriptions and guidelines that change over time.</p></blockquote>
<p>In other words, Trubek is saying that correctness is somewhat arbitrary, but as something becomes standardized, people start to treat right and wrong in an almost moral sense that isn&#8217;t justifiable.</p>
<p>Lee Simmons, a copy editor for Wired, who has the job of making sure words are spelled correctly, responds with:</p>
<blockquote><p>I would say spelling rules, for what they are, they&#8217;re all about making communication easier. If I could use an analogy, the Internet itself is essentially a set of standards &#8211; hardware and software standards &#8211; that make it possible for people with different devices to communicate. It creates a universal platform. And I would argue that our English spelling system, for all its flaws, provides just such a universal platform&#8230;. We can argue about whether we ought to reform the standards, make the system more logical, but I would argue that the standards themselves are something that we need to preserve.</p></blockquote>
<p>Simply put, it&#8217;s easier to read words that are spelled how we expect.</p>
<p>English also developed standard punctuation and good style, again with the objective of writing to be understood, but we sometimes forget things like that when writing code. We laugh at &#8220;The Department of Redundancy Department&#8221;, but many programmers don&#8217;t see anything wrong with code like:</p><pre class="crayon-plain-tag">// find the customer with the id
var customer = Customers.Find(customerId);</pre><p>or</p><pre class="crayon-plain-tag">return (a == b) ? true : false;</pre><p>Does it matter where braces and parentheses go? Certainly not in any moral sense, but there are standards that are more logical than others. If you&#8217;re reading an English sentence with parentheses (like this one) and suddenly things get a little weird( like this )then it interrupts the communication. Or what (if I) just start) throwing unnecessary( parentheses in?</p><pre class="crayon-plain-tag">return (theResult);</pre><p>Communication is actually kind of hard. Consider the fact that professional writers, who presumably have some talent for communication, almost never publish the first thing that shows up in the word processor. They revise, rework, rewrite until their ideas are expressed well. Then <em>someone else</em> comes along to fix the places where the writer still didn&#8217;t get it quite right.</p>
<p>Do people even notice if you make a similar effort to make your code easy to read? Or do they just say, &#8220;Oh, that&#8217;s easy,&#8221; compared to looking at some complicated mess and say, &#8220;Wow, that guy&#8217;s smart&#8221;?</p>
<p>Coding standards are a <em>tool</em>, one of many available to programmers to facilitate communication. Why not take advantage of it? Your future self will thank you.</p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2012/11/06/coding-standards-and-orthography/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Time to Think</title>
		<link>http://esmithy.net/2012/10/01/time-to-think/</link>
		<comments>http://esmithy.net/2012/10/01/time-to-think/#comments</comments>
		<pubDate>Tue, 02 Oct 2012 01:50:54 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Software Development]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=628</guid>
		<description><![CDATA[With time drawing short before a major deployment at work, we ran into a problem. Some people testing our application reported it timing-out on an important function. It quickly became a high priority that I was assigned to solve. Since &#8230; <a href="http://esmithy.net/2012/10/01/time-to-think/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>With time drawing short before a major deployment at work, we ran into a problem. Some people testing our application reported it timing-out on an important function. It quickly became a high priority that I was assigned to solve.</p>
<p><span id="more-628"></span></p>
<p>Since most of the people on our team work remotely, a group chat became the primary channel for the problem. There were operations people pointing out potentially significant log entries, QA and other interested people reporting every instance of the problem happening &#8212; or that they could not reproduce the problem all, ideas and theories about what could be going wrong, and my boss asking what kind of help I needed.</p>
<p>From this, and other similar experiences, I&#8217;ve learned a couple of things to do in situations like this:</p>
<h2>1. Get a bug report</h2>
<p>In times of panic, it can seem like a bureaucratic waste of time to file a bug report. But it is counter-productive to go chasing after a problem without getting the best understanding available of what the problem actually is. This is especially true in cases like this one where some people were seeing the problem frequently and others not at all.</p>
<h2>2. Go dark</h2>
<p>Of course you can&#8217;t just drop out of sight when people are counting on you to solve something quickly, but let them know that you just need some time to think, and barring any breakthrough discoveries, that you don&#8217;t want to be interrupted for a bit.</p>
<p>With all of the chat messages coming through, I spent five hours chasing after this and that without making much progress. Finally I came to my senses and just ignored everything and took time to <em>think</em>. After some time deeply analyzing one instance of the problem, it occurred to me that I had written a program a couple of years ago to test this exact scenario. Once I dusted that off and ran it a couple of times, the problem became painfully obvious.</p>
<p>Honestly, I felt kind of dumb that it took me so long to figure it out, but it reinforces how debilitating distraction can be.</p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2012/10/01/time-to-think/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Python Packaging Demystified</title>
		<link>http://esmithy.net/2012/08/25/python-packaging-demystified/</link>
		<comments>http://esmithy.net/2012/08/25/python-packaging-demystified/#comments</comments>
		<pubDate>Sat, 25 Aug 2012 22:43:34 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=616</guid>
		<description><![CDATA[Randomly take a couple of words from the following list, put them together, and there&#8217;s a decent chance you&#8217;ll come up with something real involving Python packaging. setup install dist distribute tools utils easy py The packaging situation is kind &#8230; <a href="http://esmithy.net/2012/08/25/python-packaging-demystified/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>Randomly take a couple of words from the following list, put them together, and there&#8217;s a decent chance you&#8217;ll come up with something real involving Python packaging.</p>
<ul>
<li>setup</li>
<li>install</li>
<li>dist</li>
<li>distribute</li>
<li>tools</li>
<li>utils</li>
<li>easy</li>
<li>py</li>
</ul>
<p>The packaging situation is kind of confusing with all the alternate tools, libraries and methods, so I&#8217;m going to take a stab at clarifying things &#8212; for how it seems right now, anyway.</p>
<p><span id="more-616"></span></p>
<h1> What is a package?</h1>
<p>Let&#8217;s step back for a second to review some Python vocabulary. A single <code>.py</code> file can be a <em>script</em> or a <em>module</em> &#8212; the basic difference being that a script is primarily meant to be run, while a module is primarily meant to export useful stuff for scripts or other modules. Of course a module can be executed too, typically with the <code>if __name__ == '__main__'</code> pattern. A <em>package</em> is a group of modules, providing some additional structure. In short, it&#8217;s a directory with an <code>__init__.py</code> and some other <code>.py</code> files in it.</p>
<h1> I don&#8217;t think that&#8217;s what I mean</h1>
<p>This is where the vocabulary gets muddled. If you&#8217;re familiar with <a href="http://pypi.python.org" target="_blank">PyPI</a> (pie-pee-eye), the Python Package Index, you&#8217;ll realize there&#8217;s something more than directories with <code>__init__.py</code> files. PyPI has what might better be termed <em>distributable packages</em>. Sure, they&#8217;re packages, but include metadata, documentation, and other stuff to make it easier to manage third-party Python software on your machine.</p>
<h1>Getting started</h1>
<p>The <a href="http://packages.python.org/distribute/" target="_blank">distribute</a> page cuts through much of the disutils, setuptools, easy_install, soup with this pithy graphic:</p>
<p><img src="http://python-distribute.org/pip_distribute.png" alt="http://python-distribute.org/pip_distribute.png" /></p>
<p>OK, since the New Hotness sounds good, we just want <a href="http://packages.python.org/distribute/" target="_blank"><em>distribute</em></a> and <a href="http://www.pip-installer.org/en/latest/index.html"><em>pip</em></a>. The graphic even shows you how to get them if you&#8217;re on *nix. Observant readers might notice that <em>pip</em> is being installed with <em>easy_install</em> and say, &#8220;Hey, wait a minute&#8230; <em>easy_install</em> is on the Old and Busted list, but we&#8217;re supposed to use that first?&#8221;</p>
<p>It turns out that <em>pip </em>(a recursive acronym for &#8220;Pip Installs Python&#8221;) currently requires <em>distribute</em>, and <em>distribute</em> happens to include <em>easy_install</em>. With Python 3.3 (which, by chance, has gone to release candidate status today), the features of the <em>distribute</em> package become <em>distutils2</em> in the Python standard library, and <em>distribute</em> is headed for the bit bucket. Sorry, this was supposed to be clarifying things, but it&#8217;s getting back into the realm of chaos. So for now, we&#8217;ll press forward with <em>distribute</em> as the Not-Totally-New Hotness, but workable strategy for today.</p>
<p>If you&#8217;re on Windows, you can just download distribute_setup.py from here:</p>
<p><a href="http://python-distribute.org/distribute_setup.py" target="_blank">http://python-distribute.org/distribute_setup.py</a></p>
<p>Then, as indicated above, you can run this from a command prompt:</p><pre class="crayon-plain-tag">python distribute_setup.py</pre><p>The install creates a <code>Scripts</code> subdirectory in your Python install directory, and puts <code>easy_install.exe</code> in there. It would be a good idea to add the <code>Scripts</code> subdirectory to your Windows <code>PATH</code>. The New Hotness <code>pip.exe</code> goes in there too, once that&#8217;s installed. If you go the <em>easy_install</em> route, you&#8217;ll get a UAC prompt on:</p><pre class="crayon-plain-tag">easy_install pip</pre><p>Then <em>pip</em> will be installed. Alternatively, (and without the need for UAC elevation) you can download the <em>pip</em> package directly from:</p>
<p><a href="http://pypi.python.org/pypi/pip/">http://pypi.python.org/pypi/pip/</a></p>
<p>The download is way down at the bottom of the page as a <code>.tar.gz</code> file, which isn&#8217;t the most convenient format for Windows, but <a href="http://www.7-zip.org/" target="_blank">7Zip</a> can remedy that difficulty. Once the archive is unpacked, you can install <em>pip</em> with this command from within in the directory where the <em>pip</em> archive was unpacked:</p><pre class="crayon-plain-tag">python setup.py install</pre><p>Once you&#8217;ve done this, you may delete the contents of the unpacked archive &#8212; everything you need has been copied into your Python install.</p>
<p>Now you can use <em>pip</em> to search for packages on PyPI and install them. For example:</p><pre class="crayon-plain-tag">pip search tornado</pre><p>This will find packages with &#8220;tornado&#8221; in their name or description, which includes not only the package specifically named &#8220;tornado&#8221;, but related packages as well. Installing a package is, not surprisingly:</p><pre class="crayon-plain-tag">pip install tornado</pre><p>This downloads the Tornado web server and copies it into the <code>site-packages</code> subdirectory of your Python installation (<code>C:\Python27\Lib\site-packages</code> for example). Since <code>site-packages</code> is in the Python <code>sys.path</code>, you can now import Tornado types from a Python script located anywhere on your system. Uninstalling a package is equally obvious:</p><pre class="crayon-plain-tag">pip uninstall tornado</pre><p>To see what packages you have installed, run:</p><pre class="crayon-plain-tag">pip freeze</pre><p></p>
<h1> Advanced pip</h1>
<p>&#8220;freeze&#8221; seems an odd verb for listing installed packages. This is because there is more to it: You can &#8220;freeze&#8221; a set of packages with version numbers that are all known to work peacefully together, creating a requirements file that can be used to recreate that same environment somewhere else. More details about this are in the pip <a href="http://www.pip-installer.org/en/latest/requirements.html" target="_blank">documentation</a>.</p>
<p>Let&#8217;s say you&#8217;re <em>developing</em> a package and want to make that package available to scripts on your system. I guess you could check your source out right into <code>site-packages</code> such that it can be edited in-place, but that seems awkward and unpleasant. Instead, you can install your package with <em>pip</em> using &#8220;edit mode&#8221;:</p><pre class="crayon-plain-tag">pip install -e C:\Projects\Alert\dvsdriver</pre><p>The path specified here is where my Python project resides from my source control checkout. With the <code>-e</code> option, instead of copying the package to <code>site-packages</code>, a <a href="http://docs.python.org/library/site.html" target="_blank">path configuration file</a> (a file with a <code>.pth</code> extension) is created in <code>site-packages</code> instead. The <code>.pth</code> file adds the <code>C:\Projects\Alert\dvsdriver</code> directory to the Python path, allowing you to develop your package in its natural location while still making it available for importing from other scripts.</p>
<p>You can also use <em>pip</em> to install packages from places other than PyPI, including local files, URLs of alternate package indexes, and directly from version control repositories.</p>
<h1>Virtual environments</h1>
<p>If you get into the situation where you need one version of a package for one script, and different version of the same package for another script, you&#8217;ll probably want to look into <em><a href="http://www.virtualenv.org/en/latest/index.html" target="_blank">virtualenv</a></em>. This is a package that lets you create multiple separate Python environments that are completely isolated, so they can have different sets of packages installed into them. The environments set up with <em>virtualenv</em> already come with <em>distribute</em> and <em>pip</em> in them, so they&#8217;re ready to be populated with other packages.</p>
<h1> Creating Distributable Packages</h1>
<p>Now that we&#8217;ve got a sense of what distributable packages are and how to use them, how do you create one?</p>
<p>The main thing is to create a setup.py module, which looks something like this:</p><pre class="crayon-plain-tag">from distutils.core import setup

setup(name='MyProject',
      version='1.0',
      author='Eric Smith',
      packages=['myproject'],
      )</pre><p>Notice that we&#8217;re importing from <code>distutils</code>, which means Old Stuff, but it gives us a working package that can be managed with <em>pip</em>:</p><pre class="crayon-plain-tag">pip install -e \Users\Eric\Projects\MyProject</pre><p>There&#8217;s good information about project structure, useful files (like <code>README.txt</code>), additional metadata to include in the <code>setup</code> call, and general package building in <a href="http://guide.python-distribute.org/creation.html">The Hitchhiker&#8217;s Guide to Packaging</a>.</p>
<h1>Looking forward</h1>
<p>As mentioned previously, <em>distutils2</em> is nearly upon us. With that, the <code>setup.py</code> script will be replaced by a declarative <code><a href="http://docs.python.org/dev/packaging/setupcfg">setup.cfg</a></code> and all previous libraries (<em>distutils</em>, <em>distribute</em>, <em>setuptools</em>) will be deprecated. <em>Pip</em> will stick around, though.</p>
<p>So there&#8217;s my attempt to clarify Python packaging. If I&#8217;ve made egregious errors, please let me know and I&#8217;ll fix them.</p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2012/08/25/python-packaging-demystified/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Three Suggestions for the Android Calendar App</title>
		<link>http://esmithy.net/2012/07/26/three-suggestions-for-the-android-calendar-app/</link>
		<comments>http://esmithy.net/2012/07/26/three-suggestions-for-the-android-calendar-app/#comments</comments>
		<pubDate>Fri, 27 Jul 2012 03:41:39 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Misc]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[User Interface]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=606</guid>
		<description><![CDATA[1. Two reminders I want a reminder at the specified reminder time, and another one at the appointment start time. This is how it typically goes for me: PHONE: Ding! ME: Oh yeah, I&#8217;ve got that meeting in 10 minutes. &#8230; <a href="http://esmithy.net/2012/07/26/three-suggestions-for-the-android-calendar-app/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<h2>1. Two reminders</h2>
<p>I want a reminder at the specified reminder time, and another one at the appointment start time. This is how it typically goes for me:</p>
<blockquote><p>PHONE: Ding!<br />
ME: Oh yeah, I&#8217;ve got that meeting in 10 minutes. Let me just finish this little bit of code in the meantime&#8230;</p>
<p>(15 minutes later)</p>
<p>CO-WORKER VIA IM: Hey, you coming to this meeting?<br />
ME: Shoot! Sorry! Be right there.</p></blockquote>
<p>The lack of this feature has created a market for tons of reminder &#8220;nag&#8221; apps that try to make up for it. I don&#8217;t really want a whole other app that piles on inane features in an attempt to justify its existence, though.</p>
<h2>2. Current month is white</h2>
<p>OK, this is kind of picky, but shouldn&#8217;t the current month be white while the previous and next months are gray? The current scheme is opposite that and my brain struggled with it.</p>
<p><a href="http://esmithy.net/content/ics-calendar.png"><img class="aligncenter size-medium wp-image-607" title="ics-calendar" src="http://esmithy.net/content/ics-calendar-168x300.png" alt="" width="168" height="300" /></a></p>
<h2>3. Combined day and agenda view</h2>
<p>The webOS calendar has a brilliant combination of an agenda and day view. It lets you see all of your appointments without huge swaths of blank screen pushing them out of view, but still makes it easy to add a new appointment at any time of day by tapping the accordion section.</p>
<p><a href="http://esmithy.net/content/webos-calendar.png"><img class="aligncenter size-medium wp-image-608" title="webos-calendar" src="http://esmithy.net/content/webos-calendar-200x300.png" alt="" width="200" height="300" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2012/07/26/three-suggestions-for-the-android-calendar-app/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>UI Horror: Untrimmed Validation</title>
		<link>http://esmithy.net/2012/07/07/ui-horror-untrimmed-validation/</link>
		<comments>http://esmithy.net/2012/07/07/ui-horror-untrimmed-validation/#comments</comments>
		<pubDate>Sat, 07 Jul 2012 20:50:07 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[User Interface]]></category>

		<guid isPermaLink="false">http://esmithy.net/?p=601</guid>
		<description><![CDATA[Hmm&#8230; this zip code looks pretty good to me. This was on the Discount Tire mobile site. It&#8217;s awesome that you can schedule a service appointment on the web using your mobile phone. It&#8217;s not so awesome that the form &#8230; <a href="http://esmithy.net/2012/07/07/ui-horror-untrimmed-validation/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>Hmm&#8230; this zip code looks pretty good to me.</p>
<p><img src="http://esmithy.net/content/validation.png" alt="" title="Validation Error" width="500" height="186" class="aligncenter size-full wp-image-602" /></p>
<p>This was on the Discount Tire mobile site. It&#8217;s awesome that you can schedule a service appointment on the web using your mobile phone. It&#8217;s not so awesome that the form validation doesn&#8217;t compensate for the trailing space that my phone&#8217;s keyboard automatically added. &#8220;Normal&#8221; people would give up when the site refused to accept such an obviously correct entry. </p>
]]></content:encoded>
			<wfw:commentRss>http://esmithy.net/2012/07/07/ui-horror-untrimmed-validation/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
