Twilio SMS Receive - Crazy Simple

Posted on February 10th, 2014

Twilio

If you’ve used Twilio to send or receive SMS messages before stop reading now, you already know how crazy simple it is.

Twilio is a cloud based service that enables you to programmatically make and receive phone calls and SMS messages using their simple API. We’ll see how to receive and send SMS messages using Twilio and a simple Django app.

Before you can start receiving SMS messages, you have to create a Twilio account. You can start with a limited version of the service for free, go to https://www.twilio.com/try-twilio. The free service will get you one phone number and any SMS messages sent will be prefixed with some promotional text inserted by Twilio. Additionally the free level limits the number of SMS messages that be sent and received.

Receiving and sending SMS Messages

SMS messages can be retrieved in a pull fashion via the SMS API, but for this example we’ll opt to have Twilio push any text messages sent to our number to our app. You can associate the Twilio phone number to a url to which Twilio will do a http POST.

We’ll be using the Twilio twilio python library. It is available via pypi and therefore can be installed via “pip install twilio” or “easy_install twilio”. This is the only python dependency.

Let’s get right to it. Here’s the Django view code:

import abc

from django.conf import settings
from django.core.exceptions import ValidationError
from django.http import HttpResponse, HttpResponseForbidden
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import View
from twilio import twiml
    from twilio.util import RequestValidator

from dvapp import models

class TwilioSmsView(View):
    __metaclass__ = abc.ABCMeta

    @staticmethod
    def _validate_request(request):
        """
        Make sure the request is from Twilio and is valid.
        Ref: https://www.twilio.com/docs/security#validating-requests
        """
        if 'HTTP_X_TWILIO_SIGNATURE' not in request.META:
            return 'X_TWILIO_SIGNATURE header is missing ‘ \
                   'from request, not a valid Twilio request.'

        validator = RequestValidator(TWILIO_AUTH_TOKEN)

        if not validator.validate(
            request.build_absolute_uri(), 
            request.POST, 
            request.META['HTTP_X_TWILIO_SIGNATURE']):

            return 'Twilio request is not valid.’


    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        bad_request_message = self._validate_request(request)
        if bad_request_message:
            logger.warn(bad_request_message)
            return HttpResponseForbidden()
        return super(TwilioSmsView, self).dispatch(
            request, *args, **kwargs)


    def post(self, request, *args, **kwargs):
        r = twiml.Response()
        # Twilio should put the message in the Body param.
        r.message(msg=self.get_response_message(
            request.POST.get('Body'), *args, **kwargs))
        return HttpResponse(r.toxml(), 
                            content_type='application/xml')

    @abc.abstractmethod
    def get_response_message(self, request):
        raise NotImplementedError()


class VerificationMessageView(TwilioSmsView):

    def get_response_message(self, message, *args, **kwargs):
        verification_request = models.VerificationRequest()
        verification_request.barcode = message
        verification_request.username = 'SMS'
        verification_request.verify()

        try:
            verification_request.full_clean()
        except ValidationError as e:
            logger.info('Validation error(s) creating a ‘
                        'verification request ‘
                        'via Twilio SMS: {}'.format(e))
            if 'barcode' in e.error_dict:
                return message + ' is not a valid barcode ‘ \
                                 'and could not be checked.'

        verification_request.save()

        # return some string which Twilio 
        # will send back to the originating number
        return verification_request.get_result_message()

The first class TwilioSmsView is used as a base class for any class based view whose response will be sent to the originating phone number via Twilio. It takes care of a few things:

  1. It makes sure that the incoming message is a valid request from Twilio and has not been tampered with. The _validate_request method makes use of the twilio RequestValidator class to compare a cryptographic hash against one sent as a header parameter.
  2. It bypasses csrf checking, notice "@method_decorator(csrf_exempt)". Text messages are inherently stateless. Twilio will not send the expected csrf token as a session-aware http request would.
  3. It extracts the text message sent from the origin number from the ‘Body’ request param.
  4. It wraps the plain text response provided by the subclass in Twiml, Twilio’s xml. Twiml is interpreted by Twilio in this case to send a message back to the origin phone number. This is where the twilio library comes in, we’re using it’s simple wrapper around Twiml. We could have generated the XML ourselves, it’s not a complicated schema.

