<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Alex J</title><link>https://alexj.org/index.xml</link><description>Recent content on Alex J</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Wed, 24 Jun 2020 17:00:00 +0100</lastBuildDate><atom:link href="https://alexj.org/index.xml" rel="self" type="application/rss+xml"/><item><title>Sauna 1.0</title><link>https://alexj.org/06/sauna-1-release/</link><pubDate>Wed, 24 Jun 2020 17:00:00 +0100</pubDate><guid>https://alexj.org/06/sauna-1-release/</guid><description>&lt;p>I&amp;rsquo;m pleased to announce the release of a new macOS app I&amp;rsquo;ve been working on for
a few weeks. Called &lt;em>Sauna&lt;/em>, it&amp;rsquo;s a lightweight app that displays your Steam
friends list and notifies you when a friend comes online. Of course, this is all
functionality that the Steam client provides but the goal of Sauna is to be a
good macOS citizen and to be extremely lightweight.&lt;/p>
&lt;p>Sauna regularly updates your friends list throughout the day and notifies you
when a friend comes online or starts playing a new game. It&amp;rsquo;s focused on doing
just this one thing.&lt;/p>
&lt;p>Sauna is useful for those of us who game in Boot Camp or have a dedicated PC for
playing games. With Sauna you can check in on your friends without rebooting
your Mac or booting your PC.&lt;/p>
&lt;p>You can download the first release of Sauna from the &lt;a href="https://github.com/alexjohnj/Sauna/releases">project&amp;rsquo;s GitHub
page&lt;/a>. It requires macOS 10.15 or later. You&amp;rsquo;ll need a Steam API
key to use it which you can get by filling in &lt;a href="https://steamcommunity.com/dev/apikey">this form&lt;/a> on
Steam&amp;rsquo;s website.&lt;/p>
&lt;img class="centred" srcset="https://alexj.org/images/posts/sauna-1-release/sauna-1-screenshot.png, https://alexj.org/images/posts/sauna-1-release/sauna-1-screenshot@2x.png 2x" src="https://alexj.org/images/posts/sauna-1-release/sauna-1-screenshot.png"/></description></item><item><title>Fetched Results Controller &amp; Diffable Data Source Snapshot Issues</title><link>https://alexj.org/01/nsfetchedresultscontroller-diffable-datasource/</link><pubDate>Thu, 02 Jan 2020 13:00:00 +0100</pubDate><guid>https://alexj.org/01/nsfetchedresultscontroller-diffable-datasource/</guid><description>&lt;p>In iOS 13, &lt;code>NSFetchedResultsController&lt;/code> gained support for delivering changes to
its content as a &lt;code>NSDiffableDataSourceSnapshot&lt;/code> via. its delegate. The snapshot
contains the contents of the controller and can be used to populate a table or
collection view complete with animations for content updates.&lt;/p>
&lt;p>Unfortunately the API has a few issues. The way it&amp;rsquo;s bridged from Objective-C to
Swift makes using it a bit confusing and it doesn&amp;rsquo;t match all the functionality
of the older delegate methods. In this post I&amp;rsquo;ll document a couple of the issues
I&amp;rsquo;ve run into and how I&amp;rsquo;ve worked around them.&lt;/p>
&lt;h2 id="using-snapshots-in-swift">Using Snapshots in Swift&lt;/h2>
&lt;p>The first issue I encountered using the new API is accessing the snapshot from
Swift. The documentation declares the signature of the snapshot delegate method
as&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">controller&lt;/span>(&lt;span style="color:#069;font-weight:bold">_&lt;/span> controller: NSFetchedResultsController&amp;lt;NSFetchRequestResult&amp;gt;, didChangeContentWith snapshot: NSDiffableDataSourceSnapshot)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>but this isn&amp;rsquo;t correct as &lt;code>NSDiffableDataSourceSnapshot&lt;/code> has two generic
parameters for the &lt;code>SectionIdentifier&lt;/code> and &lt;code>ObjectIdentifier&lt;/code> that are
missing. The delegate method&amp;rsquo;s signature actually bridges from Objective-C as:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">controller&lt;/span>(&lt;span style="color:#069;font-weight:bold">_&lt;/span> controller: NSFetchedResultsController&amp;lt;NSFetchRequestResult&amp;gt;, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The snapshot is an instance of &lt;code>NSDiffableDataSourceSnapshotReference&lt;/code>, a class
that lacks the generic type parameters found on the struct version. By itself
this class seems useless as there aren&amp;rsquo;t any other APIs that use it and it seems
to only exist to bridge this delegate method. To use the snapshot, you must cast
it to a &lt;code>NSDiffableDataSourceSnapshot&amp;lt;String, NSManagedObjectID&amp;gt;&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">controller&lt;/span>(&lt;span style="color:#069;font-weight:bold">_&lt;/span> controller: NSFetchedResultsController&amp;lt;NSFetchRequestResult&amp;gt;, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">snapshot&lt;/span> = snapshot &lt;span style="color:#069;font-weight:bold">as&lt;/span> NSDiffableDataSourceSnapshot&amp;lt;&lt;span style="color:#366">String&lt;/span>, NSManagedObjectID&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Do something with the snapshot&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that you can put any type for the &lt;code>SectionIdentifier&lt;/code> and &lt;code>ItemIdentifier&lt;/code>
parameters and it&amp;rsquo;ll compile but only &lt;code>String&lt;/code> and &lt;code>NSManagedObjectID&lt;/code> are valid
as can be seen in the declaration of the Objective-C protocol:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objective-c" data-lang="objective-c">&lt;span style="display:flex;">&lt;span>- (&lt;span style="color:#078;font-weight:bold">void&lt;/span>)&lt;span style="color:#c0f">controller:&lt;/span>(NSFetchedResultsController &lt;span style="color:#555">*&lt;/span>)&lt;span style="color:#033">controller&lt;/span> &lt;span style="color:#c0f">didChangeContentWithSnapshot:&lt;/span>(NSDiffableDataSourceSnapshot&lt;span style="color:#555">&amp;lt;&lt;/span>NSString &lt;span style="color:#555">*&lt;/span>, NSManagedObjectID &lt;span style="color:#555">*&amp;gt;&lt;/span> &lt;span style="color:#555">*&lt;/span>)&lt;span style="color:#033">snapshot&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At this point you&amp;rsquo;ve got a normal &lt;code>NSDiffableDataSourceSnapshot&lt;/code> that can be
used with a table or collection view. I don&amp;rsquo;t quite understand why the delegate
method gets bridged this way though since the Objective-C declaration is using
lightweight generics that should bridge across to Swift generics.&lt;/p>
&lt;h2 id="tracking-object-updates">Tracking Object Updates&lt;/h2>
&lt;p>The traditional delegate methods for &lt;code>NSFetchedResultsController&lt;/code> updates
provide notifications of object insertions, updates, moves and deletes. Although
&lt;code>NSDiffableDataSourceSnapshot&lt;/code> supports tracking all these events, the snapshot
provided to &lt;code>NSFetchedResultsController&lt;/code>&amp;rsquo;s delegate only tracks insertions,
deletions and moves.&lt;/p>
&lt;p>If you need to track object updates via. a data source snapshot, you&amp;rsquo;ll need to
use the traditional delegate methods to build and maintain your own
snapshot. Using Swift&amp;rsquo;s new &lt;code>CollectionDifference&lt;/code> type this isn&amp;rsquo;t too hard,
particularly if you only need to deal with one section. Note that you won&amp;rsquo;t be
able to use the &lt;code>NSManagedObjectID&lt;/code> as an &lt;code>ItemIdentifier&lt;/code> in the snapshot
though for reasons I&amp;rsquo;ll explain below.&lt;/p>
&lt;p>In my situation when dealing with a single section I was able to get away with
an implementation like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0a8;font-weight:bold">UIKit&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0a8;font-weight:bold">CoreData&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0a8;font-weight:bold">Combine&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">/// A singleton section for the snapshot&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">struct&lt;/span> &lt;span style="color:#0a8;font-weight:bold">Section&lt;/span>: &lt;span style="color:#366">Hashable&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">private&lt;/span> &lt;span style="color:#069;font-weight:bold">init&lt;/span>() { }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">static&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">main&lt;/span> = Section()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">final&lt;/span> &lt;span style="color:#069;font-weight:bold">class&lt;/span> &lt;span style="color:#0a8;font-weight:bold">SingleSectionController&lt;/span>&amp;lt;Object: NSManagedObject&amp;gt;: NSObject, NSFetchedResultsControllerDelegate {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @Published
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">results&lt;/span> = NSDiffableDataSourceSnapshot&amp;lt;Section, Object&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">private&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">resultsController&lt;/span>: NSFetchedResultsController&amp;lt;Object&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">init&lt;/span>(context: NSManagedObjectContext, request: NSFetchRequest&amp;lt;Object&amp;gt;) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">self&lt;/span>.resultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sectionNameKeyPath: &lt;span style="color:#069;font-weight:bold">nil&lt;/span>, cacheName: &lt;span style="color:#069;font-weight:bold">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">super&lt;/span>.&lt;span style="color:#069;font-weight:bold">init&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> resultsController.delegate = &lt;span style="color:#069;font-weight:bold">self&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">fetchData&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Handle errors gracefully in production&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>! resultsController.performFetch()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> generateNewSnapshot()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">private&lt;/span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">generateNewSnapshot&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">initialSnapshot&lt;/span> = NSDiffableDataSourceSnapshot&amp;lt;Section, Object&amp;gt;()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> initialSnapshot.appendSections([.main])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">objects&lt;/span> = resultsController.fetchedObjects {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> initialSnapshot.appendItems(objects, toSection: .main)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">self&lt;/span>.results = initialSnapshot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// &lt;/span>&lt;span style="color:#09f;font-weight:bold;font-style:italic">MARK:&lt;/span>&lt;span style="color:#09f;font-style:italic"> - Fetched Results Controller Delegate&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">private&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">transientChanges&lt;/span>: [CollectionDifference&amp;lt;Object&amp;gt;.Change] = []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">private&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">updatedObjects&lt;/span>: Set&amp;lt;Object&amp;gt; = []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">controllerWillChangeContent&lt;/span>(&lt;span style="color:#069;font-weight:bold">_&lt;/span> controller: NSFetchedResultsController&amp;lt;NSFetchRequestResult&amp;gt;) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transientChanges.removeAll()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> updatedObjects.removeAll()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">controller&lt;/span>(&lt;span style="color:#069;font-weight:bold">_&lt;/span> controller: NSFetchedResultsController&amp;lt;NSFetchRequestResult&amp;gt;, didChange anObject: &lt;span style="color:#366">Any&lt;/span>, at indexPath: IndexPath?, &lt;span style="color:#069;font-weight:bold">for&lt;/span> type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">guard&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">object&lt;/span> = anObject &lt;span style="color:#069;font-weight:bold">as&lt;/span>? Object &lt;span style="color:#069;font-weight:bold">else&lt;/span> { &lt;span style="color:#069;font-weight:bold">return&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">switch&lt;/span> type {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .insert:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">insertionIndex&lt;/span> = newIndexPath!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transientChanges.append(.insert(offset: insertionIndex.row, element: object, associatedWith: &lt;span style="color:#069;font-weight:bold">nil&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .update:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> updatedObjects.insert(object)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .move:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">sourceIndex&lt;/span> = indexPath!.row
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">destinationIndex&lt;/span> = newIndexPath!.row
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> updatedObjects.insert(object)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transientChanges.append(.insert(offset: destinationIndex, element: object, associatedWith: sourceIndex))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transientChanges.append(.remove(offset: sourceIndex, element: object, associatedWith: destinationIndex))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .delete:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">deletedIndex&lt;/span> = indexPath!.row
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> transientChanges.append(.remove(offset: deletedIndex, element: object, associatedWith: &lt;span style="color:#069;font-weight:bold">nil&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> @unknown &lt;span style="color:#069;font-weight:bold">default&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fatalError(&lt;span style="color:#c30">&amp;#34;Unhandled &lt;/span>&lt;span style="color:#a00">\(&lt;/span>NSFetchedResultsChangeType.&lt;span style="color:#069;font-weight:bold">self&lt;/span>&lt;span style="color:#a00">)&lt;/span>&lt;span style="color:#c30"> &lt;/span>&lt;span style="color:#a00">\(&lt;/span>type&lt;span style="color:#a00">)&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">controllerDidChangeContent&lt;/span>(&lt;span style="color:#069;font-weight:bold">_&lt;/span> controller: NSFetchedResultsController&amp;lt;NSFetchRequestResult&amp;gt;) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">guard&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">collectionDifference&lt;/span> = CollectionDifference(transientChanges) &lt;span style="color:#069;font-weight:bold">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// In theory, NSFetchedResultsController should deliver valid changes. In practice, I don&amp;#39;t trust it so fall&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// back to generating a new snapshot if the changes can&amp;#39;t be used as a diff.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> assertionFailure(&lt;span style="color:#c30">&amp;#34;Unable to create a collection difference from the changes &lt;/span>&lt;span style="color:#a00">\(&lt;/span>transientChanges&lt;span style="color:#a00">)&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> generateNewSnapshot()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">newSnapshot&lt;/span> = &lt;span style="color:#069;font-weight:bold">self&lt;/span>.results
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> change &lt;span style="color:#069;font-weight:bold">in&lt;/span> collectionDifference {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">switch&lt;/span> change {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .insert(&lt;span style="color:#f60">0&lt;/span>, &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">object&lt;/span>, &lt;span style="color:#069;font-weight:bold">_&lt;/span>) &lt;span style="color:#069;font-weight:bold">where&lt;/span> newSnapshot.numberOfItems(inSection: .main) == &lt;span style="color:#f60">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newSnapshot.appendItems([object], toSection: .main)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .insert(&lt;span style="color:#f60">0&lt;/span>, &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">object&lt;/span>, &lt;span style="color:#069;font-weight:bold">_&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newSnapshot.insertItems([object], beforeItem: newSnapshot.itemIdentifiers(inSection: .main).first!)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .insert(newSnapshot.itemIdentifiers(inSection: .main).count, &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">object&lt;/span>, &lt;span style="color:#069;font-weight:bold">_&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newSnapshot.appendItems([object], toSection: .main)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .insert(&lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">index&lt;/span>, &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">object&lt;/span>, &lt;span style="color:#069;font-weight:bold">_&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">existingItem&lt;/span> = newSnapshot.itemIdentifiers(inSection: .main)[index]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newSnapshot.insertItems([object], beforeItem: existingItem)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">case&lt;/span> .remove(&lt;span style="color:#069;font-weight:bold">_&lt;/span>, &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">object&lt;/span>, &lt;span style="color:#069;font-weight:bold">_&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newSnapshot.deleteItems([object])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newSnapshot.reloadItems(&lt;span style="color:#366">Array&lt;/span>(updatedObjects))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> assert(newSnapshot.itemIdentifiers == resultsController.fetchedObjects ?? [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#c30">&amp;#34;Final snapshots items do not match the FRC&amp;#39;s fetched objects&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">self&lt;/span>.results = newSnapshot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This tracks insertions, updates, moves and deletions using Swift&amp;rsquo;s
&lt;code>CollectionDifference&lt;/code> type to handle applying the updates in the correct
order. Compared to the &lt;code>controller(_:didChangeContentWith:)&lt;/code> method, this
implementation does have one downside. As the snapshot&amp;rsquo;s &lt;code>ItemIdentifier&lt;/code> are
instances of &lt;code>NSManagedObject&lt;/code>, you can&amp;rsquo;t apply the snapshot from a background
queue (or at least, a different queue to the controller&amp;rsquo;s context&amp;rsquo;s queue)
whereas you can when using &lt;code>NSManagedObjectID&lt;/code>s as identifiers.&lt;/p>
&lt;h2 id="the-problem-with-object-ids">The Problem with Object IDs&lt;/h2>
&lt;p>The reason for using &lt;code>NSManagedObject&lt;/code> as the &lt;code>ItemIdentifier&lt;/code> instead of
&lt;code>NSManagedObjectID&lt;/code> is due to how &lt;code>NSFetchedResultsController&lt;/code> handles temporary
object IDs. As soon as an object matching the controller&amp;rsquo;s fetch request is
inserted into the context, the controller will notify the delegate of an
insertion. At this point the object only has a temporary ID as it hasn&amp;rsquo;t been
saved to the context&amp;rsquo;s persistent store yet. If we were using an
&lt;code>NSManagedObjectID&lt;/code> as the snapshot&amp;rsquo;s &lt;code>ItemIdentifier&lt;/code>, we&amp;rsquo;d add a temporary ID
to the snapshot at this point.&lt;/p>
&lt;p>When the context is saved, the inserted object is given a (different) permanent
object ID. Although we can continue to use the temporary ID to retrieve the
object from its context, the &lt;code>NSManagedObjectID&lt;/code> returned by the object&amp;rsquo;s
&lt;code>objectID&lt;/code> property will be the new, permanent ID.&lt;/p>
&lt;p>Any subsequent updates to the object will be delivered to the results
controller&amp;rsquo;s delegate as updates or moves. When we try to update the data source
snapshot using the updated object&amp;rsquo;s &lt;code>NSManagedObjectID&lt;/code>, the data source will
throw an exception because it only contains the temporary object ID, not the
permanent ID.&lt;/p>
&lt;p>If you must use &lt;code>NSManagedObjectID&lt;/code> as the data source&amp;rsquo;s &lt;code>ItemIdentifier&lt;/code>, you
can (kind of) exclude objects with a temporary ID from the results controller by
creating a private, read-only child context of the main context for the results
controller. The child context (and by extension the results controller) will not
know about newly inserted objects until the parent context is saved at which
point the objects will have a permanent ID (assuming the parent context is a
root context).&lt;/p>
&lt;p>This setup creates some new problems though. If you need to merge changes into
the parent context from another context (say a background context), the merged
changes won&amp;rsquo;t propagate to the child context automatically, even if
&lt;code>automaticallyMergesChangesFromParent&lt;/code> is &lt;code>true&lt;/code> for the child context.&lt;/p></description></item><item><title>Normal Screensaver</title><link>https://alexj.org/05/normal-screensaver/</link><pubDate>Mon, 28 May 2018 09:00:00 +0100</pubDate><guid>https://alexj.org/05/normal-screensaver/</guid><description>&lt;p>I published a new project on GitHub today. It&amp;rsquo;s a screensaver for macOS called
Normal. It simply applies a Gaussian blur to the contents of the screen. I wrote
it a year ago when I was procrastinating from my dissertation. Go ahead and
&lt;a href="https://github.com/alexjohnj/Normal">check it out on GitHub&lt;/a>.&lt;/p>
&lt;!--theres-no-more--></description></item><item><title>A Banner View for iOS 11+</title><link>https://alexj.org/12/banner-view/</link><pubDate>Thu, 07 Dec 2017 18:00:00 +0000</pubDate><guid>https://alexj.org/12/banner-view/</guid><description>&lt;p>I needed a way to present non-modal alerts inside an app I&amp;rsquo;m working on and
wanted to use a banner system that&amp;rsquo;s a bit like what Tweetbot&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>
does. I ended up writing a &lt;code>UIView&lt;/code> subclass called &lt;code>BannerView&lt;/code> that I&amp;rsquo;ve &lt;a href="https://gist.github.com/alexjohnj/df4d969fa0ac6f29fa8a134c91fa30ff">open
sourced&lt;/a> for anybody to use.&lt;/p>
&lt;p>The banner view uses the new &lt;a href="https://developer.apple.com/documentation/uikit/uiview/2891102-safearealayoutguide">safe area layout guides&lt;/a> introduced
in iOS 11 to lay itself out correctly when navigation bars are present. The top
of the banner expands to fill the top safe area which looks really cool on an
iPhone X&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>:&lt;/p>
&lt;video loop autoplay muted>
&lt;source src="https://alexj.org/videos/banner-view/no-nav.webm" type="video/webm">
&lt;source src="https://alexj.org/videos/banner-view/no-nav.mp4" type="video/mp4">
Ack! Your browser doesn't support WebM or MP4 videos! What is this, the 3DS browser?
&lt;/video>
&lt;p>The appearance of the banner is easily customised and an optional icon can be
displayed alongside the message. There&amp;rsquo;s support for a swipe-to-dismiss gesture
baked in too.&lt;/p>
&lt;p>There are a few minor issues but they shouldn&amp;rsquo;t be too hard to fix. The banner
doesn&amp;rsquo;t quite resize properly if the phone&amp;rsquo;s rotated and things get a bit funky
if the banner&amp;rsquo;s embedded inside a navigation bar with &lt;code>prefersLargeTitles = true&lt;/code>. Oh, and there&amp;rsquo;s no built in way to perform an action when the user taps
the banner although you could accomplish that by attaching a
&lt;code>UITapGestureRecognizer&lt;/code> to the view.&lt;/p>
&lt;p>Again, you can get &lt;code>BannerView&lt;/code> from &lt;a href="https://gist.github.com/alexjohnj/df4d969fa0ac6f29fa8a134c91fa30ff">here&lt;/a>. It&amp;rsquo;s licensed under
an MIT license.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>I haven&amp;rsquo;t used Tweetbot for almost a year but I know it &lt;em>used&lt;/em> to have these banners.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>At least, it does in the simulator. I can only imagine what it looks like on an actual device.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>How a Core Data Attribute's Name Can Lead to Crashes</title><link>https://alexj.org/11/core-data-attribute-naming/</link><pubDate>Fri, 10 Nov 2017 19:55:00 +0000</pubDate><guid>https://alexj.org/11/core-data-attribute-naming/</guid><description>&lt;p>&lt;em>TL;DR: You can&amp;rsquo;t name a Core Data attribute &lt;code>new*&lt;/code> in Objective-C or Swift
because of how automatic reference counting interacts with manual reference
counting. If you do, you&amp;rsquo;ll crash unreliably when your Core Data stack is
deallocated.&lt;/em>&lt;/p>
&lt;p>I&amp;rsquo;ve spent the better part of a day trying to fix this hard to track down bug in
my Core Data stack that was causing a crash. The crash was introduced when I
added a new entity to my object model. The &lt;code>NSManagedObject&lt;/code> subclass looked a
little like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">public&lt;/span> &lt;span style="color:#069;font-weight:bold">final&lt;/span> &lt;span style="color:#069;font-weight:bold">class&lt;/span> &lt;span style="color:#0a8;font-weight:bold">RenamedThing&lt;/span>: NSManagedObject {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">@NSManaged&lt;/span> &lt;span style="color:#069;font-weight:bold">private&lt;/span>(&lt;span style="color:#069;font-weight:bold">set&lt;/span>) &lt;span style="color:#069;font-weight:bold">public&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">newName&lt;/span>: &lt;span style="color:#366">String&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">@NSManaged&lt;/span> &lt;span style="color:#069;font-weight:bold">private&lt;/span>(&lt;span style="color:#069;font-weight:bold">set&lt;/span>) &lt;span style="color:#069;font-weight:bold">public&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">oldName&lt;/span>: &lt;span style="color:#366">String&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">@NSManaged&lt;/span> &lt;span style="color:#069;font-weight:bold">public&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">markedForLocalDeletionDate&lt;/span>: Date?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Any tests that accessed &lt;code>RenamedThing&lt;/code>&amp;rsquo;s &lt;code>newName&lt;/code> attribute would crash when
the &lt;code>NSPersistentContainer&lt;/code> was set to &lt;code>nil&lt;/code> in the test&amp;rsquo;s &lt;code>tearDown()&lt;/code>
method. The crash message looked like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>xctest(..) malloc: *** error for object ..: pointer being freed was not allocated
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*** set a breakpoint in malloc_error_break to debug
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After eliminating any possibility of this being a bug related to Core Data&amp;rsquo;s
concurrency model, I broke out Xcode&amp;rsquo;s memory graph debugger and then &lt;code>NSZombie&lt;/code>
to see if I could find anything. This felt like an odd thing to do since this is
Swift and so automatic reference counting (ARC) is active and I can&amp;rsquo;t make any
manual calls to &lt;code>release&lt;/code> anywhere. In the end, neither the memory graph
debugger nor &lt;code>NSZombie&lt;/code> were of any help.&lt;/p>
&lt;p>At this point I was starting to think that this might be a bug in Core Data. To
check I wasn&amp;rsquo;t doing something crazy stupid with ARC though, I checked through
the documentation on ARC. Eventually, I came across the &lt;a href="https://developer.apple.com/library/content/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226-CH1-SW14">Transitioning to ARC
release notes&lt;/a> and found this lovely tidbit under the &amp;ldquo;new&amp;rdquo;
rules section:&lt;/p>
&lt;blockquote>
&lt;p>You cannot give an accessor a name that begins with new. This in turn means
that you can’t, for example, declare a property whose name begins with new
unless you specify a different getter[.]&lt;/p>
&lt;/blockquote>
&lt;p>I renamed the &lt;code>newName&lt;/code> attribute to something else and, of course, my tests
stopped crashing. This is both my favourite and least favourite type of bug&amp;mdash;a
bug that takes hours to figure out but just a second to fix.&lt;/p>
&lt;p>I was surprised to find that the transitioning to ARC guide is still relevant
today, even in Swift. I &lt;em>think&lt;/em> this means that Core Data is still using manual
memory management (at least in parts) which is a little surprising given how
long ARC has been available and how complicated the memory management must be
for Core Data. Still, &amp;ldquo;if it ain&amp;rsquo;t broke then don&amp;rsquo;t fix it&amp;rdquo; is probably a
&lt;em>really&lt;/em> good rule to stick by for something as complex as Core Data.&lt;/p></description></item><item><title>Core Data Property-Level Model Validation in Swift 4</title><link>https://alexj.org/09/core-data-model-validation-swift4/</link><pubDate>Mon, 25 Sep 2017 19:00:00 +0100</pubDate><guid>https://alexj.org/09/core-data-model-validation-swift4/</guid><description>&lt;p>I ran into an issue while writing property validation methods for a Core Data
stack where the methods simply weren&amp;rsquo;t being called. After a bit of head
scratching I realised it was because the methods needed to be annotated with
&lt;code>@objc&lt;/code>. This wasn&amp;rsquo;t needed in Swift 3 because &lt;code>@objc&lt;/code> was inferred on all
methods of subclasses of &lt;code>NSObject&lt;/code>; however, the behaviour of &lt;code>@objc&lt;/code> inference
&lt;a href="https://github.com/apple/swift-evolution/blob/master/proposals/0160-objc-inference.md">changed in Swift 4&lt;/a>. Now, subclasses of &lt;code>NSObject&lt;/code>
must explicitly mark methods that need to be accessible from Objective-C. This
took longer to realise than it should have since the &lt;a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/ObjectValidation.html#//apple_ref/doc/uid/TP40001075-CH20-SW1">Core Data
documentation&amp;rsquo;s&lt;/a> sample code for property-level
validation hasn&amp;rsquo;t been updated for Swift 4.&lt;/p>
&lt;!--theres-no-more--></description></item><item><title>Strongly Typed Notifications in Swift</title><link>https://alexj.org/07/swift-typed-notifications/</link><pubDate>Fri, 07 Jul 2017 14:48:00 +0000</pubDate><guid>https://alexj.org/07/swift-typed-notifications/</guid><description>&lt;p>While working on rewriting &lt;a href="https://github.com/alexjohnj/spotijack">Spotijack&lt;/a> in Swift, I started to
feel dissatisfied with
Foundation&amp;rsquo;s &lt;a href="https://developer.apple.com/documentation/foundation/notificationcenter">notification API&lt;/a>. It&amp;rsquo;s a stringly typed
API that makes heavy use of &lt;code>Any&lt;/code> and as someone who &lt;em>loves&lt;/em> their types this
makes me sad. To cheer myself up, I set about writing a more strongly typed
notification system.&lt;/p>
&lt;p>The end result is a small
library&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&amp;mdash;&lt;a href="https://github.com/alexjohnj/typednotification">TypedNotification&lt;/a>&amp;mdash;that
provides a set of protocols defining a more descriptive type system for
notifications. Check out the GitHub project if you&amp;rsquo;re interested. There&amp;rsquo;s a
Playground in it demoing the protocols. The rest of this blog post will cover
them in a bit more detail.&lt;/p>
&lt;h2 id="features">Features&lt;/h2>
&lt;p>Before I show you the protocols, let me run you through what I wanted to
achieve.&lt;/p>
&lt;h3 id="closure-based-api">Closure Based API&lt;/h3>
&lt;p>First, I wanted to have a closure based API that closely mirrors the foundation
API. Swift has a concise syntax for closures that makes it easy to create short
pieces of code. For longer blocks of code, Swift has function references that
let functions be used as closures. Furthermore, closures encapsulate information
on the types of their arguments which selectors lack. The foundation API already
contains a block based function for notifications so my protocol can simply
mimic that API.&lt;/p>
&lt;h3 id="notifications-as-types">Notifications as Types&lt;/h3>
&lt;p>The second requirement is for notifications to convey as much information as
possible using their types. That means no identifying notifications by a string
(at least, not to the user) and no &lt;code>userInfo&lt;/code> dictionary to attach data to a
notification. A notification&amp;rsquo;s identifier should be inherent to its type and any
data attached to the notification should be declared as properties.&lt;/p>
&lt;h3 id="automatic-removal-of-observers">Automatic Removal of Observers&lt;/h3>
&lt;p>Finally, I wanted to get rid of the need to think about the lifetime of
observers. The block based foundation API returns an opaque object that users
must remember to unregister before it is deallocated. Forgetting to manage these
objects creates bugs that are difficult to pass off as features.&lt;/p>
&lt;h2 id="the-_typednotifcation_-protocol">The &lt;em>TypedNotifcation&lt;/em> Protocol&lt;/h2>
&lt;h3 id="overview">Overview&lt;/h3>
&lt;p>This is the core protocol of &lt;em>TypedNotifications&lt;/em>. Types that conform to this
protocol can be posted as notifications. The protocol declaration is simple:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">protocol&lt;/span> &lt;span style="color:#0a8;font-weight:bold">TypedNotification&lt;/span>: Namespaced {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> associatedtype Sender
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">/// The name of the notification to be used as an identifier.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">static&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">name&lt;/span>: &lt;span style="color:#366">String&lt;/span> { &lt;span style="color:#069;font-weight:bold">get&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">/// The object sending the notification.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">sender&lt;/span>: Sender { &lt;span style="color:#069;font-weight:bold">get&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>All types conforming to &lt;code>TypedNotification&lt;/code> have a &lt;code>name&lt;/code> property that&amp;rsquo;s used
to identify the notification and a &lt;code>sender&lt;/code> property that&amp;rsquo;s used to identify the
sender. The &lt;code>sender&lt;/code> property has an associated type that can be used to
constrain senders to a subset of types.&lt;/p>
&lt;p>This protocol reduces the chances of making a mistake with the stringly typed
notification system by only having to declare the name of the notification
once. It also adds some information about the contents of the notification by
providing an associated type for the &lt;code>sender&lt;/code> property.&lt;/p>
&lt;p>To reduce the chances of a notification name collision, I&amp;rsquo;ve made the
&lt;code>TypedNotification&lt;/code> protocol conform to a &lt;code>Namespaced&lt;/code> protocol which looks
like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">protocol&lt;/span> &lt;span style="color:#0a8;font-weight:bold">Namespaced&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">static&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">namespace&lt;/span>: &lt;span style="color:#366">String&lt;/span> { &lt;span style="color:#069;font-weight:bold">get&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A protocol extension on &lt;code>TypedNotifcation&lt;/code> uses the &lt;code>Namespaced&lt;/code> protocol to
provide a default implementation for the &lt;code>name&lt;/code> property:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">extension&lt;/span> &lt;span style="color:#0a8;font-weight:bold">TypedNotification&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">static&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">name&lt;/span>: &lt;span style="color:#366">String&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#a00">\(&lt;/span>&lt;span style="color:#069;font-weight:bold">Self&lt;/span>.namespace&lt;span style="color:#a00">)&lt;/span>&lt;span style="color:#c30">.&lt;/span>&lt;span style="color:#a00">\(&lt;/span>&lt;span style="color:#069;font-weight:bold">Self&lt;/span>.&lt;span style="color:#069;font-weight:bold">self&lt;/span>&lt;span style="color:#a00">)&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will generate a notification name using the &lt;code>namespace&lt;/code> property and the
name of the type conforming to &lt;code>TypedNotifcation&lt;/code>.&lt;/p>
&lt;h3 id="usage">Usage&lt;/h3>
&lt;p>For each notification that your application posts, create a type that conforms
to &lt;code>TypedNotification&lt;/code> and implement the required properties. Thanks to the
aforementioned protocol extension, only the &lt;code>sender&lt;/code> and &lt;code>namespace&lt;/code> properties
need to be implemented. You can write a protocol extension on &lt;code>Namespaced&lt;/code> to
reduce the implementation down to just the &lt;code>sender&lt;/code> property. As an example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">extension&lt;/span> &lt;span style="color:#0a8;font-weight:bold">Namespaced&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">static&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">namespace&lt;/span>: &lt;span style="color:#366">String&lt;/span> { &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#c30">&amp;#34;org.alexj&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">struct&lt;/span> &lt;span style="color:#0a8;font-weight:bold">ExampleNotification&lt;/span>: TypedNotification {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">sender&lt;/span>: ExampleClass
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">newValue&lt;/span>: &lt;span style="color:#366">Double&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here, &lt;code>ExampleClass&lt;/code> is the only class that&amp;rsquo;s responsible for sending instances
of &lt;code>ExampleNotification&lt;/code>. If multiple types can post a notification, consider
constraining the &lt;code>sender&lt;/code> property using a protocol. If worst comes to worst,
you can make &lt;code>sender&lt;/code> an instance of &lt;code>Any?&lt;/code> at the expense of some type safety.&lt;/p>
&lt;h2 id="the-_typednotificationcenter_-protocol">The &lt;em>TypedNotificationCenter&lt;/em> Protocol&lt;/h2>
&lt;h3 id="overview-1">Overview&lt;/h3>
&lt;p>To post instances of &lt;code>TypedNotifcation&lt;/code>, the library provides another protocol
called &lt;code>TypedNotificationCenter&lt;/code>. This declares three methods to post
notifications, add observers and remove observers:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">protocol&lt;/span> &lt;span style="color:#0a8;font-weight:bold">TypedNotificationCenter&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">/// Post a `TypedNotification`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">post&lt;/span>&amp;lt;T: TypedNotification&amp;gt;(&lt;span style="color:#069;font-weight:bold">_&lt;/span> notification: T)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">/// Register a block to be executed when a `TypedNotification` is posted.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">addObserver&lt;/span>&amp;lt;T: TypedNotification&amp;gt;(forType type: T.&lt;span style="color:#069;font-weight:bold">Type&lt;/span>, object obj: T.Sender?,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> queue: OperationQueue?, using block: @escaping (T) -&amp;gt; &lt;span style="color:#366">Void&lt;/span>) -&amp;gt; NotificationObserver
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">/// Deregister a `NotificationObserver`.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">removeObserver&lt;/span>(observer: NotificationObserver)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Aside from a different type signature, these methods mirror the Foundation
&lt;code>NotificationCenter&lt;/code> APIs. &lt;em>TypedNotification&lt;/em> includes an extension on
&lt;code>NotificationCenter&lt;/code> that adds conformance to the &lt;code>TypedNotificationCenter&lt;/code>
protocol. You can use the protocol when writing tests.&lt;/p>
&lt;h3 id="the-_notifcationobserver_-class">The &lt;em>NotifcationObserver&lt;/em> Class&lt;/h3>
&lt;p>The &lt;code>addObserver&lt;/code> method returns an instance of &lt;code>NotificationObserver&lt;/code> rather
than the &lt;code>NSObjectProtocol&lt;/code> conforming object returned by the Foundation
API. &lt;code>NotificationObserver&lt;/code> is a lightweight class that stores an
&lt;code>NSObjectProtocol&lt;/code> conforming object. When a &lt;code>NotificationObserver&lt;/code> is
deallocated, &lt;code>removeObserver&lt;/code> is automatically called. There&amp;rsquo;s no need to
manually remove observers any more, just store&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>
a strong reference to the &lt;code>NotificationObserver&lt;/code>.&lt;/p>
&lt;h3 id="usage-1">Usage&lt;/h3>
&lt;p>Usage is almost identical to using the Foundation API. Building on the previous
example, here&amp;rsquo;s how to use a &lt;code>TypedNotification&lt;/code> conforming type with
&lt;code>NotificationCenter&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">class&lt;/span> &lt;span style="color:#0a8;font-weight:bold">ExampleClass&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">private&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">center&lt;/span> = NotificationCenter.&lt;span style="color:#069;font-weight:bold">default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">private&lt;/span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">_valueObserver&lt;/span>: NotificationObserver? = &lt;span style="color:#069;font-weight:bold">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">init&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _valueObserver = center.addObserver(forType: ExampleNotification.&lt;span style="color:#069;font-weight:bold">self&lt;/span>, object: &lt;span style="color:#069;font-weight:bold">self&lt;/span>, queue: &lt;span style="color:#069;font-weight:bold">nil&lt;/span>) { (noti) &lt;span style="color:#069;font-weight:bold">in&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#c30">&amp;#34;New value: &lt;/span>&lt;span style="color:#a00">\(&lt;/span>noti.newValue&lt;span style="color:#a00">)&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">value&lt;/span> = &lt;span style="color:#f60">0.0&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">didSet&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> center.post(ExampleNotification(sender: &lt;span style="color:#069;font-weight:bold">self&lt;/span>, newValue: value))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that the closure parameter &lt;code>noti&lt;/code> is of type &lt;code>ExampleNotification&lt;/code> so you
can directly access the &lt;code>newValue&lt;/code> property without any downcasting. Also note
that the observer is tied to the lifetime of &lt;code>ExampleClass&lt;/code>. When an instance of
&lt;code>ExampleClass&lt;/code> is deallocated, &lt;code>_valueObserver&lt;/code> will remove itself as an
observer.&lt;/p>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>I think the advantages of this library compared to the Foundation API are
clear. Representing notifications as types improves the safety of your code by
eliminating manually managed string identifiers and weakly typed &lt;code>userInfo&lt;/code>
dictionaries. A further advantage of typed notifications is
self-documentation. As data attached to a notification is part of the type,
there&amp;rsquo;s no need to document the keys of a &lt;code>userInfo&lt;/code> dictionary. Users can look
at the public interface of a &lt;code>TypedNotification&lt;/code> conforming type to see what
data it provides.&lt;/p>
&lt;p>The &lt;code>TypedNotificationCenter&lt;/code> protocol goes hand in hand with the
&lt;code>TypedNotification&lt;/code> protocol. It improves run time safety by automatically
removing observers when they are deallocated, eliminating an entire class of
bugs. Furthermore, it provides a starting point for writing tests for
notifications.&lt;/p>
&lt;p>The &lt;em>TypedNotification&lt;/em> library is available
from &lt;a href="https://github.com/alexjohnj/typednotification">GitHub&lt;/a> under an MIT license. It is compatible
with the Swift Package Manager and Carthage.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>I believe the cool kids call this a µFramework.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Xcode will emit a warning if you don&amp;rsquo;t store the returned &lt;code>NotificationObserver&lt;/code>. It might be a good idea to enable &amp;ldquo;Treat Warnings as Errors&amp;rdquo; in your build settings.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Fixing MATLAB on Fedora</title><link>https://alexj.org/03/fixing-matlab-fedora/</link><pubDate>Fri, 10 Mar 2017 15:00:00 +0000</pubDate><guid>https://alexj.org/03/fixing-matlab-fedora/</guid><description>&lt;p>The other week I had to set up a new installation of MATLAB r2016a on a
(somewhat) fresh Fedora installation. After running the installer, I ran into a
bunch of library errors when trying to run external C++ programs&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>
from MATLAB or when trying to plot something. When running an external C++
program, MATLAB would complain about missing versions of &lt;code>CXXABI&lt;/code> and &lt;code>GLIBCXX&lt;/code>
in &lt;code>libstdc++&lt;/code>&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>. When plotting something, MATLAB would complain about
&lt;code>libmwosgserver.so&lt;/code>.&lt;/p>
&lt;p>The first problem is caused by MATLAB trying to use its own, older version of
&lt;code>libstdc++&lt;/code>. The second problem is caused by MATLAB trying to use the system&amp;rsquo;s
version of &lt;code>libmwosgserver&lt;/code>. To fix the error when running C++ programs, I had
to tell MATLAB to use the system&amp;rsquo;s version of &lt;code>libstdc++&lt;/code>. To fix the error when
plotting, I had to tell MATLAB to use its own OpenGL libraries rather than the
system&amp;rsquo;s.&lt;/p>
&lt;p>The fixes are easy to make, you need to edit a couple of lines in a startup
script. First of all, find out where the system&amp;rsquo;s version of &lt;code>libstdc++&lt;/code> is
located (&lt;code>$&lt;/code> indicates text to enter in a shell):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>$ find /usr -name &lt;span style="color:#c30">&amp;#39;libstdc++.so.6&amp;#39;&lt;/span> -exec dirname &lt;span style="color:#c30">&amp;#39;{}&amp;#39;&lt;/span> &lt;span style="color:#c30;font-weight:bold">\;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/usr/local/MATLAB/R2016a/sys/os/glnxa64
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/usr/lib64
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/usr/lib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The first line is MATLAB&amp;rsquo;s version of the library, the second the system&amp;rsquo;s
64-bit version of the library, the third the system&amp;rsquo;s 32-bit version. If you&amp;rsquo;re
somehow still on a 32-bit system you&amp;rsquo;ll need to note down the path
&lt;code>/usr/lib&lt;/code>. Otherwise, the path you need is &lt;code>/usr/lib64&lt;/code>. Now you need to edit
MATLAB&amp;rsquo;s init script. The exact location will depend on where you installed
MATLAB to. Assuming you installed MATLAB into the &lt;code>/usr/local&lt;/code> directory then
you&amp;rsquo;ll need to edit &lt;code>/usr/local/MATLAB/R2016a/bin/.matlab7rc.sh&lt;/code>.&lt;/p>
&lt;p>This script is essentially a massive switch statement that sets different
environment variables depending on which operating system and architecture
you&amp;rsquo;re on. Find the case statement corresponding to your architecture (&lt;code>glnxa64&lt;/code>
for 64-bit Linux) which will look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#033">$ARCH&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span> in
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> glnx86|glnxa64&lt;span style="color:#555">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For me that was around line 164 but it will probably depend on which version of
MATLAB you&amp;rsquo;re running. Inside this case, find the line that says&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#033">LDPATH_PREFIX&lt;/span>&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and change it to&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#033">LDPATH_PREFIX&lt;/span>&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#033">$MATLAB&lt;/span>&lt;span style="color:#c30">/sys/opengl/lib/&lt;/span>&lt;span style="color:#033">$ARCH&lt;/span>&lt;span style="color:#c30">:/usr/lib64&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This pushes MATLAB&amp;rsquo;s OpenGL libraries and the system&amp;rsquo;s C++ libraries to the
front of MATLAB&amp;rsquo;s load path, forcing it to use those versions of the
libraries. Save the file, restart MATLAB and you should be good to go.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Since I use fish as my shell, that meant any external command.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Really it was the external program complaining, not MATLAB.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Integrating Receipts with ledger-mode</title><link>https://alexj.org/07/integrating-receipts-with-ledger-mode/</link><pubDate>Sat, 02 Jul 2016 16:00:00 +0000</pubDate><guid>https://alexj.org/07/integrating-receipts-with-ledger-mode/</guid><description>&lt;p>I&amp;rsquo;ve written a couple of functions for Emacs&amp;rsquo; ledger-mode that make working with
receipts a bit easier. With the cursor on a transaction, calling
&lt;code>alex/ledger-attach-receipt&lt;/code> will prompt for a file. This function copies the
file to a receipts directory, renaming it to its hash and sorting it in
subdirectories according to the transaction&amp;rsquo;s year and month&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Finally,
the function adds a comment to the transaction with the hash of the file. The
function &lt;code>alex/ledger-open-attached-receipt&lt;/code> reads this comment and opens the
associated file in Emacs. The receipts folder can be customised through the
variable &lt;code>alex/ledger-receipt-folder&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">defvar&lt;/span> &lt;span style="color:#033">alex/ledger-receipt-folder&lt;/span> &lt;span style="color:#c30">&amp;#34;~/finance/receipts&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">defun&lt;/span> &lt;span style="color:#033">alex/ledger-get-xact-date&lt;/span> ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">&amp;#34;Read the effective date (before =) of a
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">transaction. Returns the date as a time value.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">ledger-navigate-beginning-of-xact&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">re-search-forward&lt;/span> &lt;span style="color:#033">ledger-iso-date-regexp&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">encode-time&lt;/span> &lt;span style="color:#f60">0&lt;/span> &lt;span style="color:#f60">0&lt;/span> &lt;span style="color:#f60">0&lt;/span> (&lt;span style="color:#c0f">string-to-number&lt;/span> (&lt;span style="color:#033">match-string&lt;/span> &lt;span style="color:#f60">4&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">string-to-number&lt;/span> (&lt;span style="color:#033">match-string&lt;/span> &lt;span style="color:#f60">3&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">string-to-number&lt;/span> (&lt;span style="color:#033">match-string&lt;/span> &lt;span style="color:#f60">2&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">defun&lt;/span> &lt;span style="color:#033">alex/ledger-construct-receipt-path&lt;/span> (&lt;span style="color:#033">date&lt;/span> &lt;span style="color:#033">hash&lt;/span> &lt;span style="color:#069">&amp;amp;optional&lt;/span> &lt;span style="color:#033">ext&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">&amp;#34;Construct a path to a receipt file. DATE is a time value. HASH is a
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">string. EXT is the file extension (with dot) and defaults to .pdf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">unless&lt;/span> &lt;span style="color:#033">ext&lt;/span> (&lt;span style="color:#366">setq&lt;/span> &lt;span style="color:#033">ext&lt;/span> &lt;span style="color:#c30">&amp;#34;.pdf&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#c0f">concat&lt;/span> (&lt;span style="color:#c0f">file-name-as-directory&lt;/span> &lt;span style="color:#033">alex/ledger-receipt-folder&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">file-name-as-directory&lt;/span> (&lt;span style="color:#c0f">format-time-string&lt;/span> &lt;span style="color:#c30">&amp;#34;%Y&amp;#34;&lt;/span> &lt;span style="color:#033">date&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">file-name-as-directory&lt;/span> (&lt;span style="color:#c0f">format-time-string&lt;/span> &lt;span style="color:#c30">&amp;#34;%m&amp;#34;&lt;/span> &lt;span style="color:#033">date&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#033">hash&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#033">ext&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">defun&lt;/span> &lt;span style="color:#033">alex/ledger-attach-receipt&lt;/span> ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">&amp;#34;Prompt for a receipt file, calculate its hash and move the file to
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">alex/ledger-receipt-folder, renaming it to its hash. Inserts the new file name
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">as a comment to the transaction.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">interactive&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">let*&lt;/span> ((&lt;span style="color:#033">fname&lt;/span> (&lt;span style="color:#033">read-file-name&lt;/span> &lt;span style="color:#c30">&amp;#34;Receipt File Name:&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">fhash&lt;/span> (&lt;span style="color:#366">with-temp-buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">insert-file-contents&lt;/span> &lt;span style="color:#033">fname&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">secure-hash&lt;/span> &lt;span style="color:#fc3">&amp;#39;sha1&lt;/span> (&lt;span style="color:#c0f">current-buffer&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">xdate&lt;/span> (&lt;span style="color:#033">alex/ledger-get-xact-date&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">newpath&lt;/span> (&lt;span style="color:#033">alex/ledger-construct-receipt-path&lt;/span> &lt;span style="color:#033">xdate&lt;/span> &lt;span style="color:#033">fhash&lt;/span> (&lt;span style="color:#033">file-name-extension&lt;/span> &lt;span style="color:#033">fname&lt;/span> &lt;span style="color:#360">t&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">mkdir&lt;/span> (&lt;span style="color:#c0f">file-name-directory&lt;/span> &lt;span style="color:#033">newpath&lt;/span>) &lt;span style="color:#360">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">copy-file&lt;/span> &lt;span style="color:#033">fname&lt;/span> &lt;span style="color:#033">newpath&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#366">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">ledger-navigate-beginning-of-xact&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">end-of-line&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#c0f">insert&lt;/span> &lt;span style="color:#c30">&amp;#34;\n; Receipt: &amp;#34;&lt;/span> &lt;span style="color:#033">fhash&lt;/span> (&lt;span style="color:#033">file-name-extension&lt;/span> &lt;span style="color:#033">fname&lt;/span> &lt;span style="color:#360">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">indent-line-to&lt;/span> &lt;span style="color:#033">ledger-post-account-alignment-column&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">defun&lt;/span> &lt;span style="color:#033">alex/ledger-open-attached-receipt&lt;/span> ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30">&amp;#34;Open the receipt file specified by the Receipt tag in a new buffer.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">interactive&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#366">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">ledger-navigate-beginning-of-xact&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#366">let*&lt;/span> ((&lt;span style="color:#033">xact-date&lt;/span> (&lt;span style="color:#033">alex/ledger-get-xact-date&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">xact-end&lt;/span> (&lt;span style="color:#366">save-excursion&lt;/span> (&lt;span style="color:#033">ledger-navigate-end-of-xact&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">receipt-pos&lt;/span> (&lt;span style="color:#c0f">re-search-forward&lt;/span> &lt;span style="color:#c30">&amp;#34;; Receipt: \\(.*\\)&amp;#34;&lt;/span> &lt;span style="color:#033">xact-end&lt;/span> &lt;span style="color:#360">t&lt;/span> &lt;span style="color:#360">nil&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">receipt-hash-fname&lt;/span> (&lt;span style="color:#033">match-string&lt;/span> &lt;span style="color:#f60">1&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#366">when&lt;/span> (&lt;span style="color:#033">not&lt;/span> &lt;span style="color:#033">receipt-pos&lt;/span>) (&lt;span style="color:#c00;font-weight:bold">error&lt;/span> &lt;span style="color:#c30">&amp;#34;No receipt found for current transaction&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">find-file&lt;/span> (&lt;span style="color:#033">alex/ledger-construct-receipt-path&lt;/span> &lt;span style="color:#033">xact-date&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">file-name-base&lt;/span> &lt;span style="color:#033">receipt-hash-fname&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#033">file-name-extension&lt;/span> &lt;span style="color:#033">receipt-hash-fname&lt;/span> &lt;span style="color:#360">t&lt;/span>))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With evil-leader, I&amp;rsquo;ve bound the attach and open functions to &lt;code>SPC-i&lt;/code> and
&lt;code>SPC-I&lt;/code> respectively. Combined with git, I&amp;rsquo;ve found these functions work nicely
for sorting scans of receipts.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>With the default receipt folder, files get copied to &lt;code>~/finance/receipts/$YEAR/$MONTH&lt;/code>.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Using Ledger With an Encrypted Journal</title><link>https://alexj.org/10/encrypted-ledger-journals/</link><pubDate>Thu, 15 Oct 2015 17:00:00 -0600</pubDate><guid>https://alexj.org/10/encrypted-ledger-journals/</guid><description>&lt;p>I&amp;rsquo;m a pretty big fan of &lt;a href="http://ledger-cli.org">Ledger&lt;/a>, a command line
accounting system based on the double entry bookkeeping system. One of
its strengths lies in the fact that the journal file that contains
your transactions is a plain text file. This makes it super easy to
sync the journal using your favourite file syncing service. Of course,
before putting the journal file on a remote server, you&amp;rsquo;ll probably
want to encrypt it. Ledger program doesn&amp;rsquo;t support encrypted journal
files but, using &lt;a href="https://gnupg.org">GPG&lt;/a> and a shell alias, you can get
the vast majority of Ledger&amp;rsquo;s functionality to work with an encrypted
journal.&lt;/p>
&lt;p>To read the encrypted journal, I use a simple shell alias to pipe the
output of &lt;code>gpg&lt;/code> into &lt;code>ledger&lt;/code>, telling it to read from &lt;code>stdin&lt;/code>. For
the fish shell, the alias I use is:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#366">alias&lt;/span> eledger &lt;span style="color:#c30">&amp;#34;gpg --batch -d -q &lt;/span>&lt;span style="color:#033">$LEDGER_FILE&lt;/span>&lt;span style="color:#c30"> | ledger -f - &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Adjust this for your shell of choice. Here I&amp;rsquo;ve assumed you&amp;rsquo;ve defined
the &lt;code>$LEDGER_FILE&lt;/code> env variable, which Ledger uses by default. I&amp;rsquo;m
also running &lt;code>gpg&lt;/code> in batch mode, which suppresses most of its
output. Remove the &lt;code>--batch&lt;/code> flag if you&amp;rsquo;d rather see this output (it
won&amp;rsquo;t get piped into Ledger).&lt;/p>
&lt;p>Use this alias like you&amp;rsquo;d use the &lt;code>ledger&lt;/code> command. Any commands or
arguments get added to the end of the alias and passed on to
ledger. The majority of Ledger&amp;rsquo;s commands will work using this
method. Only commands that rely on the journal&amp;rsquo;s path will fail.&lt;/p>
&lt;h2 id="editing">Editing&lt;/h2>
&lt;p>If you edit your Ledger journal in Emacs then good news, Emacs will
decrypt the journal file automatically and encrypt it when you
save. Unfortunately, if you use &lt;code>ledger-mode&lt;/code>, not all of the built in
commands will function correctly. In particular, reports won&amp;rsquo;t work
since these are generated by running the &lt;code>ledger&lt;/code> command. This is
&lt;em>probably&lt;/em> easy to fix but I haven&amp;rsquo;t tried yet.&lt;/p></description></item><item><title>Spotijack 0.6 Released</title><link>https://alexj.org/07/spotijack-0.6-released/</link><pubDate>Fri, 24 Jul 2015 23:00:00 +0000</pubDate><guid>https://alexj.org/07/spotijack-0.6-released/</guid><description>&lt;p>After sitting on the project for a few months, I&amp;rsquo;ve just released the
next version of Spotijack on &lt;a href="https://github.com/alexjohnj/spotijack/releases">GitHub&lt;/a>, as well as the
application&amp;rsquo;s &lt;a href="https://github.com/alexjohnj/spotijack">source code&lt;/a>. This is the first public
version of Spotijack because I&amp;rsquo;ve spent a while contemplating whether
the project is OK to release.&lt;/p>
&lt;p>Spotijack is a utility for the Mac that automates the process of
recording music playing in Spotify with Audio Hijack Pro. Whenever the
song changes in Spotify, Spotijack tells Audio Hijack Pro to split the
recording and update the new recording&amp;rsquo;s metadata. Obviously the
program enables piracy which I do not support, and this is why I&amp;rsquo;ve
sat on it for so long.&lt;/p>
&lt;p>I wrote the first version of Spotijack back in December of 2011. At
this point it was a simple AppleScript with no user interface and a
lot of bugs. After that I didn&amp;rsquo;t touch the project for another two
years, largely because I didn&amp;rsquo;t use it. In 2013, looking for a new
side project, I started working on Spotijack again. I ported the
application to Cocoa-AppleScript (a horrible experience) and gave it a
proper user interface. In 2014, I then rewrote the application in
Objective-C using the ScriptingBridge framework (a mostly non-horrible
experience). Ditching AppleScript made the project much more fun to
work on. As a result, I started adding new features and fixing bugs
just for fun. I started to treat it like a proper project and wanted
to make the code public.&lt;/p>
&lt;p>I&amp;rsquo;m aware that the application&amp;rsquo;s only real use is for music piracy
but, it&amp;rsquo;s a fairly inefficient way of pirating music. I don&amp;rsquo;t think
anybody will actually use Spotijack because there are far more
efficient ways to get music. So, I don&amp;rsquo;t think I&amp;rsquo;m doing much harm by
releasing Spotijack and treating it as a little side project.&lt;/p></description></item><item><title>Always Switch Sockets off Before Plugging in</title><link>https://alexj.org/01/always-switch-sockets-off-before-plugging-in/</link><pubDate>Tue, 20 Jan 2015 09:00:00 +0000</pubDate><guid>https://alexj.org/01/always-switch-sockets-off-before-plugging-in/</guid><description>&lt;p>Oh, and make sure your stuff is on a surge protector.&lt;/p>
&lt;p>Normally I&amp;rsquo;m a stickler when it comes to electrical safety, probably because my
Granddad was an electrician and taught me about it. The other night though, in
a lapse of consciousness, I plugged my laptop&amp;rsquo;s charger into a power strip that
was still switched on at the mains. Or, should I say, started to plug in the
charger because as the plug went in, the air between it and the power strip
ignited in a decent explosion.&lt;/p>
&lt;p>The boom was loud enough to leave a ringing in my ears and it sent out a
perceptible air wave from the socket too. Fortunately the power strip doubled
as a surge protector and it did its job in protecting the desktop computer and
monitor that were also plugged in at the time. The damage to the laptop
charger&amp;rsquo;s plug was pretty interesting though. Aside from being charred, the
neutral pin has deformed slightly as has the live pin, although to a lesser
extent. The ground pin looks perfectly fine.&lt;/p>
&lt;!--&lt;div class="full-width-image-wrap">
&lt;img src="https://alexj.org/images/posts/always-switch-sockets-off-before-plugging-in/plug-960px.jpeg" alt="The damaged plug"/>
&lt;/div>-->
&lt;div class="full-width-image-wrap">
&lt;picture>
&lt;source srcset="https://alexj.org/images/posts/always-switch-sockets-off-before-plugging-in/plug-960px.webp" type="image/webp">
&lt;source srcset="https://alexj.org/images/posts/always-switch-sockets-off-before-plugging-in/plug-960px.jpeg" type="image/jpeg">
&lt;img src="https://alexj.org/images/posts/always-switch-sockets-off-before-plugging-in/plug-960px.jpeg"/>
&lt;/picture>
&lt;/div>
&lt;p>Amazingly, the laptop&amp;rsquo;s power brick still works but I haven&amp;rsquo;t tested&amp;mdash;and
don&amp;rsquo;t want to test&amp;mdash;the actual plug. Thanks to the surge protector, the only
costs were for a new fuse and a new power cable but I&amp;rsquo;d hate to think what
would&amp;rsquo;ve happened without the surge protector. Lesson learnt though, always
switch sockets off before plugging in.&lt;/p></description></item><item><title>julius 0.2.0 Released</title><link>https://alexj.org/09/julius-0.2.0-released/</link><pubDate>Tue, 09 Sep 2014 14:02:51 +0100</pubDate><guid>https://alexj.org/09/julius-0.2.0-released/</guid><description>&lt;p>Besides the &lt;a href="https://alexj.org/projects/julius/">project page&lt;/a>, I haven&amp;rsquo;t written about julius on this blog before so here&amp;rsquo;s a quick overview. julius is a stupidly simple command line tool for encrypting and decrypting text using the Caesar Cipher. It&amp;rsquo;s designed to play nicely with Unix redirections so you can use it to easily encrypt/decrypt the output of commands.&lt;/p>
&lt;p>Everyone knows that the Caesar cipher is unbelievably weak which is why nobody in their right mind uses it for anything serious. Version 0.2.0 of julius highlights this weakness by adding the &lt;code>brute&lt;/code> subcommand. Using this command, julius will try every possible key for an encrypted message and output the plaintext. Because there&amp;rsquo;s only 25 possible keys, this takes next to no time to run even on very modest hardware.&lt;/p>
&lt;p>As an example, here&amp;rsquo;s some benchmarks performed on a Raspberry Pi Model B+ running ArchLinux:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">Text to Brute-force&lt;/th>
&lt;th style="text-align:center">Text Size (Bytes)&lt;/th>
&lt;th style="text-align:center">Time Taken (ms)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">&amp;ldquo;Hello, world!&amp;rdquo;&lt;/td>
&lt;td style="text-align:center">14&lt;/td>
&lt;td style="text-align:center">4.18&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;a href="https://github.com/alexjohnj/julius/blob/master/julius.go">julius sourcecode&lt;/a>&lt;/td>
&lt;td style="text-align:center">6007&lt;/td>
&lt;td style="text-align:center">56.42&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;a href="http://www.gutenberg.org/cache/epub/11/pg11.txt">Alice in Wonderland&lt;/a>&lt;/td>
&lt;td style="text-align:center">167518&lt;/td>
&lt;td style="text-align:center">1080.00&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>On more powerful hardware (like, say, a MacBook Air), it takes julius no time at all to brute force a message.&lt;/p>
&lt;p>You can download 0.2.0 today from the &lt;a href="https://github.com/alexjohnj/julius/releases/tag/v0.2.0">GitHub releases&lt;/a> page. There&amp;rsquo;s builds available for 64 bit versions of OS X, Linux and Windows as well as an ARM build for Linux.&lt;/p></description></item><item><title>New Design, New Tools, New Everything</title><link>https://alexj.org/09/new-design-new-tools-new-everything/</link><pubDate>Mon, 01 Sep 2014 21:43:00 +0100</pubDate><guid>https://alexj.org/09/new-design-new-tools-new-everything/</guid><description>&lt;p>I&amp;rsquo;ve spent the past week working on a new design for this site which &lt;em>should&lt;/em> be live right now. The site wasn&amp;rsquo;t in desperate need for a redesign to begin with but I wanted to try out some new tools and figured a redesign was the best way to do it.&lt;/p>
&lt;p>The new design is &amp;ldquo;inspired&amp;rdquo; by Google&amp;rsquo;s &lt;a href="http://www.google.com/design/">Material Design UI&lt;/a>. I&amp;rsquo;ve sprinkled plenty of shadows and animations throughout the site to give it a sense of depth and fluidity. If you get chance, check out one of the &lt;a href="https://alexj.org/projects/">projects&lt;/a> with screenshots. The screenshot gallery is built with vanilla JavaScript and all the animations are done with CSS 2D transforms so they run at 60FPS even on low end devices.&lt;/p>
&lt;h2 id="hugo">Hugo&lt;/h2>
&lt;p>While the new design is interesting and all, the really interesting things with this redesign are the new tools being used. First up, the site is no longer built with &lt;a href="jekyllrb.com">Jekyll&lt;/a> instead using &lt;a href="http://hugo.spf13.com">Hugo&lt;/a>. Hugo is a relatively new static site generator that is written in Go&amp;mdash;one of my favourite programming languages. The biggest advantage Hugo has over Jekyll is its speed. It&amp;rsquo;s crazy fast compared to Jekyll and I &lt;em>love&lt;/em> this. The site builds nearly instantly which is great for development when combined with &lt;a href="http://livereload.com/">LiveReload&lt;/a>, which, may I add, comes built into Hugo.&lt;/p>
&lt;p>Hugo also offers a more flexible way of organising content than Jekyll. Jekyll pretty much assumes you&amp;rsquo;re working with content for a blog while Hugo assumes that the way you lay out your source content is how you want your site to be. This is awesome and really makes me want to move &lt;a href="https://alexj.org/projects/geographyas/">Geography AS Notes&lt;/a> over to Hugo so I can stop doing some weird organisation techniques with the content.&lt;/p>
&lt;p>Unfortunately, Hugo is still young and is missing a few features that Jekyll has. This hasn&amp;rsquo;t been much of a problem with this site but it is stopping me from moving Geography AS Notes over to it. One very simple feature that is missing is the ability to get the next/previous piece of content &lt;em>of a certain type&lt;/em>. Hugo currently provides the &lt;code>{{ .Next }}&lt;/code> and &lt;code>{{ .Prev }}&lt;/code> template variables for the next/previous piece of content but they ignore the type of the content (for example mixing blog posts with project pages) and there&amp;rsquo;s no way to specify the sorting that determines what the next/previous content is.&lt;/p>
&lt;p>Other issues I ran into include a lack of plugin support (Hugo has &amp;ldquo;shortcodes&amp;rdquo; which are similar to Jekyll&amp;rsquo;s custom liquid filters but can&amp;rsquo;t run external commands), a lack of an asset pipeline and a pretty bare bones &lt;code>{{ .Summary }}&lt;/code> variable for generating post summaries. The generated summaries are processed by Markdown but the resulting HTML is stripped leaving a blob of plaintext.&lt;/p>
&lt;p>Despite these shortcomings, I still love Hugo. It&amp;rsquo;s fast, it&amp;rsquo;s stable and it has, in my opinion, a superior way of organising content. I&amp;rsquo;ve been watching the project on GitHub for the past couple of weeks and it&amp;rsquo;s very active so I have a lot of hope for the future of this project. I&amp;rsquo;ll definitely be using Hugo for new projects going forwards.&lt;/p>
&lt;h2 id="gulpjs">Gulp.js&lt;/h2>
&lt;p>Another new tool I got to play around with was &lt;a href="http://gulpjs.com/">gulp.js&lt;/a>. Gulp is a task runner written in JavaScript that proved to be super useful in working around Hugo&amp;rsquo;s lack of an asset pipeline. Initially I didn&amp;rsquo;t want to use Gulp and instead wanted to use Make as a task runner, my main reason being the Node.js dependency. Ten minutes of trying to figure out Make&amp;rsquo;s syntax though and I gave Gulp a go.&lt;/p>
&lt;p>I&amp;rsquo;m pretty happy with the result. Despite being (almost) illiterate when it comes to JavaScipt&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>, writing tasks in a Gulpfile wasn&amp;rsquo;t too hard. The biggest challenge was wrapping my head around the concurrency system Gulp uses. By default tasks are run concurrently so it&amp;rsquo;s important to specify task dependencies to make sure things run in the right order. After rewriting my Gulpfile a few times the concurrency issues were fixed and things seemed to just work.&lt;/p>
&lt;p>Gulp and Hugo seem to work quite well together. Using Gulp I can recompile CoffeeScript and Sass files when they change. This, in turn, makes Hugo regenerate the site (did I mention it does this quickly?) and then the site automatically reloads in the browser. Brilliant.&lt;/p>
&lt;h2 id="autoprefixer">Autoprefixer&lt;/h2>
&lt;p>&lt;a href="https://github.com/postcss/autoprefixer">Autoprefixer&lt;/a> is a useful little Node.js utility that automatically adds vendor prefixes to experimental CSS properties. It works nicely with gulp.js and helped make my Sass files cleaner. It also made development a little faster since I didn&amp;rsquo;t have to spend time looking up which properties require vendor prefixes and which don&amp;rsquo;t. Autoprefixer makes life easier, there&amp;rsquo;s not much else to say about it.&lt;/p>
&lt;h2 id="open-source">Open Source&lt;/h2>
&lt;p>Because this site is built using Hugo and because Hugo is still quite a young project, I&amp;rsquo;ve decided to put the source for the site &lt;a href="https://github.com/alexjohnj/alexjohnj">up on GitHub&lt;/a>. Previously I kept it in a private repository but I think it&amp;rsquo;s for the greater good to make the source publicly available as a potential learning resource for people new to Hugo. Just a small point, the repository was private in the past and I may have been a bit neglectful when it came to commit messages. Don&amp;rsquo;t judge.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>I can read and write JavaScript it&amp;rsquo;s just I don&amp;rsquo;t do it often enough to remember the syntax and when I do write it, I prefer to use CoffeeScript.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Restoring the Network Manager Applet After Upgrading to Lubuntu 14.04</title><link>https://alexj.org/04/restoring-the-network-manager-applet-after-upgrading-lubuntu/</link><pubDate>Sat, 19 Apr 2014 22:30:00 +0000</pubDate><guid>https://alexj.org/04/restoring-the-network-manager-applet-after-upgrading-lubuntu/</guid><description>&lt;p>It seems there&amp;rsquo;s a bug when upgrading Lubuntu to version 14.04 that causes the network manager applet to disappear from the system tray. I&amp;rsquo;ve experienced the problem on two separate installations so I&amp;rsquo;m going to guess this is a widespread problem.&lt;/p>
&lt;p>The fix is easy. Just open a terminal session and run this command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#366">echo&lt;/span> &lt;span style="color:#c30">&amp;#34;nm-applet&amp;#34;&lt;/span> &amp;gt;&amp;gt; ~/.config/lxsession/Lubuntu/autostart
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>All the command does is add &lt;code>nm-applet&lt;/code> to your LXDE autostart file. If you logout and log back in, the network manager applet should be running.&lt;/p></description></item><item><title>Some .sexy New TLDs</title><link>https://alexj.org/03/some-sexy-new-tlds/</link><pubDate>Wed, 12 Mar 2014 21:45:00 +0000</pubDate><guid>https://alexj.org/03/some-sexy-new-tlds/</guid><description>&lt;p>Today I became the proud owner of &lt;a href="http://alexj.sexy">alexj.sexy&lt;/a> following in the footsteps of people like Myke Hurley of &lt;a href="http://myke.sexy">myke.sexy&lt;/a>. This is possibly the best $25 I&amp;rsquo;ve ever spent.&lt;/p>
&lt;p>Joking aside, I couldn&amp;rsquo;t help but notice how stupid some of the new TLDs are. A sliver do allow for some creative and memorable domains but for the most part they&amp;rsquo;re pretty useless. Take, for example, the &lt;code>.guru&lt;/code> TLD. How many times in the past year have you referred to someone as a guru? I&amp;rsquo;d guess it&amp;rsquo;s a pretty small number.&lt;/p>
&lt;p>I&amp;rsquo;m probably getting cranky over nothing here. Nowadays people don&amp;rsquo;t access a website via its domain name anyway so the new TLDs will likely be of little importance. Still, I find it funny to think that a board of people at ICANN looked at TLDs like &lt;code>.sexy&lt;/code> or &lt;code>.cool&lt;/code> and thought they were good.&lt;/p></description></item><item><title>Nginx—Surprisingly Easy</title><link>https://alexj.org/02/nginx-surprisingly-easy/</link><pubDate>Mon, 03 Feb 2014 00:15:00 +0000</pubDate><guid>https://alexj.org/02/nginx-surprisingly-easy/</guid><description>&lt;p>Out of curiosity I spent my Sunday afternoon moving this blog from Amazon S3 to a &lt;a href="https://www.digitalocean.com/?refcode=755d29c48c8c">DigitalOcean&lt;/a> VPS (spoiler, that&amp;rsquo;s a referral link). I didn&amp;rsquo;t have a solid reason to do this&amp;mdash;it&amp;rsquo;s both more expensive and more effort&amp;mdash;aside from to learn. I&amp;rsquo;ve never set up a web server from scratch before let alone owned a VPS. I kind of felt I needed one so I could earn some geek cred.&lt;/p>
&lt;p>I&amp;rsquo;m running Ubuntu 12.04 LTS on the VPS since Ubuntu&amp;rsquo;s the Linux distribution I have the most experience with&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Nginx is acting as the web server and seems to be fast. It&amp;rsquo;s not like I have anything to compare it with though. The VPS is DigitalOcean&amp;rsquo;s cheapest droplet&amp;mdash;512 MB of RAM and a 20 GB SSD.&lt;/p>
&lt;p>Given my lack of experience with web servers, I was expecting Nginx to be a pain to set up. Surprisingly, it wasn&amp;rsquo;t. Within 30 minutes I had a test page up and within an hour I was serving a mirror of this site from a subdomain of this site. Granted, this is a purely static site, so I didn&amp;rsquo;t have to set up the full LEMP stack.&lt;/p>
&lt;p>I won&amp;rsquo;t go into detail about how I set up the VPS but I&amp;rsquo;ll share a few resources I found. DigitalOcean&amp;rsquo;s support website has some &lt;a href="https://www.digitalocean.com/community">good documentation&lt;/a> on setting up a Linux VPS. There&amp;rsquo;s also a tutorial on &lt;a href="https://www.digitalocean.com/community/articles/how-to-install-linux-nginx-mysql-php-lemp-stack-on-ubuntu-12-04">setting up a LEMP stack&lt;/a> but I&amp;rsquo;m not a fan of it. The tutorial uses the outdated nginx package provided in the Ubuntu repositories. It needs updating with info on getting the latest version of Nginx.&lt;/p>
&lt;p>A very useful resource I found for learning about Nginx was &lt;a href="http://blog.martinfjordvald.com">Martin Fjordvald&amp;rsquo;s blog&lt;/a>. He&amp;rsquo;s written several useful and interesting articles especially &lt;a href="http://blog.martinfjordvald.com/2010/07/nginx-primer/">&amp;ldquo;Nginx Configuration Primer&amp;rdquo;&lt;/a>. He&amp;rsquo;s also &lt;a href="http://blog.martinfjordvald.com/2013/05/my-new-nginx-book-instant-nginx-starter/">written a short book&lt;/a> that goes into a bit more detail. It&amp;rsquo;s a quick read and definitely worth the ~£6.00 if you&amp;rsquo;re new to Nginx.&lt;/p>
&lt;h2 id="advantages">Advantages&lt;/h2>
&lt;p>More than anything this was a learning exercise but there were a few benefits.&lt;/p>
&lt;p>The biggest advantage is native support for root domain names. AWS doesn&amp;rsquo;t support them unless you use their &lt;a href="http://aws.amazon.com/route53/">Route 53&lt;/a> service. As a result, I had to use a 3^rd party service to redirect the root domain to the www &lt;code>cname&lt;/code> record. This added an extra 200&amp;ndash;300 ms to the DNS look up if somebody used the naked domain. It was a noticeable difference in performance. Now, the homepage loads in ~300 ms regardless of which domain you use.&lt;/p>
&lt;p>Another advantage is the extra control I get. With S3, supporting features like gzip compression was an all or nothing choice. If a browser didn&amp;rsquo;t support gzip&amp;mdash;an admittedly rare occurrence&amp;mdash;it&amp;rsquo;d receive nothing but garbled data. With Nginx though I can enable the &lt;code>gzip_static&lt;/code> directive and support these browsers.&lt;/p>
&lt;p>Going forwards I&amp;rsquo;m probably going to serve &lt;a href="http://geographyas.info">Geography AS Notes&lt;/a> from the same DigitalOcean droplet since GitHub pages offers even less control than Amazon S3. I don&amp;rsquo;t see any reason why I can&amp;rsquo;t serve it from the same droplet since this website is such a low volume site, it will have a negligible performance impact. If need be I can get a better DigitalOcean droplet but since Geography AS Notes is strictly not for profit and advertisement free I&amp;rsquo;d have to find some way to subsidise the cost.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>I&amp;rsquo;ve been running and maintaining Ubuntu on my Grandmother&amp;rsquo;s computer since 8.04. She uses my old MacBook now but had no problems with Ubuntu when she used it.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Redesigning Geography AS Notes</title><link>https://alexj.org/01/redesigning-geography-as-notes/</link><pubDate>Wed, 08 Jan 2014 21:45:00 +0000</pubDate><guid>https://alexj.org/01/redesigning-geography-as-notes/</guid><description>&lt;p>Just under a month ago, I &lt;a href="https://alexj.org/12/project-updates/">wrote about&lt;/a> which projects I&amp;rsquo;d be
focusing my time on in the coming year. The main takeaway from the post was
that &lt;a href="https://alexj.org/projects/geographyas/">Geography AS Notes&lt;/a> was going to get some love. Well,
here&amp;rsquo;s my first packet of love&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>, a redesign.&lt;/p>
&lt;p>It&amp;rsquo;s been nearly two years since I last refreshed &lt;em>Geography AS Notes&amp;rsquo;&lt;/em> design
and in that time its grown boring and stale. The website&amp;rsquo;s design has never
been eye catching&amp;mdash;one frequent piece of feedback I get is that the site is
boring to look at&amp;mdash;but for the past two years I&amp;rsquo;ve taken the function over
form approach. This needed to change.&lt;/p>
&lt;p>If a site is boring to look at, the reader is more likely to perceive the
content as boring which isn&amp;rsquo;t good if you want them to study using it.
Furthermore, I&amp;rsquo;ve realised that Helvetica (or Arial on Windows) ins&amp;rsquo;t a good
body font and my choice of font was hurting the legibility of the site, a
problem that grew worse with the length of articles.&lt;/p>
&lt;p>For the redesign, I wanted to make something that not only looked half decent
but was comfortable to read on a multitude of devices. In addition, I wanted to
simplify the site&amp;rsquo;s markup and make use of the new HTML5 semantic elements to
improve accessibility, something which is very important when you&amp;rsquo;re dealing
with people&amp;rsquo;s education. Finally, I wanted to make it easier for people to find
content on the site by implementing site search. These were the four main
objectives when redesigning the website.&lt;/p>
&lt;p>The first objective I worked on was improving the HTML semantics. This didn&amp;rsquo;t
take too long and drastically simplified the site&amp;rsquo;s markup. I found a fantastic
&lt;a href="http://mystrd.at/modern-clean-css-sticky-footer/">new method&lt;/a> for writing CSS sticky footers. By using it I
was able to eliminate lots of un-semantic wrapper styles and layout my pages
with just the &lt;code>&amp;lt;header&amp;gt;&lt;/code>, &lt;code>&amp;lt;article&amp;gt;&lt;/code> and &lt;code>&amp;lt;footer&amp;gt;&lt;/code> elements. The new CSS
sticky footer doesn&amp;rsquo;t work in IE &amp;lt;= 7 but &lt;em>nobody&lt;/em> uses that anymore.&lt;/p>
&lt;p>Speaking of IE, I raised my minimum target from IE 7 to 8&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>. I was surprised
by how painless it was developing for IE 8. All I needed to do was drop in
&lt;a href="https://code.google.com/p/html5shiv/">html5shiv.js&lt;/a> and there were no further problems. I&amp;rsquo;ve
actually had more problems with IE 9/10/11 and their funky way of resizing
&lt;code>&amp;lt;img&amp;gt;&lt;/code> elements with SVG content (which I still haven&amp;rsquo;t fixed).&lt;/p>
&lt;p>With the site&amp;rsquo;s structure modernised, I started working on how the site looks.
I opted for a dark colour scheme with splashes of orange. I think it looks good
and it&amp;rsquo;s much better than the old design. Article headers receive more emphasis
than they used to while the site&amp;rsquo;s header is less dominant to focus on the
content. The new design is more fluid than the old one, relying less on media
queries and making extensive use of % dimensions. This kept the SCSS clean.&lt;/p>
&lt;p>I made some significant changes to the homepage. Previously, the homepage used
a set of expanding divs that contained the site&amp;rsquo;s content. It looked sort of
cool but didn&amp;rsquo;t work well on small screens and content was often truncated
since I couldn&amp;rsquo;t use &lt;code>height:auto&lt;/code> on the divs and have CSS3 transitions work.
In the new design, a (pseudo) segmented control is used to control the visible
topic. This works better on mobiles and doesn&amp;rsquo;t have any problems with
truncated content as I&amp;rsquo;m only toggling the div&amp;rsquo;s (well, &lt;code>&amp;lt;section&amp;gt;&lt;/code> now)
&lt;code>visibility&lt;/code> and &lt;code>display&lt;/code> properties.&lt;/p>
&lt;p>The next objective to strike off was legibility. I played around with a few
font combinations from Google Fonts and ended up combining
&lt;a href="https://www.google.com/fonts/specimen/Arvo">Arvo&lt;/a> and &lt;a href="http://www.google.com/fonts/specimen/Raleway">Raleway&lt;/a>. Arvo&amp;mdash;a slab-serif
font&amp;mdash;is used for the site&amp;rsquo;s body while Raleway&amp;mdash;a sans-serif font&amp;mdash;is used
for headers. I like the combination, they look nice together and, most
importantly, they&amp;rsquo;re highly readable.&lt;/p>
&lt;p>The final objective was search. Most people arrive at &lt;em>Geography AS Notes&lt;/em>
using Google but once they&amp;rsquo;re on the site, there&amp;rsquo;s no further search
functionality. Since the site&amp;rsquo;s Jekyll powered, implementing search has a few
challenges. Mainly, it needs to be handled client-side. I &lt;a href="https://github.com/alexjohnj/geographyas/tree/super-experimental-search">toyed
with&lt;/a> writing a search engine back in January last year. It
worked&amp;mdash;surprisingly efficiently may I add&amp;mdash;but it needed &lt;em>a lot&lt;/em> of work and
I never got around to finishing it.&lt;/p>
&lt;p>I tried again to implement client-side site search, this time using
&lt;a href="http://lunrjs.com">lunr.js&lt;/a> so I didn&amp;rsquo;t have to handle the actual searching. Lunr.js is
simple to set up but, in my limited testing, it proved too slow at indexing
full text content for every page. It was unreasonable of me to expect it to
handle such a large index but it was worth a shot.&lt;/p>
&lt;p>Ultimately, I offloaded site search to a third party service. I tried
&lt;a href="http://tapirgo.com">Tapir&lt;/a> but the service seems to have shut down&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. I ended up using
DuckDuckGo which offers some limited customisation of the search results. It&amp;rsquo;s
a compromise though&amp;mdash;I&amp;rsquo;d much rather have results on my website.&lt;/p>
&lt;p>The final design ended up looking like this:&lt;/p>
&lt;picture>
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-new.webp" type="image/webp">
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-new.png" type="image/png">
&lt;img class="shadow" src="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-new.png" alt="Screenshot of the new design">
&lt;/picture>
&lt;picture>
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-new-article.webp" type="image/webp">
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-new-article.png" type="image/png">
&lt;img class="shadow" src="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-new-article.png" alt="Screenshot of an article with the new design.">
&lt;/picture>
&lt;p>For reference, the old design:&lt;/p>
&lt;picture>
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-old.webp" type="image/webp">
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-old.png" type="image/png">
&lt;img class="shadow" src="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-old.png" alt="Screenshot of the old design">
&lt;/picture>
&lt;picture>
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-old-article.webp" type="image/webp">
&lt;source srcset="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-old-article.png" type="image/png">
&lt;img class="shadow" src="https://alexj.org/images/posts/redesigning-geography-as-notes/geography-as-old-article.png" alt="Screenshot of an article with the old design.">
&lt;/picture>
&lt;p>I made a few smaller changes while working on the redesign. All the topics now
have their own page which can be linked to. In addition, I&amp;rsquo;ve made it more
obvious that people can edit existing articles and create new ones. All
articles have an edit link in their footer and I&amp;rsquo;ve &lt;a href="http://geographyas.info/pages/contributing/">written
about&lt;/a> other ways people can contribute.&lt;/p>
&lt;p>This redesign is just the first in what I hope is a string of updates to
&lt;em>Geography AS Notes&lt;/em> that will be coming throughout the year. If you have any
suggestions for the website, let me know via the &lt;a href="https://github.com/alexjohnj/geographyas/">GitHub
repository&lt;/a>.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>It&amp;rsquo;s quantum love, coming in discrete packets.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>I&amp;rsquo;d like to raise it higher than this but a lot of schools&amp;mdash;where the website&amp;rsquo;s most commonly accessed from&amp;mdash;still use IE 8.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>I never received an email with my private API key and received no response from the API with the public key when I tried to use it.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Project Updates</title><link>https://alexj.org/12/project-updates/</link><pubDate>Wed, 11 Dec 2013 01:22:00 +0000</pubDate><guid>https://alexj.org/12/project-updates/</guid><description>&lt;p>This is a boring updated on the status of several of my projects. If you don&amp;rsquo;t care about them then just give this post a miss. There&amp;rsquo;ll be a new post in a couple of months which may be more interesting.&lt;/p>
&lt;p>My situation&amp;rsquo;s changed a lot in the past couple of months. I&amp;rsquo;m now at university, living away from my parents, studying what I want to study with some great people. It&amp;rsquo;s pretty awesome. It&amp;rsquo;s also a lot of work which is why I&amp;rsquo;m changing what projects I&amp;rsquo;m focusing on in my spare time.&lt;/p>
&lt;h2 id="simplecode">SimpleCode&lt;/h2>
&lt;p>&lt;strong>SimpleCode is dead&lt;/strong>. The website&amp;rsquo;s been inactive for some time now&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> and, on December 4&lt;sup>th&lt;/sup> 2013, I let the domain name expire. I&amp;rsquo;ve been waiting to kill this project and I&amp;rsquo;m glad that it&amp;rsquo;s finally gone. Looking back on the programming tutorials I wrote when I was a less experienced programmer I feel sorry for anybody who tried to learn from them. There&amp;rsquo;s so much incorrect or poorly explained information, it&amp;rsquo;s horrible. I feel only a small bit of sadness killing this projects as it was the first website that I actively maintained and the first domain name I ever owned.&lt;/p>
&lt;h2 id="symsteamprojectssymsteam">&lt;a href="https://alexj.org/projects/symsteam/">SymSteam&lt;/a>&lt;/h2>
&lt;p>&lt;strong>SymSteam is on an indefinite hiatus&lt;/strong>. I love SymSteam. It was the first Mac application I worked on where I actually understood most of what I was doing. I didn&amp;rsquo;t need to refer to Stack Overflow&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> every other line of code and I was actively trying to follow best practices rather than doing my own thing. It was also my first useful application. SimpleCalc was useless, there are thousands of calculators far more powerful than it but SymSteam, as far as I can tell, is unique.&lt;/p>
&lt;p>Work on beta 3 of SymSteam started in April 2013 and the most recent commit was in the tail end of June. Since then, the project&amp;rsquo;s stalled. Ever since building a dedicated computer for gaming, I&amp;rsquo;ve failed to find the motivation to work on SymSteam and it&amp;rsquo;s for this reason that I&amp;rsquo;m officially putting it on hold. I&amp;rsquo;m not calling the project dead because one day I may find the motivation to start working on it again but for now, it just isn&amp;rsquo;t there.&lt;/p>
&lt;h2 id="appcastrprojectsappcastr">&lt;a href="https://alexj.org/projects/appcastr/">Appcastr&lt;/a>&lt;/h2>
&lt;p>&lt;strong>It&amp;rsquo;s dead&lt;/strong>. Appcastr&amp;rsquo;s a funny project. I enjoyed making it despite the constant headaches autolayout gave me but for some reason, I completely lost interest in it. It&amp;rsquo;s annoying because I still have ideas for features to add but I don&amp;rsquo;t want to set aside the time to work on them when there&amp;rsquo;s other things I can be doing.&lt;/p>
&lt;p>The project page for Appcastr has, for almost a year, said that it&amp;rsquo;s not dead and that version 0.2 will be released some day. That&amp;rsquo;s not happening. If you want to try out version 0.2, compile it from the 0.2 branch in the &lt;a href="https://github.com/alexjohnj/appcastr">git repository&lt;/a>. Otherwise, you can &lt;a href="https://github.com/downloads/alexjohnj/appcastr/Appcastr-0.1.1.zip">download&lt;/a> a &amp;lsquo;stable&amp;rsquo; and usable build of version 0.1.1.&lt;/p>
&lt;h2 id="geography-as-notesprojectsgeographyas">&lt;a href="https://alexj.org/projects/geographyas/">Geography AS Notes&lt;/a>&lt;/h2>
&lt;p>&lt;strong>It&amp;rsquo;s alive!&lt;/strong>. Work on Geography AS Notes has been all over the place this year. At the start of the year, I rewrote the rivers topic over the space of three months, replacing the old content that was essentially notes with researched prose&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. After the rewrite though, I haven&amp;rsquo;t done much but manage the technical side of the website. In the coming months, I&amp;rsquo;m going to start focusing on the website&amp;rsquo;s content again.&lt;/p>
&lt;p>Currently, I&amp;rsquo;m converting diagrams to the SVG format so that they look good at all resolutions. I&amp;rsquo;m also adding more information to several of the diagrams to make them more useful. After this, there will be some significant content updates to the coasts and population change topics. The latter topic is in desperate need of a rewrite so I&amp;rsquo;ll try to prioritise this. After that, I will look into adding some new content. I&amp;rsquo;d like to write something about volcanoes &amp;amp; plate tectonics so I think that&amp;rsquo;ll probably be what&amp;rsquo;s coming next.&lt;/p>
&lt;h2 id="a-prototype">A Prototype&lt;/h2>
&lt;p>Although my main focus for the coming months is going to be my degree and Geography AS Notes, I recently started prototyping an application that I may or may not develop further. I don&amp;rsquo;t want to reveal too much about it as it&amp;rsquo;s something that I&amp;rsquo;m considering making for profit but let&amp;rsquo;s just say it&amp;rsquo;d be my first proper attempt at iOS &amp;amp; graphics programming&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup> and it&amp;rsquo;d be related to my degree&amp;hellip;&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>I&amp;rsquo;ve had a 4,000 word draft for a post sitting uncommitted in the git repository for nearly 18 months.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>OK, I did ask &lt;a href="http://stackoverflow.com/questions/11811119/cocoa-application-crashing-when-launched-on-login">one question&lt;/a> on SO when I couldn&amp;rsquo;t figure out why SymSteam was crashing when started on login but I ended up solving that my self any way.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>The feedback on the rewrite&amp;rsquo;s been great by the way. Thanks to everyone who&amp;rsquo;s emailed me with comments and suggestions. It&amp;rsquo;s really nice.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>It isn&amp;rsquo;t a game.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Review - Tomb Raider</title><link>https://alexj.org/08/review-tomb-raider/</link><pubDate>Sat, 10 Aug 2013 23:00:00 +0000</pubDate><guid>https://alexj.org/08/review-tomb-raider/</guid><description>&lt;p>The newest entry in the Tomb Raider franchise is a much needed reboot of an ageing series. Published by Square Enix &amp;amp; developed by Crystal Dynamics, the game is a major improvement over past titles in the series with the developers focusing a significant amount of their resources on creating an engaging and enjoyable solo experience.&lt;/p>
&lt;p>The story of the new Tomb Raider is the real meat of the game. It focuses on the early days of Lara Croft as she is thrown between a rock and a hard place and starts to develop her survival skills. Lara, an archaeologist, and her colleagues/friends are investigating the ancient Yamani Kingdom when their ship is shipwrecked on an island that happens to be Yamani. They pretty quickly find out they&amp;rsquo;re not alone and that there are other survivors on the island who aren&amp;rsquo;t very friendly and kidnap them. Lara escapes and begins trying to rescue her friends while doing some archaeology on the side like any good scholar.&lt;/p>
&lt;p>The story is told through a series of cutscenes as well as several of Lara&amp;rsquo;s self narrated diary entries. The cutscenes are interesting to watch and the diary entries are equally interesting to listen to. The voice acting in both is well done with some convincing performances. Overall the story is well told, entertaining and captivating. The development of the characters, more specifically Lara, is very good. By choosing to have parts of the story re-told through Lara&amp;rsquo;s diary entries, it really helps hammer home just how much she is changing as a result of the events and by the end of the game, you&amp;rsquo;ll see a very big difference between the Lara you started with and the Lara you ended with. The other characters don&amp;rsquo;t get as much attention as Lara does but their relationship with Lara is well portrayed and there is a strong sense of emotional attachment between her and some of the characters such as Roth.&lt;/p>
&lt;p>Tomb Raider&amp;rsquo;s gameplay is by no means poor or boring but compared to the fantastic story it feels a little second rate. It relies a little too much on quicktime events &amp;amp; button mashing. After the first two hours of gameplay I&amp;rsquo;d already been through nearly a dozen quicktime or button mashing sequences and there were many places in the game where said sequences felt unnecessary. At the same time, there were some places where the sequences did actually work well.&lt;/p>
&lt;p>There are essentially two components to Tomb Raider&amp;rsquo;s gameplay, platforming and shooting. There is arguably a third in the form of stealth but that really takes a back seat as while there appears to be the option to stealthily take out entire groups of enemies I think that this is just an illusion as when I tried this I often found myself in the midst of a firefight just moments later. Maybe I was just doing it wrong.&lt;/p>
&lt;p>The platforming sections work well. The controls are tight and it&amp;rsquo;s always fairly obvious where you need to go thanks to Lara&amp;rsquo;s “Survival Instincts”. The threat of dying in the platforming sections is next to non existent thanks to some &lt;em>very&lt;/em> lenient platforming targets. When I did die it was generally because I hit the wrong key and made Lara go the wrong way not because a certain jump was too hard to make.&lt;/p>
&lt;p>The shooting sections tend to be duck and cover affairs. You&amp;rsquo;ll spend most of your time crouching behind a crate, popping out every now and then to shoot some enemies. Sometimes the shooting sections can feel a bit boring as you face wave after wave of enemy, especially later on in the game when the enemies start throwing grenades that force you to keep moving from cover to cover, often not giving you chance to actually return fire. At other points though the combat can be fun and exciting. The level where Lara obtains a grenade launcher is particularly memorable for me. The cover system in Tomb Raider works well enough. Lara automatically takes cover behind objects as you approach them assuming there are enemies nearby. I would have preferred a dedicated button to make her stick to cover but there&amp;rsquo;s nothing up with the cover system that has been implemented.&lt;/p>
&lt;p>Shooting sections are occasionally spiced up with the introduction of boss fights. These tend to be heavy gunners dressed in body armour or with shields. They put up a bit more of a fight than the regular enemies but you tend to face them one-on-one which sort of eliminates the threat of them since you can easily dodge their attacks and attack their weak points. Doing so then leads to a joyous quicktime event. Failing said event causes the enemy to get back up but in the time it takes for them to get back up, you can probably hit them in their weak spot again causing them to go back down. It&amp;rsquo;s a bit of a cheap tactic but hey, it works.&lt;/p>
&lt;p>The weapons in the game are OK. You get a bow &amp;amp; arrow which, if used correctly, is possibly the most powerful and certainly the most versatile weapon in the game. You also get a pistol, rifle &amp;amp; shotgun which get upgraded to more advanced models as you progress through the story. You&amp;rsquo;ll probably find yourself using your pistol the most often since ammo is so plentiful for it and it tends to kill enemies in one or two shots. Throughout the game there are weapon modifications to collect and also a sort of in game currency called salvage which allows you to purchase said modifications. The modifications improve the base stats of your weapons and a few add some cool new functions to them like firing incendiary rounds from your shotgun.&lt;/p>
&lt;p>You also gain experience as you kill enemies in the game which allows you to buy perks. The experience rewarded is based on how badass the kill is. Body shots earn the least, followed by headshots and then executions. Executions can only be performed by purchasing the required perk though. Aside from adding executions, perks also make various improvements to Lara such as reducing fall damage or making her more efficient at looting enemies. The perks are all helpful but there isn&amp;rsquo;t much need to ration your experience points. You earn a significant quantity of experience every time you finish a story mission so you&amp;rsquo;ll never really run short and be unable to afford a perk.&lt;/p>
&lt;p>There are many collectibles to be found in this game too ranging from artefacts which have their own L.A. Noir like inspection mode to diary entries from other characters which help flesh out their backstory a little bit. It is actually sort of worth trying to find these collectibles since they add a bit to the story but the placement of them is often illogical. It would seem, for example, that characters that are kidnapped right in front of you and you chase after take the time to leave their diary entries as they are kidnapped.&lt;/p>
&lt;p>In addition to hidden items to find there are also hidden tombs to find. Known as Challenge Tombs, these sections are one of my favourite parts of the game aside from the story. Challenge Tombs lack enemies, offering some slightly more relaxing gameplay. Instead, they have one large central puzzle which you must solve in order to access a reward (generally a weapon modification). Some of the puzzles require a few minutes of thought but solving them is fairly satisfying, generally employing a bit of platforming, timing and interacting with the environment. The puzzles are made a bit easier by the use of Lara&amp;rsquo;s “Survival Instincts” which highlight points of interest in the environment. While it&amp;rsquo;s helpful, “Survival Instincts” sort of spoils the challenge of the Challenge Tombs. To get maximum enjoyment out of the challenge tombs, try and ration your usage of the “Survival Instincts”.&lt;/p>
&lt;p>The graphical design of the game is truly fantastic. The game looks beautiful with the character models being very detailed and believable. Environments are rich and detailed with some great vistas at various points in the game. The graphics aren&amp;rsquo;t just eye candy though, they do a great job of adding to the atmosphere of the game and making the world as a whole more immersive. Another aspect of the game&amp;rsquo;s design which is fantastic is the sound. As I&amp;rsquo;ve already said, the voice acting in this game is terrific and that&amp;rsquo;s not just for the cutscenes. Enemies in the game have their own conversations which are fun to listen to before killing them. It&amp;rsquo;s really cool to hear how enemies go from questioning how some girl can be running circles around them to genuinely fearing her. Then, when things start to go their way again, they fear her less.&lt;/p>
&lt;p>Not only is the voice acting great but the ambient sound is brilliant too. The sound of Lara climbing up a craggy cliff face is incredibly satisfying as is the sound an arrow makes as it lodges itself in an enemy&amp;rsquo;s skull. All the individual grunts and breaths Lara takes sound brilliant and add to the immersion of the game.&lt;/p>
&lt;p>As far as reboots go, Tomb Raider is a shining example of how to do one properly. Thanks to some TLC by the developers, Tomb Raider is a fantastic game with an engaging and worthwhile story mixed in with some beautiful set pieces. Yes the gameplay isn&amp;rsquo;t the most original or exciting thing out there but it&amp;rsquo;s easy to forget when the story is this good. I only wish that the developers had laid off the quicktime events and button mashing because it really did make some sections of the game tedious to play. Still, even though these sequences exist throughout the game, they make up a relatively small amount of the actual game and won&amp;rsquo;t detriment from your overall enjoyment of the game. Definitely play this game.&lt;/p>
&lt;h2 id="a-side-note---the-multiplayer">A Side Note - The Multiplayer&lt;/h2>
&lt;p>It&amp;rsquo;s obvious that Tomb Raider is a game focused on a solo experience but, for better or for worse, the publisher decided that a multiplayer component was also necessary. The multiplayer section of the game was developed by a separate studio and, while it isn&amp;rsquo;t terrible, it&amp;rsquo;s easily forgettable. The multiplayer includes a standard PvP deathmatch mode as well as a scavenger mode where one team must collect supplies while the other tries to stop them and an escort style mode where one team must transport supplies to a specific point on the map.&lt;/p>
&lt;p>The multiplayer might occupy you for two or three hours at best but seriously, go into this game expecting a great single player experience, not a multiplayer one.&lt;/p></description></item><item><title>SymSteam Beta 2.1 Released</title><link>https://alexj.org/03/symsteam-beta-2-1/</link><pubDate>Sun, 31 Mar 2013 12:32:00 +0000</pubDate><guid>https://alexj.org/03/symsteam-beta-2-1/</guid><description>&lt;p>This is a &lt;em>very&lt;/em> small update for SymSteam that fixes two bugs you probably didn&amp;rsquo;t even know existed. The first, a simple spelling mistake in an error message. The second, a cock-up with pointers that meant that if an error occurred while deleting an old symbolic link an empty error message would be displayed. Not particularly helpful.&lt;/p>
&lt;p>Code wise, SymSteam should be easier to translate into other languages because I&amp;rsquo;ve changed all the user interface strings so that they&amp;rsquo;re created using &lt;code>NSLocalizedString()&lt;/code> and are stored in a strings file. If anyone wants to go ahead and translate SymSteam into another language please do.&lt;/p>
&lt;p>You should be prompted to install the update if you already have SymSteam installed. If not, you can view the changelog and download it from SymSteam&amp;rsquo;s &lt;a href="https://alexj.org/projects/symsteam/">project page&lt;/a>. As always, you can find the source code for SymSteam on &lt;a href="https://github.com/alexjohnj/symsteam/">GitHub&lt;/a>.&lt;/p></description></item><item><title>S3 Deployment Script Mk 2</title><link>https://alexj.org/02/s3-deployment-script-mk-2/</link><pubDate>Sun, 17 Feb 2013 16:16:00 +0000</pubDate><guid>https://alexj.org/02/s3-deployment-script-mk-2/</guid><description>&lt;p>Since releasing the first version of the script I use to deploy this blog to S3, I&amp;rsquo;ve iterated on the script a little bit and made a few changes. Most of the changes in version 2 of the script are syntax changes to the script itself that make it a little bit easier to read&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> however there are a couple of functionality changes.&lt;/p>
&lt;p>The biggest change is the introduction of command line flags. Three flags have been added, &lt;code>-b&lt;/code>, &lt;code>-s&lt;/code> and &lt;code>-n&lt;/code>. These tell the script to not minify sass files, not compile sass files and to perform a dry run respectively. The first two flags speak for themselves while the third, the dry run flag, simply means that the site will be generated as usual but it won&amp;rsquo;t be deployed to S3. It&amp;rsquo;s akin to running &lt;code>s3cmd sync --dry-run src/ s3://bucket&lt;/code>.&lt;/p>
&lt;p>Another new ‘feature’ is a countdown that is performed before deploying to S3. After generating the site, the script waits three second before pushing the generated site giving you opportunity to cancel the deployment if you forgot to pass the &lt;code>-n&lt;/code> flag or realise that you&amp;rsquo;re (somehow) deploying to the wrong bucket. I&amp;rsquo;m sure some people aren&amp;rsquo;t going to like this but seriously, what&amp;rsquo;s waiting 3 seconds compared to the time it&amp;rsquo;d take to fix the mess after deploying your testing site to the wrong bucket?&lt;/p>
&lt;p>A couple of the smaller changes to the script relate to Python 3 compatibility. I&amp;rsquo;ve removed the dependency on PyYaml since it isn&amp;rsquo;t compatible with Python 3 and it was a bit of an overkill to get just one line out of the &lt;code>_config.yml&lt;/code> file. In addition, I&amp;rsquo;ve fixed a crash that would occur when gzipping files and the script is run under Python 3.&lt;/p>
&lt;p>The updated script is below and you can also find the script as a &lt;a href="https://gist.github.com/alexjohnj/4559517">gist on GitHub&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">#! /usr/local/bin/python3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0cf;font-weight:bold">gzip&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0cf;font-weight:bold">os&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">from&lt;/span> &lt;span style="color:#0cf;font-weight:bold">subprocess&lt;/span> &lt;span style="color:#069;font-weight:bold">import&lt;/span> PIPE, check_call, CalledProcessError
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">from&lt;/span> &lt;span style="color:#0cf;font-weight:bold">sys&lt;/span> &lt;span style="color:#069;font-weight:bold">import&lt;/span> exit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">from&lt;/span> &lt;span style="color:#0cf;font-weight:bold">time&lt;/span> &lt;span style="color:#069;font-weight:bold">import&lt;/span> sleep
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0cf;font-weight:bold">argparse&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">get_s3_bucket_name&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">with&lt;/span> &lt;span style="color:#366">open&lt;/span>(&lt;span style="color:#c30">&amp;#34;_config.yml&amp;#34;&lt;/span>) &lt;span style="color:#069;font-weight:bold">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> line &lt;span style="color:#000;font-weight:bold">in&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> line&lt;span style="color:#555">.&lt;/span>split(&lt;span style="color:#c30">&amp;#39;:&amp;#39;&lt;/span>)[&lt;span style="color:#f60">0&lt;/span>] &lt;span style="color:#555">==&lt;/span> &lt;span style="color:#c30">&amp;#34;s3bucket&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> line&lt;span style="color:#555">.&lt;/span>strip(&lt;span style="color:#c30">&amp;#34;s3bucket:&amp;#34;&lt;/span>)&lt;span style="color:#555">.&lt;/span>strip()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Error: No bucket was found in the site&amp;#39;s _config.yml file&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">compile_sass&lt;/span>(input_file, output_file, minify&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;sass &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> path_to_sass_file &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#34;:&amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> sass_compile_path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> minify:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> command &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#34; --style compressed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Something went wrong compiling sass files.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">generate_site&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call([&lt;span style="color:#c30">&amp;#34;jekyll&amp;#34;&lt;/span>, &lt;span style="color:#c30">&amp;#34;--no-auto&amp;#34;&lt;/span>], stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong generating the site with Jekyll.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">gzip_files&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> root, dirs, files &lt;span style="color:#000;font-weight:bold">in&lt;/span> os&lt;span style="color:#555">.&lt;/span>walk(&lt;span style="color:#c30">&amp;#34;_site/&amp;#34;&lt;/span>): &lt;span style="color:#09f;font-style:italic"># Traverse the _site/ directory&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> f &lt;span style="color:#000;font-weight:bold">in&lt;/span> files:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>splitext(f)[&lt;span style="color:#f60">1&lt;/span>] &lt;span style="color:#000;font-weight:bold">in&lt;/span> [&lt;span style="color:#c30">&amp;#39;.html&amp;#39;&lt;/span>, &lt;span style="color:#c30">&amp;#39;.css&amp;#39;&lt;/span>, &lt;span style="color:#c30">&amp;#39;.js&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> current_path &lt;span style="color:#555">=&lt;/span> os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>join(root, f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">with&lt;/span> &lt;span style="color:#366">open&lt;/span>(current_path, &lt;span style="color:#c30">&amp;#39;rb&amp;#39;&lt;/span>) &lt;span style="color:#069;font-weight:bold">as&lt;/span> f_in:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">with&lt;/span> gzip&lt;span style="color:#555">.&lt;/span>open(current_path &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#39;.gz&amp;#39;&lt;/span>, &lt;span style="color:#c30">&amp;#39;wb&amp;#39;&lt;/span>) &lt;span style="color:#069;font-weight:bold">as&lt;/span> f_out:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f_out&lt;span style="color:#555">.&lt;/span>writelines(f_in)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#555">.&lt;/span>replace(current_path &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#39;.gz&amp;#39;&lt;/span>, current_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">deploy_to_s3_bucket&lt;/span>(bucket, dry_run&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">False&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> dry_run:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Doing a dry run!&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>: &lt;span style="color:#09f;font-style:italic"># Upload all uncompressed files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --exclude &amp;#39;*.html&amp;#39; --exclude &amp;#39;*.js&amp;#39; --exclude &amp;#39;*.css&amp;#39; _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> dry_run:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command_ls &lt;span style="color:#555">=&lt;/span> command&lt;span style="color:#555">.&lt;/span>split()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command_ls&lt;span style="color:#555">.&lt;/span>insert(&lt;span style="color:#555">-&lt;/span>&lt;span style="color:#f60">2&lt;/span>, &lt;span style="color:#c30">&amp;#34;--dry-run&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#555">.&lt;/span>join(command_ls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>, stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong deploying the site to s3.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>: &lt;span style="color:#09f;font-style:italic"># Upload all compressed files and add an appropriate header&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --add-header=&amp;#39;Content-Encoding: gzip&amp;#39; --exclude &amp;#39;*.*&amp;#39; --include &amp;#39;*.html&amp;#39; --include &amp;#39;*.js&amp;#39; --include &amp;#39;*.css&amp;#39; _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> dry_run:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command_ls &lt;span style="color:#555">=&lt;/span> command&lt;span style="color:#555">.&lt;/span>split()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command_ls&lt;span style="color:#555">.&lt;/span>insert(&lt;span style="color:#555">-&lt;/span>&lt;span style="color:#f60">2&lt;/span>, &lt;span style="color:#c30">&amp;#34;--dry-run&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#555">.&lt;/span>join(command_ls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>, stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong setting the content encoding on the files deployed to s3&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>: &lt;span style="color:#09f;font-style:italic"># Remove any files that have been deleted&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --delete-removed _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> dry_run:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command_ls &lt;span style="color:#555">=&lt;/span> command&lt;span style="color:#555">.&lt;/span>split()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command_ls&lt;span style="color:#555">.&lt;/span>insert(&lt;span style="color:#555">-&lt;/span>&lt;span style="color:#f60">2&lt;/span>, &lt;span style="color:#c30">&amp;#34;--dry-run&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#39; &amp;#39;&lt;/span>&lt;span style="color:#555">.&lt;/span>join(command_ls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>, stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong removing files from the bucket&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">if&lt;/span> __name__ &lt;span style="color:#555">==&lt;/span> &lt;span style="color:#c30">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser &lt;span style="color:#555">=&lt;/span> argparse&lt;span style="color:#555">.&lt;/span>ArgumentParser(description&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#34;Deploy a Jekyll site to Amazon S3&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#555">.&lt;/span>add_argument(&lt;span style="color:#c30">&amp;#39;-s&amp;#39;&lt;/span>, &lt;span style="color:#c30">&amp;#39;--no-sass&amp;#39;&lt;/span>, help&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#34;Don&amp;#39;t compile Sass files&amp;#34;&lt;/span>, action&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#39;store_true&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#555">.&lt;/span>add_argument(&lt;span style="color:#c30">&amp;#39;-b&amp;#39;&lt;/span>, &lt;span style="color:#c30">&amp;#39;--beautiful-sass&amp;#39;&lt;/span>, help&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#34;Don&amp;#39;t minify Sass files&amp;#34;&lt;/span>, action&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#39;store_false&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#555">.&lt;/span>add_argument(&lt;span style="color:#c30">&amp;#39;-n&amp;#39;&lt;/span>, &lt;span style="color:#c30">&amp;#39;--dry-run&amp;#39;&lt;/span>, help&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#34;Perform a dry run when deploying to S3 (akin to running s3cmd with the --dry-run flag)&amp;#34;&lt;/span>, action&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#39;store_true&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> args &lt;span style="color:#555">=&lt;/span> parser&lt;span style="color:#555">.&lt;/span>parse_args()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path_to_sass_file &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;assets/styles/sass/styles.scss&amp;#34;&lt;/span> &lt;span style="color:#09f;font-style:italic"># Change to your path&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sass_compile_path &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;assets/styles/css/styles.css&amp;#34;&lt;/span> &lt;span style="color:#09f;font-style:italic"># Change to your path&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> args&lt;span style="color:#555">.&lt;/span>no_sass &lt;span style="color:#555">==&lt;/span> &lt;span style="color:#069;font-weight:bold">False&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Compiling Sass Files...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> compile_sass(path_to_sass_file, sass_compile_path, minify&lt;span style="color:#555">=&lt;/span>args&lt;span style="color:#555">.&lt;/span>beautiful_sass)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Running Jekyll...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> generate_site()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Gzipping File...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gzip_files()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Getting Bucket Name...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bucket_name &lt;span style="color:#555">=&lt;/span> get_s3_bucket_name()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> i &lt;span style="color:#000;font-weight:bold">in&lt;/span> &lt;span style="color:#366">reversed&lt;/span>(&lt;span style="color:#366">range&lt;/span>(&lt;span style="color:#f60">1&lt;/span>,&lt;span style="color:#f60">4&lt;/span>)):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#c30;font-weight:bold">\r&lt;/span>&lt;span style="color:#c30">Deploying to &lt;/span>&lt;span style="color:#a00">{0}&lt;/span>&lt;span style="color:#c30"> in &lt;/span>&lt;span style="color:#a00">{1}&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#555">.&lt;/span>format(bucket_name, i), end&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#c30">&amp;#39;&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sleep(&lt;span style="color:#f60">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#c30;font-weight:bold">\n&lt;/span>&lt;span style="color:#c30">Deploying...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploy_to_s3_bucket(bucket_name, dry_run&lt;span style="color:#555">=&lt;/span>args&lt;span style="color:#555">.&lt;/span>dry_run)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Successfully Deployed Site!&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>At the same time, I&amp;rsquo;ve removed half of the comments in the script because most of them were &lt;code>#increment i by one&lt;/code> style comments.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Deploying This Jekyll Site to S3</title><link>https://alexj.org/01/deploying-this-jekyll-site-to-s3/</link><pubDate>Thu, 17 Jan 2013 21:22:00 +0000</pubDate><guid>https://alexj.org/01/deploying-this-jekyll-site-to-s3/</guid><description>&lt;p>If you’re the type of person who likes to read the footers of websites (who doesn’t?) you’ll have seen that this website is &amp;ldquo;powered” by Jekyll. In case you haven’t heard, Jekyll is a static site generator that is especially useful for generating static blogs. That is, you run the Jekyll program on a computer, point it at a “source” directory for your website and let it generate a load of HTML files that you can upload to practically any web server that serves files. There’s no need to install PHP or Rails and no need to get a fancy server that can run them. Jekyll is a really awesome utility and that is a terrible description of what it does. You’re better off checking out the &lt;a href="https://github.com/mojombo/jekyll">GitHub page&lt;/a> and seeing how that describes Jekyll.&lt;/p>
&lt;p>The purpose of this post isn’t to describe what Jekyll is though, it’s to tell you how I deploy this blog that is built with Jekyll to Amazon S3. As a lot of people have written, Amazon S3 is a great place to host small Jekyll blogs. It’s cheap, it’s fast and it has an exceptionally good uptime.&lt;/p>
&lt;p>Deploying a Jekyll blog to Amazon S3 is relatively straightforward by using a tool called &lt;a href="http://s3tools.org/s3cmd">s3cmd&lt;/a>. s3cmd is a powerful tool that provides a good, command line method for synchronising a directory with a bucket on Amazon S3. While running s3cmd on your generated site will produce a website that’s hosted on S3 (once you’ve set up permissions, DNS etc.) there’s a few things that can be done to optimise your website and make it a bit faster.&lt;/p>
&lt;p>You can run gzip to compress all your CSS, HTML &amp;amp; JavaScript files but in order to serve these files, you’ll need to specify the correct content encoding on the compressed files otherwise browsers won’t render your pages. You can also run CSS &amp;amp; JavaScript minification, heck, you can even run HTML minification if you’re desperate to squeeze some speed out of your site. The problem is, running all of these processes requires you to remember a lot of different and complicated commands. Even if you have the memory of a &lt;a href="http://newscientist.com/article/dn2960-sea-lion-scores-top-for-memory.html">sea lion&lt;/a>, running all these commands makes deploying your site slow and tedious. I’ve gone ahead and written a script that combines all the commands I need for deploying my website and I thought I would share it with the world.&lt;/p>
&lt;p>The script is written in Python because I don’t know any other scripting languages that well. I don’t know Python that well too but it’s the scripting language I’m most confident in. The script does a couple of things. The first thing it does is compile any sass files that need to be compiled and minifies the resulting CSS file. This is (possibly) quite specific to my website since not all Jekyll sites are going to use sass (though I do recommend it) and not all sites are going to compile just one sass file&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. After compiling the sass files, the &lt;code>jekyll&lt;/code> command is run, building the site. I’ve added a plugin to Jekyll to minify HTML&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> so that gets done when the site is generated rather than by using another package. Once the site is generated, I run the &lt;code>gzip&lt;/code> command to compress all HTML, CSS &amp;amp; JavaScript files. Finally, the generated website is uploaded to an Amazon S3 bucket with the gzipped files having the appropriate content header set so that they will be loaded by browsers.&lt;/p>
&lt;p>The script basically wraps up five or six commands into one simple &lt;code>python _deploy.py&lt;/code> command. Any way, enough waffle. The script:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">#! /usr/local/bin/python3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0cf;font-weight:bold">yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0cf;font-weight:bold">gzip&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0cf;font-weight:bold">os&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">from&lt;/span> &lt;span style="color:#0cf;font-weight:bold">subprocess&lt;/span> &lt;span style="color:#069;font-weight:bold">import&lt;/span> PIPE, check_call, CalledProcessError
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">from&lt;/span> &lt;span style="color:#0cf;font-weight:bold">sys&lt;/span> &lt;span style="color:#069;font-weight:bold">import&lt;/span> exit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">get_s3_bucket_name&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">with&lt;/span> &lt;span style="color:#366">open&lt;/span>(&lt;span style="color:#c30">&amp;#34;_config.yml&amp;#34;&lt;/span>) &lt;span style="color:#069;font-weight:bold">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> config_file &lt;span style="color:#555">=&lt;/span> yaml&lt;span style="color:#555">.&lt;/span>load(f)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> config_file[&lt;span style="color:#c30">&amp;#34;s3bucket&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> &lt;span style="color:#c00;font-weight:bold">KeyError&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Error, no S3 bucket was found in _config.yml.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> &lt;span style="color:#c00;font-weight:bold">IOError&lt;/span> &lt;span style="color:#069;font-weight:bold">as&lt;/span> e:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;I/O error(&lt;/span>&lt;span style="color:#a00">{0}&lt;/span>&lt;span style="color:#c30">): &lt;/span>&lt;span style="color:#a00">{1}&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#555">.&lt;/span>format(e&lt;span style="color:#555">.&lt;/span>errno, e&lt;span style="color:#555">.&lt;/span>strerror))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">compile_sass&lt;/span>(input_file, output_file, minify&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;sass &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> path_to_sass_file &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#34;:&amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> sass_compile_path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> minify:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> command &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#34; --style compressed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Something went wrong compiling sass files.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">generate_site&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call([&lt;span style="color:#c30">&amp;#34;jekyll&amp;#34;&lt;/span>, &lt;span style="color:#c30">&amp;#34;--no-auto&amp;#34;&lt;/span>], stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong generating the site with Jekyll.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">gzip_files&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> root, dirs, files &lt;span style="color:#000;font-weight:bold">in&lt;/span> os&lt;span style="color:#555">.&lt;/span>walk(&lt;span style="color:#c30">&amp;#34;_site/&amp;#34;&lt;/span>): &lt;span style="color:#09f;font-style:italic"># Traverse the _site/ directory&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> f &lt;span style="color:#000;font-weight:bold">in&lt;/span> files:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filename, extension &lt;span style="color:#555">=&lt;/span> os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>splitext(f) &lt;span style="color:#09f;font-style:italic"># Get the extension of the current file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> extension &lt;span style="color:#555">==&lt;/span> &lt;span style="color:#c30">&amp;#34;.html&amp;#34;&lt;/span> &lt;span style="color:#000;font-weight:bold">or&lt;/span> extension &lt;span style="color:#555">==&lt;/span> &lt;span style="color:#c30">&amp;#34;.css&amp;#34;&lt;/span> &lt;span style="color:#000;font-weight:bold">or&lt;/span> extension &lt;span style="color:#555">==&lt;/span> &lt;span style="color:#c30">&amp;#34;.js&amp;#34;&lt;/span>: &lt;span style="color:#09f;font-style:italic"># Check to see if it&amp;#39;s a compressible extension&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">with&lt;/span> &lt;span style="color:#366">open&lt;/span>(os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>join(root, f), &lt;span style="color:#c30">&amp;#39;r&amp;#39;&lt;/span>) &lt;span style="color:#069;font-weight:bold">as&lt;/span> f_to_compress: &lt;span style="color:#09f;font-style:italic"># Open the file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">with&lt;/span> gzip&lt;span style="color:#555">.&lt;/span>open(os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>join(root, f) &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#34;.gz&amp;#34;&lt;/span>, &lt;span style="color:#c30">&amp;#39;wb&amp;#39;&lt;/span>) &lt;span style="color:#069;font-weight:bold">as&lt;/span> f_compressed: &lt;span style="color:#09f;font-style:italic"># Open a new gzip file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f_compressed&lt;span style="color:#555">.&lt;/span>writelines(f_to_compress) &lt;span style="color:#09f;font-style:italic"># Write the original file into the gzip file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#555">.&lt;/span>remove(os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>join(root, f)) &lt;span style="color:#09f;font-style:italic"># Remove the original file.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#555">.&lt;/span>rename(os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>join(root, f) &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#c30">&amp;#34;.gz&amp;#34;&lt;/span>, os&lt;span style="color:#555">.&lt;/span>path&lt;span style="color:#555">.&lt;/span>join(root, f)) &lt;span style="color:#09f;font-style:italic"># Rename file to original&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">def&lt;/span> &lt;span style="color:#c0f">deploy_to_s3&lt;/span>(bucket, dry&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">False&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> dry:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;DOING DRY RUN&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>: &lt;span style="color:#09f;font-style:italic"># Upload all uncompressed files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> &lt;span style="color:#000;font-weight:bold">not&lt;/span> dry:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --exclude &amp;#39;*.html&amp;#39; --exclude &amp;#39;*.js&amp;#39; --exclude &amp;#39;*.css&amp;#39; _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --exclude &amp;#39;*.html&amp;#39; --exclude &amp;#39;*.js&amp;#39; --exclude &amp;#39;*.css&amp;#39; _site/ --dry-run &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>, stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong deploying the site to s3.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>: &lt;span style="color:#09f;font-style:italic"># Upload all compressed files and add an appropriate header&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> &lt;span style="color:#000;font-weight:bold">not&lt;/span> dry:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --add-header=&amp;#39;Content-Encoding: gzip&amp;#39; --exclude &amp;#39;*.*&amp;#39; --include &amp;#39;*.html&amp;#39; --include &amp;#39;*.js&amp;#39; --include &amp;#39;*.css&amp;#39; _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --add-header=&amp;#39;Content-Encoding: gzip&amp;#39; --exclude &amp;#39;*.*&amp;#39; --include &amp;#39;*.html&amp;#39; --include &amp;#39;*.js&amp;#39; --include &amp;#39;*.css&amp;#39; --dry-run _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>, stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong setting the content encoding on the files deployed to s3&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> &lt;span style="color:#000;font-weight:bold">not&lt;/span> dry:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --delete-removed _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;s3cmd sync -P --delete-removed --dry-run _site/ &amp;#34;&lt;/span> &lt;span style="color:#555">+&lt;/span> bucket
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> check_call(command, shell&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>, stdout&lt;span style="color:#555">=&lt;/span>PIPE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">except&lt;/span> CalledProcessError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit(&lt;span style="color:#c30">&amp;#34;Something went wrong removing files from the bucket&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">if&lt;/span> __name__ &lt;span style="color:#555">==&lt;/span> &lt;span style="color:#c30">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path_to_sass_file &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;assets/styles/sass/styles.scss&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sass_compile_path &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#c30">&amp;#34;assets/styles/css/styles.css&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Compiling Sass Files...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> compile_sass(path_to_sass_file, sass_compile_path, minify&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Running Jekyll...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> generate_site()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Gzipping File...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gzip_files()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Getting Bucket Name...&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bucket_name &lt;span style="color:#555">=&lt;/span> get_s3_bucket_name()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Deploying to &lt;/span>&lt;span style="color:#a00">%s&lt;/span>&lt;span style="color:#c30">...&amp;#34;&lt;/span> &lt;span style="color:#555">%&lt;/span> bucket_name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deploy_to_s3(bucket_name, dry&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#069;font-weight:bold">False&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#c30">&amp;#34;Successfully Deployed Site!&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There’s a few prerequisites for the script. You’ll need to install the &lt;a href="http://pyyaml.org/wiki/PyYAML">PyYaml&lt;/a> library for Python, as well as the &lt;a href="http://sass-lang.com">sass&lt;/a> &amp;amp; &lt;a href="http://s3tools.org/s3cmd">s3cmd&lt;/a> packages. You’ll also need to modify your website’s &lt;code>_config.yml&lt;/code> file so that it has the name of the s3 bucket you want s3cmd to upload your website to:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#309;font-weight:bold">s3bucket&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>s3://bucketname&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you don’t need to compile any sass files, go ahead and comment out&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> the 6th line of the &lt;code>__main__&lt;/code> method (that’s where it says &lt;code>compile_sass(path_to_sass_file, sass_compile_path, minify=True)&lt;/code>). If you do need to compile sass files, modify the &lt;code>path_to_sass_file&lt;/code> and &lt;code>sass_compile_path&lt;/code> variables so that they point to where the main sass file is and where you want it to be compiled to.&lt;/p>
&lt;p>And that’s it. It’s not the most general script in the world but I can see it coming in handy for one or two people who have similar deployment processes to me. If you want to leave some feedback on the script, I’ve created a &lt;a href="https://gist.github.com/4559517">Gist on GitHub&lt;/a> for the script with some more extensive documentation.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>By one sass file I don’t mean I’ve got all of my sass in one file, I just compile lots of different sass files into one CSS file.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>I know HTML minification isn’t the safest or best way to boost a site’s performance, you don’t need to tell me.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>To comment a line out in Python just add a &lt;code>#&lt;/code> to the start of the line.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Fixing Slow Wireless Speeds in Ubuntu 12.10</title><link>https://alexj.org/12/fixing-slow-wiereless-speeds-in-ubuntu/</link><pubDate>Mon, 31 Dec 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/12/fixing-slow-wiereless-speeds-in-ubuntu/</guid><description>&lt;p>After building a new computer recently I went ahead and installed Ubuntu 12.10 on it alongside Windows. Since the computer doesn&amp;rsquo;t have a wireless card and isn&amp;rsquo;t anywhere near a router, I bought a cheap Wireless USB adapter so that I could connect to my wireless network. I actually bought the adapter several years ago and used it on a computer running Ubuntu 10.04 that was in a similar situation. The adapter I bought was a &lt;a href="http://www.amazon.co.uk/gp/product/B001HO3ZTQ/ref=as_li_ss_tl?ie=UTF8&amp;amp;camp=1634&amp;amp;creative=19450&amp;amp;creativeASIN=B001HO3ZTQ&amp;amp;linkCode=as2&amp;amp;tag=simpl06-21">Belkin F5D8053&lt;/a>, the v6 revision. I bought this adapter because I&amp;rsquo;d been told that it had good compatibility with Linux and I remember it working perfectly well in Ubuntu 10.04. I was surprised to find then that when I used the adapter with my new installation of Ubuntu 12.10 it was very unreliable. The adapter worked but after arround five minutes of usage, the connection speed would drop substantially and anything, be it on the internet or my local network, was incredibly slow.&lt;/p>
&lt;p>I set about trying to figure out what was up. The first thing I thought was that maybe the driver used for the adapter had been messed up somehow between Ubuntu 10.04 and 12.10. Running &lt;code>lsusb&lt;/code> revealed that the adapter used a RTL8191SU chipset by Realtek so I went to Realtek&amp;rsquo;s website and downloaded the source to compile the driver for the adapter. Turns out, something changed in version 3.4 of the Linux Kernel that made compiling these drivers impossible without some minor modifications. Version 3.5 of the Kernel broke even those modifications so compiling new drivers wasn&amp;rsquo;t an option any more.&lt;/p>
&lt;p>Some more Googling produced some interesting information. Apparently, there&amp;rsquo;s a &lt;a href="https://bugs.launchpad.net/ubuntu/+source/linux/+bug/621265">bug&lt;/a> in network-manager, the package used by Ubuntu to connect to networks, that results in the connection speed slowing down after a few minutes of use. The solution, install an alternate to network-manager and disable network-manager. I went ahead and installed &lt;a href="https://launchpad.net/wicd">wicd&lt;/a> and disabled network-manager and so far, in the past 36 hours of use (around 12 hours continuous use over 3 days), I&amp;rsquo;ve had no drop in connection speed. It would seem that the drop in connection speed is due to network-manager which means that people having similar problems with connection speed could find that replacing network-manager will solve their problems.&lt;/p>
&lt;h2 id="the-fix">The Fix&lt;/h2>
&lt;p>The fix is quite simple, install wicd and disable network-manager. The steps for doing this are outlined below:&lt;/p>
&lt;p>First, install the wicd package:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get intall wicd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, disable network-manager and stop it from starting on startup too:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo stop network-manager
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo mv /etc/init/network-manager.conf /etc/init/network-manager.conf.off
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now start the wicd daemon and bring up the GUI tool so you can connect to your wireless network:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo /etc/init.d/wicd start
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wicd-client
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And that&amp;rsquo;s it, assuming that network-manager was your problem, you should now have a far more stable wireless connection and wicd should start on login too. If you want to get wicd in Unity&amp;rsquo;s system tray, you&amp;rsquo;ll need to run the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>gsettings &lt;span style="color:#366">set&lt;/span> com.canonical.Unity.Panel systray-whitelist &lt;span style="color:#c30">&amp;#34;[&amp;#39;wicd&amp;#39;]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then logout and log back in, you should now have a (fairly ugly) tray icon for wicd. There&amp;rsquo;s replacement icons available, just Google for them.&lt;/p>
&lt;p>Once you&amp;rsquo;re certain that wicd has solved your problem and you don&amp;rsquo;t need network-manager anymore, you can go ahead and remove nework-manager. First, revert the changes made to network-manager&amp;rsquo;s upstart job and then remove the package:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo mv /etc/init/network-manager.conf.off /etc/init/network-manager.conf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get remove --purge network-manager
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That solved the problem for me. If it does for you, be sure to file a bug report so that this issue hopefully gets fixed in network-manager.&lt;/p></description></item><item><title>SymSteam Beta 2 Released</title><link>https://alexj.org/12/symsteam-beta-2-released/</link><pubDate>Mon, 31 Dec 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/12/symsteam-beta-2-released/</guid><description>&lt;p>It may have taken nearly 9 months but beta 2 of SymSteam is finally available for download. This is a &lt;em>huge&lt;/em> update for SymSteam that completely changes how it works and fixes many bugs as a result. SymSteam no longer manages drives based on their names, rather, it relies on the UUID of a drive to determine if the drive is the Steam drive. This makes SymSteam more reliable in situations where you have drives with the same name but it does introduce a new requirement. The drive you&amp;rsquo;re storing your games on must have a UUID which essentially means that the drive must be HFS+ formatted&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>In addition to this major change, lots of small changes have also been made that make SymSteam easier to use and more reliable. The error correction performed on your Steam folders when SymSteam is launched is far more reliable now and SymSteam can fix incorrect folder setups in many more cases, reducing the number of situations where SymSteam will fail to work. The setup process has been rewritten to make it much simpler to use and also to make it less likely to fail. The preferences window has been expanded upon too, providing more in-depth options for notifications and an option to start SymSteam on login. Oh, SymSteam finally has an icon too! It&amp;rsquo;s not amazing but it&amp;rsquo;s better than the generic app icon that SymSteam had before the update.&lt;/p>
&lt;p>The full changelog for beta 2 can be found &lt;a href="http://alexjohnj.github.com/symsteam/changelog.html">here&lt;/a>.&lt;/p>
&lt;p>SymSteam now has some fairly extensive documentation available on its new &lt;a href="http://www.alexj.me/projects/symsteam/">project page&lt;/a>. The new documentation includes information on setting up SymSteam and some troubleshooting information too. The documentation can be accessed &lt;a href="http://www.alexj.me/projects/symsteam/documentation/">here&lt;/a>.&lt;/p>
&lt;p>If you already have SymSteam installed then it will prompt you to update (assuming you&amp;rsquo;ve allowed it to). If not, you can download beta 2 of SymSteam &lt;a href="http://www.alexj.me/projects/symsteam/">here&lt;/a>.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>There are other filesystems that assign drives a UUID but I don&amp;rsquo;t think any of these have native support in OS X.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Recent Projects</title><link>https://alexj.org/06/recent-projects/</link><pubDate>Wed, 13 Jun 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/06/recent-projects/</guid><description>&lt;p>It would seem that pretty much the only thing I use this blog for is announcements. Guess what&amp;rsquo;s in this post, some more announcements, specifically regarding some new projects that I&amp;rsquo;ve started in the past month (and a bit).&lt;/p>
&lt;p>Now that I&amp;rsquo;ve got a lot more free time on my hands, I&amp;rsquo;ve managed to start two new Cocoa projects for the Mac, one of which I&amp;rsquo;ve been intending to do for several months. They&amp;rsquo;re both free applications and they&amp;rsquo;re both open source, with the source code being available off of my &lt;a href="https://github.com/alexjohnj">GitHub&lt;/a> page. If you were to categorise both of these projects, they&amp;rsquo;d both fall into two categories that are totally new to me in terms of development. One of the projects is a game and another is an utility aimed at developers.&lt;/p>
&lt;h2 id="memtester----mac">MemTester ( - Mac)&lt;/h2>
&lt;p>MemTester is the game. I&amp;rsquo;ve never made a game before so this was/is quite a fun little project to work on. MemTester is a very simple memory testing game (surprise, surprise) that tests your ability to memorise an increasingly lengthy sequence of numbers, letters or symbols. The game has three difficulties, easy (just numbers), medium (just lowercase letters) &amp;amp; hard (nearly all possible ASCII characters) and has amazing features like a high scores table &amp;amp; fancy animations (/sarcasm). I started working on MemTester as a port of a similar game that a friend of mine made. &lt;a href="http://code.google.com/p/mem-tester">My friend&amp;rsquo;s version&lt;/a> was written in Java and was very un-Mac like so I decided to make a native Cocoa port of the game for fun. He wasn&amp;rsquo;t best pleased.&lt;/p>
&lt;p>So far, version 1.0 of MemTester has been released and there&amp;rsquo;s been no further development since then (though I do have a couple of things planned). Development for MemTester will be on and off, but you can watch &lt;a href="https://github.com/alexjohnj/memtester-mac">the project&lt;/a> on GitHub. You can also download the latest version from &lt;a href="https://github.com/alexjohnj/memtester-mac/downloads">here&lt;/a>.&lt;/p>
&lt;h2 id="appcastr">Appcastr&lt;/h2>
&lt;p>Appcastr is the development tool. It&amp;rsquo;s quite possibly my most ambitious project to date and so far, developing it has been a joy and I&amp;rsquo;ve learnt a lot of new things working on it. Appcastr is a tool for developers who use the Sparkle update system, that&amp;rsquo;s the update system used by 99% of Mac applications that aren&amp;rsquo;t in the Mac App Store, that makes it easier &amp;amp; quicker for developers to create and maintain appcast files by providing a GUI to edit them with. Appcast files are just RSS files with a special &lt;code>&amp;lt;enclosure&amp;gt;&lt;/code> element in them that allows them to include information on the latest version of an application and where to get it from. The appcast file is downloaded by the Sparkle update framework and then parsed to see if there&amp;rsquo;s a new version of the application available.&lt;/p>
&lt;p>Since appcast files are just XML files, it&amp;rsquo;s not exactly hard to edit the files by hand but it&amp;rsquo;s a lot faster to use a GUI application and I plan on adding a couple of handy features that will make distributing an update significantly faster if the developer chooses to use Appcastr.&lt;/p>
&lt;p>I&amp;rsquo;m a bit concerned working on a project like Appcastr. Given that it&amp;rsquo;s aimed at developers, it&amp;rsquo;s much more likely that the source code will be viewed by, and possibly edited by, other developers than my other projects. This means the quality of my code is a lot more important and will probably receive some judgment. Make no mistake, I really want other developers to look at my code and tell me where to improve it. Given that I&amp;rsquo;ve taught myself to program, I&amp;rsquo;m certain that my code is rife with malpractice and things that&amp;rsquo;d probably make more experienced developers cry. I&amp;rsquo;d love to hear some constructive criticism about the quality of my code.&lt;/p>
&lt;p>Appcastr version 0.1 was released just yesterday and version 0.1.1 was released this morning. I&amp;rsquo;m going to work diligently on this project so expect frequent updates. You can get the bleeding edge version of Appcastr by visiting the &lt;a href="https://github.com/alexjohnj/appcastr">project&amp;rsquo;s GitHub page&lt;/a> and checking for the branch which is the most commits ahead (at the time of writing, version 0.2). Be warned though, things could be broken or buggy in that branch. The &amp;ldquo;master&amp;rdquo; branch will have more stable code and (should) be a representation of the most recent build available off of the project&amp;rsquo;s &lt;a href="https://github.com/alexjohnj/appcastr/downloads">downloads page&lt;/a>.&lt;/p>
&lt;hr>
&lt;p>So they&amp;rsquo;re the two new projects that I&amp;rsquo;ve launched recently. My third project, &lt;a href="https://github.com/alexjohnj/symsteam">SymSteam&lt;/a>, hasn&amp;rsquo;t received much love recently. It&amp;rsquo;s been stuck on beta 1 for a while. To be honest, I&amp;rsquo;ve been using the application day-in-day-out for several months now and haven&amp;rsquo;t noticed any bugs in my usage so I&amp;rsquo;m tempted to make an icon for the application and release it as version 1.0. Laziness FTW!&lt;/p></description></item><item><title>Thoughts on WWDC 2012</title><link>https://alexj.org/06/thoughts-on-wwdc/</link><pubDate>Tue, 12 Jun 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/06/thoughts-on-wwdc/</guid><description>&lt;p>Yesterday (June 11th) marked the first day of Apple&amp;rsquo;s famous WWDC event. As per usual, Apple opened the event with the only part of the event that isn&amp;rsquo;t under NDA (and hence the only part that I&amp;rsquo;ve actually been able to see so far), the keynote. The keynote was a blast with lots of new, exciting things announced. Here&amp;rsquo;s my thoughts on a couple of those things that were announced.&lt;/p>
&lt;h2 id="updated-macbook-airshttpwwwapplecommacbookair">&lt;a href="http://www.apple.com/macbookair/">Updated MacBook Airs&lt;/a>&lt;/h2>
&lt;p>The updated MacBook Airs are really nice. They&amp;rsquo;re a lot faster now thanks to both a new generation of Ivy Bridge processors and faster SSD technology. I&amp;rsquo;d really like to get one but I&amp;rsquo;ve just splashed out on a last generation MacBook Air, so can&amp;rsquo;t afford the new generation any time soon. The graphical performance of the new Airs has been improved by “up to 60%” which really excites me. The Airs still have an integrated graphics card but it&amp;rsquo;s an Intel HD 4000 series card, which is a surprisingly beefy integrated card and performs respectably at many modern games. The price of the Airs has been dropped a bit too. Here in the UK, the prices have gone down enough that the low end 13 inch model costs the same as the previous generation low end 11 inch model. Nice.&lt;/p>
&lt;p>The MacBook Pros also got an update, just a small bump in specs, nothing too exciting. Apple seems to have deprecated the 17 inch MacBook Pro though which&amp;rsquo;ll annoy about ten people.&lt;/p>
&lt;h2 id="macbook-pro-with-retina-displayhttpwwwapplecommacbook-pro">&lt;a href="http://www.apple.com/macbook-pro/">MacBook Pro With Retina Display&lt;/a>&lt;/h2>
&lt;p>Wow. Just wow. This thing is awesome. This isn&amp;rsquo;t the 15 inch Air that everybody was hoping for but I think it&amp;rsquo;s better. The new MacBook Pro has a completely redesigned case which is stunning to look at. What&amp;rsquo;s even more stunning to look at though is that new display. It has over 5 million pixels in it. I haven&amp;rsquo;t seen one of these yet but I know from the numbers, that display is gorgeous. The new MacBook Pro has really excited me, it represents the start of a revamp of Apple&amp;rsquo;s line of computers. I envision new designs &amp;amp; retina displays coming to most of Apple&amp;rsquo;s computers throughout the next couple of years.&lt;/p>
&lt;h2 id="os-x-mountain-lionhttpwwwapplecomosx">&lt;a href="http://www.apple.com/osx/">OS X Mountain Lion&lt;/a>&lt;/h2>
&lt;p>Mountain Lion was probably my favourite piece of news from the WWDC keynote. True, most of what Apple talked about in the keynote had already been posted on their website but there was still some interesting new features that they announced. The new “Power Nap” feature is pretty neat, allowing you to download updates to your Mac while it&amp;rsquo;s in sleep mode. It&amp;rsquo;ll also keep all of your iCloud stuff in sync while your Mac sleeps which is handy. If Apple includes support for updating your (App Store) applications while in sleep mode too then that&amp;rsquo;d just be fantastic.&lt;/p>
&lt;p>The inclusion of speech-to-text processing in Lion too is pretty cool. If the processing is as good as iOS&amp;rsquo;s speech-to-text then I can see this feature actually being really useful for dictating long text documents. Apple says that the dictation works in any text field too, so no need to wait for developers to update their applications. The only saddening part was that there was no mention of a speech API, something which I would love to play around with.&lt;/p>
&lt;p>There were a couple of other tidbits in 10.8 that looked interesting. The new version of Safari seems pretty good and I&amp;rsquo;ll probably switch back to it from Chrome when it is released. GPU accelerated scrolling was also mentioned, which would make scrolling a lot smoother in OS X and would help make OS X &lt;em>feel&lt;/em> even faster. The fact that Apple seems to have “fixed” full screen mode on external displays is fantastic too. Full screen mode might actually be useful in 10.8 now.&lt;/p>
&lt;h2 id="ios-6httpwwwapplecomiosios6">&lt;a href="http://www.apple.com/ios/ios6/">iOS 6&lt;/a>&lt;/h2>
&lt;p>iOS 6 was probably the most disappointing of all the announcements at WWDC this year. There were good aspects to the announcement but overall I felt unsatisfied with the new features that had been announced. Siri was updated with support for getting information on sports &amp;amp; movie, a feature which I doubt will be used much. She also got some new voice commands, a few of which will be useful. She finally gained support for obtaining local business information outside of the US, something which I&amp;rsquo;ve been waiting for since the iPhone 4S launched back in October. There&amp;rsquo;s still no API for Siri though which is really annoying. I like to control my music using Siri (saves getting the phone out of my pocket) but I don&amp;rsquo;t use the built in music app. It would be nice if I could use music voice commands in other apps like Rdio, something which would only be possible with an API.&lt;/p>
&lt;p>Unfortunately, there was no UI refresh in iOS 6 which was the main thing I was hoping for. Well, I say there was no UI refresh but really there was a mixture of UI updates which seems to have removed a lot of consistency in the iOS&amp;rsquo;s UI. Different shades of colours have been changed throughout the UI. The status bar, for example, is now a silvery blue instead of grey which is an interesting colour choice. There seems to be a slightly different shade of blue being used in navigation bars too. The new maps app has been given the silver UI treatment. It doesn&amp;rsquo;t look out of place in the OS but it certainly isn&amp;rsquo;t consistent with the rest of the OS&amp;rsquo;s UI. I don&amp;rsquo;t see why Apple would update parts of the UI in one application without updating the rest of the OS. It really doesn&amp;rsquo;t play nice with the consistency of the user interface throughout the operating system.&lt;/p>
&lt;p>As expected, Apple updated the maps app and they&amp;rsquo;re now using their own custom mapping service rather than Google Maps. The new maps (as in the actual maps) look a lot nicer and cleaner when compared to Google&amp;rsquo;s and Apple&amp;rsquo;s included some swanky 3D mode in the maps which looks pretty awesome. The new maps application also includes support for turn by turn directions which is pretty neat. I don&amp;rsquo;t have any use for it (yet) though.&lt;/p>
&lt;p>There were tons of other new features in iOS 6. The phone application (yeah, I&amp;rsquo;d forgotten about that too) got some nice updates which could come in handy for gracefully rejecting phone calls. There&amp;rsquo;s also a new Passbook application which looks like a virtual wallet for different types of tickets. I think it&amp;rsquo;ll be &lt;em>really&lt;/em> awesome but I doubt it&amp;rsquo;s going to get much use outside of the US for a long time which is a crying shame. Such an application could be really useful.&lt;/p>
&lt;p>The major disappointment with iOS 6 is its lack of backwards compatibility. Apple&amp;rsquo;s saying the update is compatible with the iPhone 3GS and onwards, the second &amp;amp; third generation iPad &amp;amp; the fourth generation iPod Touch. I don&amp;rsquo;t understand why the 3rd generation iPod Touch or 1st Generation iPad aren&amp;rsquo;t on the list though since they&amp;rsquo;re basically identical to the iPhone 3GS and iPhone 4 respectively in terms of processing power. Maybe I can understand the 1st Generation iPad not making the shortlist, it is quite slow to use with iOS 5 at the moment, but the 3rd generation iPod Touch, that represents a sizeable chunk of the iOS market and it&amp;rsquo;s pretty much the same as the 3GS. Strange.&lt;/p>
&lt;p>Overall, this year&amp;rsquo;s WWDC keynote has been very impressive. Lots of new products that are very exciting. I&amp;rsquo;m really looking forward to Mountain Lion (due in July) and I think it&amp;rsquo;s going to be a good all around update to OS X. iOS 6 will be pretty good too though, for me, it&amp;rsquo;s not as exciting as Mountain Lion. The thing I&amp;rsquo;m waiting for now is the release of the actual development sessions from WWDC, they&amp;rsquo;re probably the best part of the whole week for me.&lt;/p></description></item><item><title>SymSteam Beta 1 Released</title><link>https://alexj.org/04/symsteam-beta-1-released/</link><pubDate>Wed, 11 Apr 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/04/symsteam-beta-1-released/</guid><description>&lt;p>Good news! Just the other day, SymSteam, my small pet project, received a pretty major update. In fact, this update is so major, I&amp;rsquo;ve labelled it Beta 1. Pretty much everything to do with SymSteam has changed.&lt;/p>
&lt;p>Beta 1 brings numerous new features and changes to the application. A list of the changes is available on SymSteam&amp;rsquo;s new website &lt;a href="http://alexjohnj.github.com/symsteam/changelog.html">here&lt;/a> but in this post I&amp;rsquo;d just like to elaborate on some of the features and the work that&amp;rsquo;s gone into beta 1 of SymSteam.&lt;/p>
&lt;p>Development on SymSteam 0.2 began back in February with the simple aim of adding support for Growl notifications and fixing some bugs to make SymSteam more usable. Growl support was added pretty quickly but I had the idea of rewriting the underlying logic of SymSteam which helped to remove a substantial number of bugs. The rewrite took the best part of a day and made SymSteam far easier to use. It simplified the setup procedure and helped make SymSteam less likely to fail in difficult situations. After this rewrite, I rewrote the scanning system for detecting the SteamApps folder. I changed it so that SymSteam performed first a shallow scan (only scanning top level directories) and then it performed a deep scan, should it not find the SteamApps folder when performing the shallow scan. This change in the scanning mechanism helped to significantly reduce the time it took for SymSteam to detect the SteamApps folder when a drive was plugged in, in around 90% of cases.&lt;/p>
&lt;p>After making all these changes to the scanning system however, I decided to endulge on another rewrite, completely changing the way SymSteam functions. SymSteam no longer scans all drives that are connected for the SteamApps folder, rather it will resolve the symbolic link you point it to and only scan at that directory, performing a simple check to see if the folder exists at the symbolic link or not. If the correct drive is connected, the folder will exist and SymSteam can update the SteamApps folders so that Steam loads its games off of the external hard drive. If the folder doesn&amp;rsquo;t exist however, then life carries on as normal and nothing changes. This is &lt;em>a lot&lt;/em> faster than scanning each drive that is connected and SymSteam can now work in a split second rather than a few seconds.&lt;/p>
&lt;p>After this rewrite, I attempted to write a startup method that would run when SymSteam is launched that would attempt to fix any naming problems with the SteamApps folders that may arise when SymSteam is quit without being given the ability to rename the SteamApps folders so that the local folder is active. This error prevention method seems to work with limited success and I&amp;rsquo;ll continue to develop it in future versions to make it more successful.&lt;/p>
&lt;p>I decided to update the preferences window in SymSteam too, using the MASPreferences framework to provide a proper preferences window. I wrote about how to use MASPreferences and my experiences with it on SimpleCode &lt;a href="http://simplecode.me/2012/04/08/preferences-window-in-cocoa-maspreferences/">here&lt;/a>.&lt;/p>
&lt;p>There were several other changes within SymSteam, however I feel that these were the most substantial. Other changes include a vastly simplified setup procedure (which I briefly touched upon when talking about the rewrite), a new tool to help ease the creation of symbolic links and a new autoupdate system too, which will make pushing updates much easier in the future.&lt;/p>
&lt;p>SymSteam beta 1 is available now and I&amp;rsquo;ve already got plans for the next version of SymSteam, beta 2. SymSteam now has its own website, so you can get access to SymSteam beta 1 and the source code via &lt;a href="http://alexjohnj.github.com/symsteam">http://alexjohnj.github.com/symsteam&lt;/a>.&lt;/p></description></item><item><title>SymSteam 0.1.5 Released</title><link>https://alexj.org/02/symsteam-0-1-5-released/</link><pubDate>Sat, 18 Feb 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/02/symsteam-0-1-5-released/</guid><description>&lt;p>SymSteam, &lt;a href="https://alexj.org/02/symsteam">my small project I released just a few weeks ago&lt;/a>, received its first major update today. Version 0.1.5 was pushed to the GitHub repo a few hours ago and with it came an important new feature and some bug fixes.&lt;/p>
&lt;p>The most important new feature is a new preferences window which allows you to change the where your symbolic and local SteamApps folders are located. This is pretty handy since, previously, the only way to change these paths was to delete SymSteam&amp;rsquo;s plist file. Under the hood, SymSteam&amp;rsquo;s got a new system for managing connected drives so that it knows what drives are connected and, of those connected drives, which one is actually a Steam drive. This should remove a couple of bugs related to having multiple connected drives. It could, however, introduce some new ones. Watch out. One more thing to note, scanning drives for SteamApps folders should be a little bit faster now. Not massively, but a little bit.&lt;/p>
&lt;p>To download the latest version of SymSteam, head over &lt;a href="https://github.com/alexjohnj/SymSteam/downloads">here&lt;/a>. If you&amp;rsquo;re interested in the source code, you can check out the code &lt;a href="https://github.com/alexjohnj/SymSteam">here&lt;/a>. The code in the &lt;code>master&lt;/code> branch should be reasonably in sync with the latest version of SymSteam available to download. Most major new features will get their own branch while they&amp;rsquo;re being developed.&lt;/p>
&lt;h2 id="02">0.2&lt;/h2>
&lt;p>I also started working on version 0.2 today. The plan for version 0.2 is to add support for &lt;a href="http://growl.info/">Growl&lt;/a> notifications, improve the error handling logic for missing folders and add the ability to specify a single drive and path to scan for a SteamApps folder, improving performance greatly. So far, basic support for Growl has been added but not tested.&lt;/p>
&lt;p>If you want to see and (hopefully) contribute towards the development of the latest build of SymSteam, you can look at the &lt;code>symsteam-2.0&lt;/code> branch &lt;a href="https://github.com/alexjohnj/SymSteam/tree/symsteam-2.0">here&lt;/a> (I know, that should be &lt;code>symsteam-0.2.0&lt;/code> but I wasn&amp;rsquo;t thinking when I named the branch). Be warned, stuff will likely be unstable and broken in that branch.&lt;/p></description></item><item><title>Moving from 1Password To LastPass to 1Password Again</title><link>https://alexj.org/02/1password-to-lastpass-to-1password/</link><pubDate>Tue, 14 Feb 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/02/1password-to-lastpass-to-1password/</guid><description>&lt;p>In the past few weeks I&amp;rsquo;ve had a bit of an epiphany. I&amp;rsquo;ve become incredibly security and privacy conscious. I&amp;rsquo;ve closed my Facebook account, started moving away from Google and begun using far stronger, unique passwords. In the past, for websites, I&amp;rsquo;ve had one strong password which would be impossible to guess and then used that on every website (except my bank, which doesn&amp;rsquo;t allow special characters in a password). I know, shoot me. That&amp;rsquo;s the worst thing you can do in terms of passwords next to having 123456 as your password. Every website had the same password and email and (normally) username. If you got the password to one website, you had it to all of them. I&amp;rsquo;ve realised this security flaw and over the past couple of weeks, changed all of my passwords so that they are unique to each website and totally unintelligible to humans. 30 characters of random, well, characters. How the hell was I meant to remember them? I used a password manager. In the past, I&amp;rsquo;d been using &lt;a href="https://agilebits.com/onepassword">1Password&lt;/a> to “manage” my passwords. I&amp;rsquo;d really been using 1Password, not because I couldn&amp;rsquo;t remember my passwords, but rather to reduce the number of keystrokes needed when entering a password.&lt;/p>
&lt;p>Now that I was using impossible to remember passwords, a password manager was no longer a convenience, it was a necessity. After updating all my passwords in 1Password, everything was fine for a few days. 1Password is an excellent piece of software and is incredibly simple to use. Three days after changing all my passwords though, I hit a roadblock. I was on a public computer and needed to login to a website. Normally I&amp;rsquo;d just use 1Password but this wasn&amp;rsquo;t any computer. a) It didn&amp;rsquo;t have 1Password installed &amp;amp; b) It ran Linux. 1Password doesn&amp;rsquo;t run on Linux. It&amp;rsquo;s Mac &amp;amp; Windows only. I had a problem. To be fair to the 1Password developers, I could actually access my 1Password database from the computer via Dropbox. The 1Password data file includes a small web application that allows you to view your 1Password data from any web browser, without the main application being installed. The problem is, this requires my Dropbox account to have a memorable, and therefore insecure password, something I wasn&amp;rsquo;t comfortable doing. When I was back on my own computer, I started looking into a cross platform solution (since I use non Mac computers a lot) and came across the famous &lt;a href="https://lastpass.com/index.php">LastPass&lt;/a>. Reading through the feature list, the free version seemed to cover most of my needs and the incredibly cheap premium version would cover the rest (mobile support). Best of all, LastPass was cross platform, since it is only a web service and browser extension.&lt;/p>
&lt;p>I signed up to LastPass and installed its extension to my (at the time) browser of choice, Chrome (now Firefox). I was initially quite impressed. Setup was simple. LastPass offered to import my passwords from 1Password (though this did require me to export the passwords to a plaintext format) and even scanned them to tell me if there were any duplicate passwords or other potential security risks. Functionality wise, LastPass was incredibly impressive. It wasn&amp;rsquo;t anywhere near as aesthetically pleasing as 1Password but it had far more functions. After installing LastPass on my Mac and Linux boxes I carried on as normal, with the intention of replacing 1Password and purchasing a premium subscription to it after a few days. Within a few hours though, I began to become concerned about my security with the service though.&lt;/p>
&lt;p>Typically, peoples concern with LastPass&amp;rsquo; security comes from the fact that the service stores all their passwords on a server (for synchronisation). This wasn&amp;rsquo;t my concern as, before signing up, I&amp;rsquo;d done extensive research into the service&amp;rsquo;s security. LastPass&amp;rsquo; password storage is &lt;em>very&lt;/em> secure. LastPass can&amp;rsquo;t access your passwords, they can only see the encrypted file. The passwords themselves are only decrypted on your computer. I wasn&amp;rsquo;t concerned about somebody accessing my passwords remotely. I was concerned about someone getting them from my computer.&lt;/p>
&lt;p>The LastPass extension works like this. You login to your LastPass account and all of your passwords are synchronised to your computer. When you visit a site, the extension offers to autofill the password for you. If you allow it to, it will continue to do so in the future. 1Password has a similar extension. With 1Password, you visit a site and press a key combination. Up pops a window asking you to unlock your password with a master password. The extension then fills in the username &amp;amp; password and will optionally submit the login for you. When you revisit the website again, you must do the same action. At first glance, 1Password&amp;rsquo;s method of autofilling logins seems to be slow(er), requiring several steps. LastPass is far more simple, automatically filling in the form when you visit a site. You don&amp;rsquo;t need to log back in or unlock your passwords. LastPass just assumes that it&amp;rsquo;s you using the computer and not, say, someone who&amp;rsquo;s just stolen your computer. That&amp;rsquo;s my security concern with LastPass. By default, &lt;strong>you&amp;rsquo;re never logged out of the extension.&lt;/strong> That&amp;rsquo;s one hell of a major security flaw. Even worse is the fact that it automatically fills in passwords for you. With 1Password, even if your password vault is still unlocked (it doesn&amp;rsquo;t actually lock until after 5 minutes; you tell it; or some event happens like closing the laptop&amp;rsquo;s lid), you have a small layer of security in terms of a key combination to fill in the password. Your common laptop thief isn&amp;rsquo;t going to know what buttons to press to activate the 1Password extension. With LastPass, they don&amp;rsquo;t need to know any buttons. It&amp;rsquo;ll do it for them.&lt;/p>
&lt;p>Again, in the interest of fairness, LastPass does provide two options in its settings menu to&lt;/p>
&lt;ol>
&lt;li>Automatically logoff when all browsers are closed for x minutes.&lt;/li>
&lt;li>Automatically logoff when idle for x minutes.&lt;/li>
&lt;/ol>
&lt;p>The option&amp;rsquo;s there but I&amp;rsquo;m extremely concerned by the fact that these two options aren&amp;rsquo;t enabled by default. Enabling these options then throws up an annoying usability problem. When the LastPass extension logs you out automatically and you visit a site, the extension doesn&amp;rsquo;t prompt you to login to autofill the password. This can result in a bit of head scratching as you try and work out why the extension isn&amp;rsquo;t filling in your passwords. 1Password has this problem too, but it&amp;rsquo;s alleviated slightly by the fact that the key combination to autofill passwords becomes a reflex action and so you ultimately end up being prompted for your password anyway.&lt;/p>
&lt;p>That&amp;rsquo;s my main problem with the LastPass extension. While this was annoying, it was fixable and so I continued using the service and, ultimately, ended up signing up for the premium service solely for the ability to access my passwords on mobile devices, in my case, an iPhone. This is where the service became sickeningly bad.&lt;/p>
&lt;p>A years subscription to LastPass premium cost £7.49 ($12) which, in terms of coffee pay is maybe 3 and a bit coffees? Either way, it&amp;rsquo;s not going to break the bank. I downloaded the iPhone(/iPad) app and went through the setup. As expected, the UI was pretty rough. I&amp;rsquo;d come to expect this, given LastPass&amp;rsquo; track record with the browser extensions. After logging in, you&amp;rsquo;re presented with a list of your sites according to their groups. If you haven&amp;rsquo;t set up any groups, you&amp;rsquo;ll just get a list of sites under a group called “none”. Tapping on a site presents a modal list of options including the ability to copy a password or username to the clipboard. Counter intuitively, the button to cancel the list is at the top, whereas it&amp;rsquo;s normally at the bottom in iOS. This is just one of the many user interface and design flaws in the application. The most frustrating of these flaws is the option (or lack thereof) to logout on close.&lt;/p>
&lt;p>The mobile application, like its desktop counterpart, likes to keep you logged in for as long as it can. If you close the app so that it enters the background, you stay logged in until the application is killed full stop. As soon as the application is killed, everything&amp;rsquo;s nice and secure. Digging around in the settings, there&amp;rsquo;s an option called “log out on close”. Logic foretells that enabling this will log you out of the application when you close the application, even if it&amp;rsquo;s still running in the background. Oh no. It does nothing. At all. At least, as far as I can tell it does nothing. The app continues to keep you logged in until you kill the application. This is a &lt;strong>major&lt;/strong> security flaw, especially on a mobile device. Having to remember to logout of a mobile application is just stupid, especially one with as sensitive data as this. Mr iThief could happily take my phone and, even if I haven&amp;rsquo;t launched LastPass in a while, it could still be running in the background and Mr iThief could easily get my passwords.&lt;/p>
&lt;p>Again, In the interest of fairness, 1Password (Touch) does a similar thing and can stay logged in indefinitely as long as it&amp;rsquo;s running in the background. Whether it does this by default I can&amp;rsquo;t remember but it does provide some fairly extensive options to configure auto locking. Currently, my copy of 1Password requires a pin as soon as it is enters the background, requiring the master password after 5 minutes of inactivity. I think this is pretty secure and works nicely.&lt;/p>
&lt;p>Thanks largely to the terrible iOS application (it has so many problems), I&amp;rsquo;ve stopped using LastPass for now and switched back over to 1Password. I&amp;rsquo;ve also sent a message to LastPass asking for a refund, I &lt;em>should&lt;/em> get one. It&amp;rsquo;s going to be annoying on my Linux machines trying to use 1Password but, in terms of design and local security, 1Password feels a lot safer since it doesn&amp;rsquo;t leave me logged in for indefinite periods of time. LastPass is an excellent product assuming you only use the browser extensions with automatic log out enabled. Otherwise, despite the excellent security LastPass takes in storing your passwords, it fails miserably at keeping your passwords safe on your devices, especially mobile ones.&lt;/p>
&lt;p>&lt;strong>Update&lt;/strong> Within minutes of writing this post and a few hours of requesting a refund, I&amp;rsquo;ve received an email from LastPass confirming a refund for the premium service. Ultimately, no harm done.&lt;/p>
&lt;p>&lt;strong>Update 2&lt;/strong> The other night (14/02/12), I got two emails from Joe at LastPass in response to the issues I raised in this post. Here&amp;rsquo;s what he had to say:&lt;/p>
&lt;h3 id="email-1">Email 1&lt;/h3>
&lt;blockquote>
&lt;p>For windows we force you to choose if you want to be logged out or stay logged in, the fact that we don&amp;rsquo;t have this option on OS X / Linux prompting you by default is something we&amp;rsquo;ll rectify.&lt;/p>
&lt;p>For iOS, we have the option to logout on close, and to pin prompt if you&amp;rsquo;ve left LastPass logged in the background  &amp;ndash; did you miss this in the &amp;lsquo;more&amp;rsquo; menu?&lt;/p>
&lt;p>Best,&lt;/p>
&lt;p>Joe&lt;/p>
&lt;/blockquote>
&lt;h3 id="email-2">Email 2&lt;/h3>
&lt;blockquote>
&lt;p>I should also mention that you might want to try LastPass Icon -&amp;gt; Preferences -&amp;gt; whatever 1password key combo for fill in next site to get that behavior.&lt;/p>
&lt;p>Joe&lt;/p>
&lt;/blockquote>
&lt;p>Nice to see a response from someone at LastPass! So it would seem I missed a setting in the iOS application. Apparently, enabling pin prompt in LastPass (along with logout on close) will solve my security concern on the iOS app. I didn&amp;rsquo;t realise that both of these settings had to be enabled, the app didn&amp;rsquo;t (but should) point this out. I can&amp;rsquo;t actually verify that this would solve my security concern since I don&amp;rsquo;t have a premium subscription (and hence access to the mobile application) anymore but I trust the guys and girls at LastPass and don&amp;rsquo;t see why they&amp;rsquo;d lie.&lt;/p>
&lt;p>I&amp;rsquo;m pleased to see that LastPass are aware of the issue of remaining logged in indefinitely to the browser extension on Mac OS X (and, it would seem, Linux). Judging by the email, the Windows version asks the user if they should remain logged in during setup, which I think is a pretty good implementation as I know it would annoy some users if they where being logged out of the extension all the time.&lt;/p>
&lt;p>Finally, in response to using a keyboard combination to fill in site login info, while using a custom keyboard combo is handy, LastPass continues to offer to autofill passwords after enabling the keyboard shortcuts (as expected). After experimenting a bit, I&amp;rsquo;ve found that, in order to get the extra layer of “security through obscurity” that the 1Password extension offers, it&amp;rsquo;s necessary to change a few other preferences. You must also disable the “Automatically Fill Login Information” preference (LastPass Prefs &amp;gt; General) and the “Show Fill Notifications” preference (LastPass Prefs &amp;gt; Notifications). This way, LastPass wont throw up an alert telling you that it can autofill a login form and it wont attempt to automatically fill a login form either. The only way that the form can be filled is by you explicitly telling the extension to do so, hopefully via a slightly obscure shortcut that someone couldn&amp;rsquo;t guess.&lt;/p>
&lt;p>In light of this information, I&amp;rsquo;m going to reconsider using LastPass since it really is a fantastic service and cross platform compatibility is &lt;em>so&lt;/em> useful to me. It would just seem that LastPass requires some tweaking in order to get its local security to levels which I feel comfortable with.&lt;/p></description></item><item><title>SymSteam</title><link>https://alexj.org/02/symsteam/</link><pubDate>Sun, 05 Feb 2012 00:00:00 +0000</pubDate><guid>https://alexj.org/02/symsteam/</guid><description>&lt;p>I&amp;rsquo;ve just released a new project on GitHub. It&amp;rsquo;s called &lt;a href="https://github.com/alexjohnj/symsteam">SymSteam&lt;/a> and it&amp;rsquo;s for anybody who likes to play Steam games on their MacBook Air (or any other Mac for that matter). MacBook Airs are reasonably powerful machines, not gaming machines but with the Intel HD graphics inside of them they can handle many of the games on OS X without too much difficulty. The problem is, games are big and SSDs are small. The baseline MacBook Air only has a 64GB SSD and, while the top of the line model has a 256GB SSD, that still isn’t a lot of space for your games once you’ve got all of your other stuff on there too. The solution to this storage problem that most people use is to put their games on an external hard drive and create a symbolic link to them where Steam would expect to find them. This way, you can play games off of your external hard drive when you’ve got the hard drive with you. And there’s the problem, it’s sort of an all or nothing solution. You can either sacrifice some portability, convenience and speed and have all of your games on an external hard drive or you can have your games on an internal hard drive, just not that many. What if you want to play a quick game of Plants vs Zombies while you’re out but don’t have your hard drive with you? That’s not a huge game, it’d only take up maybe 100MB on your hard drive. That game would be fine on your internal hard drive but if you choose to put all your games on an external hard drive you won’t be able to play it off of your internal drive without doing a bit of messing around with folders. This is where SymSteam comes in.&lt;/p>
&lt;h2 id="enter-symsteam">Enter SymSteam&lt;/h2>
&lt;p>SymSteam runs in the background. It doesn&amp;rsquo;t have a dock icon or a menu bar icon. It doesn&amp;rsquo;t have any windows apart from a setup window. It&amp;rsquo;s not going to get in your way. SymSteam watches for when you plug in a USB storage device. It then scans said device to see if it can find a SteamApps folder. If it doesn&amp;rsquo;t, nothing happens. If it does though, it will configure your Steam folder so that Steam will load games off of the device you just plugged in. Unplug the device and you’re playing games off of your internal hard drive. It’s seamless, when it works.&lt;/p>
&lt;p>SymSteam is really immature at the moment. It works, but only just. I&amp;rsquo;m running it on my system because it&amp;rsquo;s handy and I&amp;rsquo;ll continue to work on it for now. Currently, I want to get some sort of preferences window and Growl notifications implemented. There&amp;rsquo;s also a lot of bugs that need fixing.&lt;/p>
&lt;p>If you&amp;rsquo;re a developer, or want the latest, cutting edge build, you can get SymSteam from its GitHub repository &lt;a href="https://github.com/alexjohnj/symsteam">here&lt;/a>.&lt;/p>
&lt;p>If you&amp;rsquo;re just interested in downloading a ready to use, reasonably/partly/just about stable version, get version 0.1 from &lt;a href="https://github.com/alexjohnj/symsteam/downloads">here&lt;/a>.&lt;/p></description></item></channel></rss>