• Node.js and launchd - updated! Now with terminal-notifier

    I thought it would be nice to pump the backup notifications I talked about in this post to the OSX notification system. It turns out that this isn’t quite as simple as you’d hope but it’s not too bad.

    You need 2 things the terminal-notifier command line tool and node-terminal-notifier.

    First you install the terminal-notifier tool by typing this command in the terminal: $ [sudo] gem install terminal-notifier

    On my Mac, Ruby was already installed which was handy.

    Then you: npm install terminal-notifier

    Then in your node script: var notifier = require('terminal-notifier');

    and: notifier('This is the message.', { title: 'This is the title' });

    Sweet!

  • Node.js and launchd

    I use Jungledisk to do daily backups for my wife’s business. Jungledisk helpfully notifies me if the job succeeded or failed. But a while ago, something happened and the jobs just stopped happening altogether. They weren’t failing, they were just never starting so I wasn’t getting notified. This was really bad because I didn’t notice for a couple of weeks. I realized I needed a way to be notified when something didn’t happen which is a more difficult problem than being notified when something did happen.

    In trying to figure out how to solve this problem, I noticed that Jungledisk can publish an RSS feed of my backup history. So I realized I could just write a script to go out and get that RSS feed, parse it, and check if my backup job completed successfully in the last 24 hours and notify me if it has not.

    My first thought was to write the script in Python but then I would have had two problems because I don’t know Python all that well. Then I realized that I could write it in Javascript and use node.js! For notification, I’m using a cool service called Pushover which pushes notifications to my android phone.

    So I whipped up a node script (see below) and scheduled it with Windows Task Scheduler and we’re rocking!

    'use strict';
    
    var http = require('http'),
    	xml2js = require('xml2js'),
    	dateUtils = require('date-utils'),
    	pushover = require('node-pushover');
    
    var jobs = [ 'this is an array of the names of the jobs in which you are interested' ];
    
    main();
    
    
    function main() {
    	var options = {
    	  host: 'backupreporting.jungledisk.com',
    	  port: 80,
    	  path: '/feeds/backupreporting.ashx?format=rss20&guid=yourguidhere',
    	  method: 'GET'
    	};
    
    	var req = http.request(options, function(res) {
    	  //console.log('STATUS: ' + res.statusCode);
    	  res.setEncoding('utf8');
    
    	  var fullResponse = '';
    
    	  res.on('data', function (chunk) {
    	  	fullResponse = fullResponse + chunk;  	
    	  });
    
    	  res.on('end', function () {  	
    	    var parser = new xml2js.Parser();
    	    parser.parseString(fullResponse, parseRssCallback);
    	  });
    	});
    
    
    	req.on('error', function(e) {
    	  console.log('problem with request: ' + e.message);
    	});
    
    	req.end();
    }
    
    function parseRssCallback (err, result) {
    	try {
    		//console.dir(result.rss.channel[0]);
    		var now = new Date();	
    		var title, pubDate;
    
    		//filter to ones that are COMPLETE and have a pubDate in the last 24 hours
    		var filtered = result.rss.channel[0].item.filter(function(item) {
    			pubDate = new Date(item.pubDate[0]);
    			title = item.title[0];		
    
    			return title.indexOf('COMPLETE') > -1
    				&& pubDate.getHoursBetween(now) < 24
    				&& jobInJobsList(title);
    		});
    
    		if (!listContainsEachJob(filtered)) {
    			sendNotification('Backup Alert', 'Some backup jobs did not complete!');
    		}
    	} 
    	catch (e) {
    		sendNotification('Backup Alert', 'An error ocurred checking backup status!');
    	}
    }
    
    function listContainsEachJob (list) {
    	//make sure the list has at least one entry for each job we care about
    	var titles = list.map(function(item) {
    		return item.title[0];
    	});
    
    	var result = true;
    	jobs.forEach(function(job) {
    		result = result && titles.some(function (title) {
    			return title.indexOf(job) > -1;
    		});
    	});
    
    	return result;
    }
    
    function jobInJobsList (title) {	
    	//check if the title is in the jobslist
    	return jobs.some(function(job) {
    		return title.indexOf(job) > -1
    	});
    }
    
    function sendNotification (title, message) {
    	pushover.send({  
    	    token: 'your token here',
    	    user: 'your user here',
    	    title: title,
    	    message: message
    	}, function(err, response){
    	    if (err){
    	        //console.error("Error sending pushover");
    	        //console.error(err);
    	    }else{
    	        //console.log("Sending pushover complete");
    	        //console.log(response);
    	    }
    	});	
    }
    

    But then I got a MacBook Air, which is awesome; but no more Windows Task Scheduler. It seems that the way to schedule this sort of thing on a Mac is using launchd. To schedule a task with launchd, you create a .plist file which is an XML file specifying the parameters of your job. This is documented elsewhere but notice that to run a node script, you can just specify an array of 2 arguments for ProgramArguments. The first is node, the second is the script you want to run. The .plist is below:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.mikejuniper.backupcheck</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/local/bin/node</string>
            <string>/Users/mjuniper/projects/scripts/backupcheck.js</string>
        </array>
        <key>StartCalendarInterval</key>
        <dict>
            <key>Hour</key>
            <integer>6</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
    </dict>
    </plist>
    

    I’m very new to node and Macs so I’d be very interested in any thoughts anybody’s got on this.

  • Singletracker.net - the technology

    Just a brief post to quickly call out the technologies I’m using in Singletracker.net. In the coming weeks, I’ll try to put up some more posts that get more into the details.

    For starters, It’s running in Windows Azure which so far has been really easy to deal with but is going to get too expensive once my 90 day free trial is up.

    On the back end it’s Asp.NET MVC 4 with WebAPI and Entity Framework 5. For authentication, I’m using OpenID with Facebook and Google. I plan to add Yahoo and Microsoft soon. If people actually use this, I will probably re-build the back end with node.js, express, and MongoDB.

    On the front end, it’s Twitter Bootstrap for the responsive layout and other goodness. I’m using Font Awesome for the icons throughout - I think there is probably not a single image (except the map tiles of course). For the UI, I’m using Backbone.js and Leaflet.js with OpenCycleMap’s tiles.

  • Introducing Singletracker - crowd-sourced trail conditions for mountain bikers

    So a few weeks ago I wanted to go mountain biking. I wasn’t sure the trails would be fit to ride on so I went to the websites of the various trails looking for current trail condition info. It was either non-existent or hopelessly out of date.

    So I got to thinking, it would be great to have a crowd sourced trail conditions web app. So I built it!

    It’s a fairly responsive design - intended to be used on mobile phones (recent versions of iOS and Android) but it should work very well on desktop browsers and tablets. Note that it is intended to be used on modern devices/ browsers - I give ielt9 the Heisman.

    V1 is now live. I’m representing the trails as points (think of them as the trail-head) because it’s far easier for a whole bunch of reasons. I’m thinking about ways to handle them as polylines but that’s down the road a ways. I want to get it out there, try to get people using it, get their feedback and see where that takes us.

    The zen of it is:

    1. You land at the page and get a map centered on your location.
    2. If there are any trails that have been entered nearby, they will appear as points with different symbols for good, fair, poor, or unknown condition.
    3. You can click/ tap on a point to get a popup containing a bit more information and a button that takes you to the trail details page.
    4. You can also click 'List' in the menu to get a list of all the trails in the system sorted by distance from your location.
    5. If you click/tap an item in the list, you go to the trail details page.
    6. On the trail details page there is info about the trail and its condition as well as buttons to: update the trail condition, edit the trail info, or view the trail on the map.

    Check it out singletracker.net.

  • Diving in to Backbone.js

    We’ve known for a while that there are a number of javascript mvc frameworks out there and that many of the apps we build would likely benefit greatly from using one. Bet we’ve held back mostly because of the learning curve/ time constraints but also partly because it wasn’t clear which one was gonna be ‘best’ for our needs. Well, we’ve got a particular project on our plate right now that just begs for a framework like this so we decided to take the plunge. We settled on Backbone.js mostly because it seems like there are lots of super smart people who are using it (and they still like it!) and also because there seem to be lots of resources available for it (not least of which is the very good documentation).

    I don’t really have the time to do a proper Intro to Backbone post but fortunately many others have done so. Some are better than others though, so what I thought I’d do is share some of the resources I found most helpful and mention a few of the additional libraries we’re using.

    It’s true that the learning curve is somewhat steep but once you dive in, it’s not really that bad. A good place to start is http://backbonetutorials.com/ which provides a brief introduction to the various backbone components. A very easy to understand and quite good hello world example is at http://arturadib.com/hello-backbonejs/docs/1.html. This one is cool because it starts very simply and progressively adds complexity.

    Rob Conery wrote a two part post discussing some of the problems he saw with many Backbone tutorials. I was really happy to see this because I shared some of his objections.

    One of the things I missed when I first started this project was model binding. Then I found this awesome plugin for Backbone which provides two-way, convention-based model binding. This was ridiculously easy to use and it allowed me to slim down my views considerably.

    One issue I had was that the particular needs of our app called for messaging between various components other than just a model and its view. For instance, I needed my Results view to know when my Criteria model changed so it could update itself with new data based on the Criteria model that changed; but other than that, the Results view needed no knowledge of the Criteria model. I initially used a shared reference to the Criteria model but I didn’t love it because I wanted my components to be more loosely coupled. I now think that would have been fine but my dissatisfaction lead me to this post that suggests using the Event Aggregator pattern. It’s really very simple. Since I already had all my modules namespaced, I just put my event aggregator as a property on my namespace object and all my other modules have access to it:

    dts.eventAggregator = _.extend({}, Backbone.Events);
    

    Then you can just do <pre>dts.eventAggregator.bind(‘fooEvent’, fooEventHandler, this);</pre> and <pre>dts.eventAggregator.trigger(‘fooEvent’, {/event arguments/});</pre>

    I found this very good post discussing a good approach to avoiding memory leaks. One of the comments lead me to this StackOverflow post discussing the same issue. I think a combination of these two approaches would work well.

    Though I’m new to javascript mvc frameworks, I’ve been using javascript templating for a while now. I’ve mostly been using jQuery Templates which I like just fine, but I took this opportunity to try some other templating engines. I ended up settling on icanhaz.js because it uses the mustache.js template syntax (it actually uses mustache under the covers, I think) which I like very much and because it precompiles the templates into functions (thus improving performance, I assume) and hangs them right on the ich object with the function name corresponding to the id of the template (thus making it very easy to use). I’ll definitely be sticking with icanhaz going forward.

    Of course, one of the best things about the MVC pattern is that it lends itself quite well to testing. I found a very nice multi-part post on testing backbone applications - the first part is here. I had used qUnit before so I decided to stay with it to minimize the risk of my head exploding from all the new stuff (though I was tempted to give Jasmine a whirl). The post above lead me to sinon.js (and its qUnit adapter, sinon-qunit) which is a really great little library that provides test spies, stubs, and mocks. Unit testing this stuff was fun and it actually helped me uncover some bugs that I didn’t notice in my ui testing.

    I’ve learned a ton this week, which is always fun, and I’m looking forward to building more applications with Backbone.js.