Finally we need to wire up the url to our view. This is standard Django url.py code:

sms_patterns = patterns(‘', url(r'sms/verify/', sms_views.VerificationMessageView.as_view(), name='sms-verify'))

Note that Twilio will send all sorts of headers with the request in addition to the text message.

Crazy simple eh?


"I Didn't Know" #2

Posted on January 20th, 2014

I found out about “git add -p” today. Too often my git commits are too large, incorporating more than one change. git add -p (patch mode) lets you add some of the changes in a file and not others. John Kary has an excellent set of videos on git add -p.

http://johnkary.net/blog/git-add-p-the-most-powerful-git-feature-youre-not-using-yet/

I wish I knew about this a long time ago.


Android Data Sync

Posted on December 31st, 2013

If you have an Android app that 1) reads and/or writes data from a SQLite database and 2) needs to update that data periodically from another source, say a RESTful web service then one approach you can take is to hook into the Android Sync Service. I recently created an Android project with a Sync Adapter to consume and publish data to a simple Go RESTful web service and I’d like to share what I’ve learned.

Android source: https://github.com/snyderep/TeamKarmaAndroid
Go source: https://github.com/snyderep/TeamKarmaServer

Relevant parts of the Android project:

  1. Content Provider
  2. Authenticator and Authenticator Service
  3. Sync Adapter and Sync Service

Content Provider

Content providers are the standard Android way of providing access to data in one process from another process. I didn’t need a provider since I did not intend to share data with other applications, but I decided that the provider abstraction was a nice clean one and it was worth using regardless. While a content provider isn’t strictly necessary for use within a sync adapter, I felt it better to do so. A provider is required for the app to provide custom search suggestions which I may incorporate at a later time.

Android docs on content providers: http://developer.android.com/guide/topics/providers/content-providers.html

The easiest way to create a Content Provider is to extend android.content.ContentProvider and fill in the relevant CRUD methods: query, insert, update and delete as well as getType(). Content providers specify data types (tables) via URIs. For example the URI “content://com.sportsteamkarma.provider/team” may represent a list of sports teams while “content://com.sportsteamkarma.provider/team/1” may represent a specific team. getType returns the MIME type associated with a content URI.

Example partial content provider:

public class DataContentProvider extends ContentProvider {
  private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

  private static final int TEAM_ID = 6;

  private static final String AUTHORITY = "com.sportsteamkarma.provider";

  static {
    uriMatcher.addURI(AUTHORITY, "league/#/team/#", TEAM_ID);
  }

  private DbHelper dbHelper;

  @Override
  public boolean onCreate() {
    dbHelper = new DbHelper(getContext());
    return true;
  }

  @Override
  public Cursor query(Uri uri, String[] columns, String selection, 
                      String[] selectionArgs, String sortOrder) {

    List segments;
    segments = uri.getPathSegments();

    SQLiteDatabase db = dbHelper.getWritableDatabase();

    switch (uriMatcher.match(uri)) {
      case TEAM_ID:
        return db.query(
          DataContract.Team.TABLE_NAME, 
          columns,
          buildSelection(
            DataContract.Team.COLUMN_NAME_LEAGUE_ID + "=? AND “ +
            DataContract.Team._ID + "=?", selection),
          buildSelectionArgs(
            new String[] {segments.get(1), segments.get(3)}, 
            selectionArgs), 
          null, null, sortOrder);
        default:
          throw new RuntimeException("No content provider URI match.");
    }
  }

  @Override
  public String getType(Uri uri) {
    switch (uriMatcher.match(uri)) {
      case TEAM_ID:
        return "vnd.android.cursor.item/vnd.com.sportsteamkarma.provider.team";
      default:
        throw new RuntimeException("No content provider URI match.");
    }
  }

  private String buildSelection(String baseSelection, String selection) {
    if (TextUtils.isEmpty(selection)) {
        return baseSelection;
    }
    return TextUtils.concat(baseSelection, 
                            " AND (“,
                            selection, ")").toString();
  }

  private String[] buildSelectionArgs(String[] baseArgs, 
                                      String[] selectionArgs) {
    if (selectionArgs == null || selectionArgs.length == 0) {
        return baseArgs;
    }
    return ArrayUtils.addAll(baseArgs, selectionArgs);
  }

  ...
}

A content provider must also be registered in the manifest. A snippet like this one must be added within the application element. Apologies if the xml below is funky. postach.io’s Markdown support seems a bit flaky.

< provider
    android:name=".data.provider.DataContentProvider"
    android:authorities="com.sportsteamkarma.provider"
    android:exported="false"
    android:syncable=“true">
< /provider>

Authenticator and Authenticator Service

The sync framework requires an Authenticator. An Authenticator plugs into the Android accounts and authentication framework. Unfortunately even if an app doesn’t use accounts, as my app does not yet use accounts, an authenticator component is still required. Fortunately it’s not difficult to provide a no-op authenticator component, also called a Stub Authenticator in the Android docs.

Android docs on creating a Stub Authenticator: http://developer.android.com/training/sync-adapters/creating-authenticator.html

The authenticator service is necessary for the sync framework to access the authenticator. Here is a sample authenticator service:

public class AuthenticatorService extends Service {
  private Authenticator authenticator;

  @Override
  public void onCreate() {
    authenticator = new Authenticator(this);
  }

  /*
   * When the system binds to this Service to make the RPC call 
   * return the authenticator’s IBinder.
   */
  @Override
  public IBinder onBind(Intent intent) {
    return authenticator.getIBinder();
  }
}

Android development generally requires a fair amount of xml to wire the bits and pieces together. This is no exception, we need xml for the authenticator metadata and we need to point to that metadata in the manifest as we register the authenticator service.

The authenticator metadata:

< ?xml version="1.0" encoding="utf-8"?>
< account-authenticator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.sportsteamkarma"
    android:icon="@drawable/ic_launcher"
    android:smallIcon="@drawable/ic_launcher"
    android:label="@string/app_name”/>

Even if the app does not use accounts, accountType is required and should be a domain that is under your control.

And the addition of the authenticator service to the app manifest, the service element belongs within the application:

< service
    android:name="com.sportsteamkarma.service.datasync.AuthenticatorService">
    < intent-filter>
      < action android:name="android.accounts.AccountAuthenticator"/>
    < /intent-filter>
    < meta-data
      android:name="android.accounts.AccountAuthenticator"
      android:resource="@xml/authenticator" />
< /service>

Android will trigger the intent action android.accounts.AccountAuthenticator which will cause the AuthenticatorService to be started.

Sync Adapter and Sync Service

Now for the fun part. 2 more major pieces are needed, the sync adapter which does the work of syncing the data between the server and the local database and the sync service which is the service that ties the sync adapter into the Android sync framework.

Android docs for creating a sync adapter: http://developer.android.com/training/sync-adapters/creating-sync-adapter.html

When a sync happens the sync adapter’s onPerformSync method is called by the framework and within the scope of that method anything that is permitted by the framework can be done to sync the data. In the case of my app I built a simple REST api in Go and standard calls are made to retrieve and parse JSON. Alternatives include making SOAP calls or using web sockets or downloading flat files. Once the data is retrieved from the external source, persisting it is done via a content provider client, this is where the content provider comes into play. Again, a content provider isn’t strictly necessary, within the sync adapter you can certainly open and update a local database directly.

Here’s a sync adapter example:

public class SyncAdapter extends AbstractThreadedSyncAdapter {
  private static final String AUTHORITY = "com.sportsteamkarma.provider";
  private static final String PREFIX = "content://" + AUTHORITY + "/";

  public SyncAdapter(Context context, boolean autoInitialize) {
    super(context, autoInitialize);
  }

  @Override
  public void onPerformSync(Account account, Bundle extras, String authority,
    ContentProviderClient contentProviderClient, SyncResult syncResult) {

    // naive implementation, delete and replace everything

    SyncResult result = new SyncResult();

    try {
        deleteSports(contentProviderClient);
        insertSports(contentProviderClient);
    } catch (RemoteException | IOException e) {
        syncResult.hasHardError();
    }
  }

  private void deleteSports(ContentProviderClient contentProviderClient) 
    throws RemoteException {

    // Execute a query against the content provider.     
    Cursor cursor = contentProviderClient.query(
      // The URI “content://com.sportsteamkarma.provider/sport”
      // will be recognized by the content provider.
      Uri.parse(PREFIX + "/sport”),
      // specify that we only want the _id column.
      new String[] {DataContract.Sport._ID}, null, null, null);

    if (cursor.moveToFirst()) {
        do {
            long sportId = cursor.getLong(0);
            contentProviderClient.delete(
              Uri.parse(PREFIX + "/sport/" + sportId), 
              null, null);              
        } while (cursor.moveToNext());
    }
  }

  /**
   * Fetch data from the remote server and populate the local database 
   * via the content provider.
   */
  private void insertSports(ContentProviderClient contentProviderClient) 
    throws RemoteException, IOException {

    URL url = new URL("http", "192.168.117.16", 3000, "/api/sports");
    URLConnection conn = url.openConnection();

    try (
      BufferedReader bufReader = new BufferedReader(
        new InputStreamReader(conn.getInputStream(), "UTF-8”)
      );
      JsonReader reader = new JsonReader(bufReader)
    ) {
      reader.beginArray();

      Long id = null;
      String sportName = null;

      while (reader.hasNext()) {
        reader.beginObject();

        while (reader.hasNext()) {
          String name = reader.nextName();
          if (name.equals("id")) {
            id = reader.nextLong();
          } else if (name.equals("name")) {
            sportName = reader.nextString();
          }
        }

        reader.endObject();

        ContentValues contentValues = new ContentValues();
        contentValues.put(
          DataContract.Sport.COLUMN_NAME_SPORT_NAME, sportName);
        contentProviderClient.insert(
          Uri.parse(PREFIX + "/sport/" + id), contentValues);
      }

      reader.endArray();
    }
  }
}

To be able to trigger the adapter from the Android sync framework a Service is needed:

public class SyncService extends Service {
  private static SyncAdapter syncAdapter = null;
  // Object to use as a thread-safe lock
  private static final Object syncAdapterLock = new Object();

  @Override
  public void onCreate() {
    super.onCreate();

    /*
     * Create the sync adapter as a singleton.
     * Set the sync adapter as syncable
     * Disallow parallel syncs
     */
    synchronized (syncAdapterLock) {
        if (syncAdapter == null) {
            syncAdapter = new SyncAdapter(getApplicationContext(), true);
        }
    }
  }

  /**
   * Return an object that allows the system to invoke
   * the sync adapter.
   */
  @Override
  public IBinder onBind(Intent intent) {
    /*
     * Get the object that allows external processes
     * to call onPerformSync(). The object is created
     * in the base class code when the SyncAdapter
     * constructors call super()
     */
    return syncAdapter.getSyncAdapterBinder();
  }
}

And of course more XML is needed. xml metadata is needed for the sync adapter and also the sync service must be registered in the manifest.

syncadapter.xml - belongs in res/xml:

< sync-adapter
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:contentAuthority="com.sportsteamkarma.provider"
    android:accountType="com.sportsteamkarma"
    android:userVisible="false"
    android:supportsUploading="false"
    android:allowParallelSyncs="false"
   android:isAlwaysSyncable="true"/>

Note that the accountType must match the authenticator.xml accountType.

The service entry in the app manifest (within the application element):

<service
  android:name="com.sportsteamkarma.service.datasync.SyncService"
  android:exported="true"
  android:process=":sync">
    < intent-filter>
         < action android:name="android.content.SyncAdapter"/>
    < /intent-filter>
    < meta-data
      android:name="android.content.SyncAdapter"
      android:resource="@xml/syncadapter" />
< /service>

Final Steps - Add Account & Start The Sync

In the scope of the application’s main activity an account must be added even if your app doesn’t use accounts. It’s a requirement of the sync framework. Fortunately a dummy account can be used. In my main activity in onCreate I have:

    // Create the account type and default account
    Account newAccount = new Account("dummyaccount", "com.sportsteamkarma");
    AccountManager accountManager = (AccountManager) this.getSystemService(ACCOUNT_SERVICE);
    // If the account already exists no harm is done but
    // a warning will be logged.
    accountManager.addAccountExplicitly(newAccount, null, null);

Finally, you can either let the sync happen periodically as Android deems it necessary or you can force a sync to happen.

To just enable the sync (not kick it off) call setSyncAutomatically on ContentResolver. An account is needed but it can be a dummy account.

ContentResolver.setSyncAutomatically(
  newAccount, "com.sportsteamkarma.provider", true);

To manually force a sync, call requestSync. The last parameter, the bundle of ‘extras’ will be passed through to the sync adapter. If there are any special conditions that the sync adapter needs to be aware of they should be passed as flags or strings here in a Bundle.

ContentResolver.requestSync(
  newAccount,"com.sportsteamkarma.provider", Bundle.EMPTY);

graphicsmagick Image Resizing

Posted on December 23rd, 2013

The Android runtime attempts to display images appropriate to the platform on which it is running. A developer can provide multiple versions of the same image with each version having a different original size. Regardless of the original image selected by the runtime, the image is ultimately rendered at the size specified by explicit or implicit constraints. This allows an app to render normal 1:1 images (“drawables”), retina-compatible 2:1 images, etc..

More information regarding Android support for varying screen sizes and densities can be found here

To scale images to different sizes I use graphicsmagick , a popular fork of the also popular imagemagick. The graphicsmagick site provides installation instructions, but if you have a mac then the easiest installation method by far is to use homebrew.

homebrew install graphicsmagick

Example of scaling an image, assume that the original image is larger and the image is being scaled down:

gm convert soccer_ball.png -resize 112x112 soccer_ball2.png

Example of changing the image background to a transparent background:

gm convert soccer_ball.png -background none soccer_ball2.png

Android project folder layout for resources:

Directory Density
src/main/res/drawable-hdpi High
src/main/res/drawable-mdpi Medium
src/main/res/drawable-xhdpi Extra High
src/main/res/drawable-xxhdpi Extra Extra High

"I Didn't Know" #1

Posted on December 18th, 2013

I didn’t know it was possible with curl to specify multiple files to download with one command. I probably did know at some point but forgot. That’s one benefit of aging, you can make the same happy little discoveries over and over again.

curl -O "https://az412801.vo.msecnd.net/vhd/IEKitV1_Final/VirtualBox/OSX/IE7_Vista/IE7.Vista.For.MacVirtualBox.part{1.sfx,2.rar,3.rar,4.rar,5.rar}”

This means you can do something like this:

$ for i in "file1.txt file2.txt file3.txt"
do
    curl -O http://address/${i}
done

Go Go Golang

Posted on December 9th, 2013

There are 2 programming languages that I am into at the moment, Clojure and Go (http://golang.org/). They seem to be opposites of each other in many ways, how can I love both? Clojure certainly wins on elegance with the ability to create pure functions, its homoiconic structure (Clojure programs are represented by Clojure data structures), its simplicity and clarity (http://www.drdobbs.com/architecture-and-design/the-clojure-philosophy/240150710#). Clojure is beautiful and parentheses are beautiful IMHO. So why do I love Go so much? It’s not what I would call beautiful.

Here is the equivalent Go and Clojure code taken from http://blog.drewolson.org/blog/2013/07/04/clojure-core-dot-async-and-go-a-code-comparison/:

package main

import (
  "fmt"
  "math/rand"
  "time"
)

func main() {
  for i := 0; i < 10; i++ {
    go func(i int) {
      sleep := time.Duration(rand.Intn(1000))
      time.Sleep(sleep * time.Millisecond)
      fmt.Println(i)
    }(i)
  }

  time.Sleep(2000 * time.Millisecond)
}


(ns async-example.core
  (:require [clojure.core.async :refer :all])
  (:gen-class))

(defn -main [& args]
  (doseq [i (range 10)]
    (go
      (Thread/sleep (rand-int 1000))
      (println i)))

  (Thread/sleep 2000))

Go looks like C. Old fashioned, some would say ugly C. This was my initial reaction to Go but something happened along the way. I found myself wanting to write Go, all the time. This must be the influence of my subconscious because consciously I should be wanting to write Clojure, after all functional programming just logically makes sense. Clojure is beautiful, the tools are great, the community is great, so why should I desire to write Go over Clojure (again note that writing Clojure is also great fun). So what about Go do I love?

I like type safety. I’ve written a lot of ruby and python and I've enjoyed both. I’m not a compiler zealot, I’ve been involved with huge, complex and very successful projects that use dynamically typed languages. However I have to admit that despite inconveniences and the occasional bit of clumsiness (go has int, int32 and int64 types for example), the compiler is great especially when you have a language and environment like go that is optimized for compilation speed (http://stackoverflow.com/questions/2976630/why-does-go-compile-so-quickly). It’s a big win when you can work at dynamic language repl speeds with a statically typed language.

I like Go’s coroutine based concurrency model. Clojure recently implemented a nearly identical model with core.async. I’m sure I’ll have fun with core.async when I get the chance to mess with it. See http://golang.org/doc/effective_go.html#concurrency. Here’s a bit of code that collects sensor and weather data and drops the combined data on a channel:

package main

import (
  "<a href="http://github.com/snyderep/chariot_sensor_data">github.com/snyderep/chariot_sensor_data</a>"
  "<a href="http://github.com/snyderep/weather">github.com/snyderep/weather</a>"
)

type combinedData struct {
  Measurements *chariot_sensor_data.MeasurementData
  Weather      *weather.WeatherData
}

func collectData(dataCh chan *combinedData) {
  // start collecting sensor data
  measurementsChan := make(chan *chariot_sensor_data.MeasurementData)
  go chariot_sensor_data.Listen(udpAddrS, measurementsChan)

  // start collecting weather data
  weatherChan := make(chan *weather.WeatherData)
  go weather.GetPeriodicWeatherData(&amp;weather.Point{40.134739, -75.205564}, weatherChan)

  var w *weather.WeatherData
  var latestWeather *weather.WeatherData
  var measurements *chariot_sensor_data.MeasurementData

  for {
    select {
    case measurements = &lt;-measurementsChan:
      dataCh &lt;- &amp;combinedData{measurements, latestWeather}
    case w = &lt;-weatherChan:
      latestWeather = w
    }
  }
}

Go manages to avoid the traps of other languages like Java that are primarily written in an imperative style. There’s no inheritance for example, Go favors composition and functions are first class objects.

Maybe I’ll start dreaming in functions, the above imperative style code will then seem awkward and wrong. For now though it seems clean (not my code necessarily, just go code in general), easy and natural.

If you haven’t geeked out yet with Go, why not have a go? Sorry :) Start here:

http://golang.org/
http://golang.org/doc/install
http://tour.golang.org/
http://golang.org/doc/effective_go.html


Cascalog

Posted on November 26th, 2013

For a while now I've been interested in functional programming, Clojure in particular but I haven't had the time or the opportunity to geek out with it. I've also done quite a bit with Hadoop. Now I've jumped right in with Cascalog, the natural intersection of Clojure and Haoop. Why start at the shallow end of the tech pool when you can dive right in at the deep end?

Cascalog is described on the Cascalog site as "Data processing on Hadoop without the hassle". Think of it as an alternative to Hive that works at a higher level. Basically you build queries in an expressive and composable way as one would expect with a Clojure library and you get scalability for free. Here is an example, my first ever Cascalog query (https://github.com/snyderep/cascalog-sensor-data/blob/master/src/chariot_sensor_data/core.clj):

(ns cascalog-sensor-data.core
  (require [cascalog.logic.ops :as c])
  (use [cascalog.api]
       [cascalog.more-taps :only (hfs-delimited)]))

(def sensorinfo-tap
  (hfs-delimited "/Users/eric/Documents/motedata_2013_10_28.csv"
                 :delimiter ","
                 :classes [Float Integer Float Float Integer Integer Float]
                 :skip-header? true))

(defn print-mote-counts
  []
  (?- (stdout)
    (&lt;- [?mote ?count ?max_humidity ?max_temperature]
      (sensorinfo-tap :> ?timestamp ?mote ?humidity ?temperature ?pirValue ?motionState ?micValue)
      (c/count :> ?count)
      (c/max ?humidity :> ?max_humidity)
      (c/max ?temperature :> ?max_temperature))))

Running this locally yields the expected ton of log messages from Cascalog/Cascading and Hadoop. It also of course produces the results. Keep in mind that we could easily put this data into HDFS and run this on a true Hadoop cluster.

$ lein run
13/11/26 10:05:17 INFO util.HadoopUtil: resolving application jar from found main method on: clojure.main
13/11/26 10:05:17 INFO planner.HadoopPlanner: using application jar: /Users/eric/.m2/repository/org/clojure/clojure/1.5.1/clojure-1.5.1.jar
13/11/26 10:05:17 INFO property.AppProps: using <a href="http://app.id">app.id</a>: 95FF0B909C0248509D17736FCE4F178B
13/11/26 10:05:19 INFO flow.Flow: [] starting
13/11/26 10:05:19 INFO flow.Flow: []  source: Hfs["TextDelimited[['!G__4145', '!G__4146', '!G__4147', '!G__4148', '!G__4149', '!G__4150', '!G__4151']]"]["/Users/eric/Documents/motedata_2013_10_28.csv"]
13/11/26 10:05:19 INFO flow.Flow: []  sink: StdoutTap["SequenceFile[[UNKNOWN]->['?mote', '?count', '?max_humidity', '?max_temperature']]"]["/var/folders/2n/hxcfl5xj70d0fkcrps676xc80000gp/T/temp90912110954941277911385478317162124000"]
13/11/26 10:05:19 INFO flow.Flow: []  parallel execution is enabled: false
13/11/26 10:05:19 INFO flow.Flow: []  starting jobs: 1
13/11/26 10:05:19 INFO flow.Flow: []  allocating threads: 1
13/11/26 10:05:19 INFO flow.FlowStep: [] starting step: (1/1) ...1277911385478317162124000
… and much much more

RESULTS
-----------------------
1     513378     57.2     25.4
2     118543     53.6     26.4
3     513093     61.2     25.7
4     513613     61.2     27.6
-----------------------

OAuth2

Posted on November 21st, 2013

I've been working recently with OAuth2 and a few of the big guys like Login With Amazon, Facebook and Github. I keep thinking what an opportunity there was to get a real OAuth2 standard and we (collectively) blew it. Instead we have a framework where the requests are different, yes obviously each provider has a different domain, but the url formats are vastly different. Much worse is that fact that the responses vary widely. Some providers return a 401 if the token is invalid, some return a 400, some also return json with vendor specific keys and messages for more detail and for all that do return json those formats vary widely as well.

Gah!


Blogging Again with Postach.io

Posted on November 14th, 2013

About Me

My name is Eric Snyder and for 5-plus years I was a consultant with Chariot Solutions before I decided to venture into the world of a VC funded startup here in the Philadelphia area. That was a great experience but now I'm back with Chariot Solutions and I'll be blogging here at Code Highway To Somewhere.

Unnatural Love of Evernote

Anyone who knows me is aware that I have an unnatural love for Evernote, it borders on the creepy. There are many reasons for this that include the great client apps, (usually) pain free syncing of notes, great search capability, excellent browser plugins and maybe the most impressive feature called related notes where Evernote works some AI magic to return notes with similar content. As I type this in the Mac Evernote client I'm seeing a changing list of 3 related notes that change because the content of this post is changing. That's impressive.

postach.io

postach.io is a blogging platform built on Evernote. I created this site on pistachio by supplying just a few pieces of information. I create notes in Evernote using any Evernote client and then sync them as usual. If I tag a note as "published" it becomes a new post on the blog. It's that simple and, since my blog posts are just Evernote notes, all of my blog content is searchable via the excellent search feature, I can view notes related to my blog posts, etc..

So far so good. If I run into any difficulties with pistach.io I'll be sure to post them here.


Eric Snyder

Software consultant at Chariot Solutions with 19 years experience with a variety of technologies. Current favorites include golang and clojure.