Timeago for C#

By Rob on Thursday, September 10, 2009

2 Comments

Filed Under: .Net, Dot

Sometimes you don’t want to make your users think. There’s the odd situation where you want to represent time in natural language: “about 4 hours ago” instead of just printing out a full timestamp. If you’re building a website, then the jQuery plugin Timeago is a pretty sweet way to do it (as long as you can stand webpages that auto update text).

Sucks for me, I’m working with WPF! (Not really sucks at all). So I needed a C# implementation of the same thing. Surely someone’s done this, right? Well my Google-fu failed me, and even when I Googled on bing I came up with nothing, so I built it myself. And I’m posting it here for you (and for me, later). If you’ve found a good one, please let me know.

First, the test cases, so you can see if the format I want is the format you want:

[TestClass]
public class FriendlyTimeDescriptionTest
{
    private static string Run(TimeSpan span)
    {
        return FriendlyTimeDescription.Describe(span);
    }

    [TestMethod]
    public void TestNow()
    {
        Assert.AreEqual("now", Run(new TimeSpan(0, 0, 0, 0)));
    }

    [TestMethod]
    public void TestSeconds()
    {
        Assert.AreEqual("1 second ago", Run(new TimeSpan(0, 0, 0, 1)));
        Assert.AreEqual("2 seconds ago", Run(new TimeSpan(0, 0, 0, 2)));
        Assert.AreEqual("59 seconds ago", Run(new TimeSpan(0, 0, 0, 59)));
    }

    [TestMethod]
    public void TestMinutesAndSeconds()
    {
        Assert.AreEqual("about 1 minute ago", Run(new TimeSpan(0, 0, 1, 1)));
        Assert.AreEqual("about 3 minutes ago", Run(new TimeSpan(0, 0, 3, 1)));
    }

    [TestMethod]
    public void TestMinutesAndSecondsRounding()
    {
        Assert.AreEqual("about 4 minutes ago", Run(new TimeSpan(0, 0, 3, 31)));
    }

    [TestMethod]
    public void TestDaysHours()
    {
        Assert.AreEqual("about 3 hours ago", Run(new TimeSpan(0, 3, 3, 1)));
        Assert.AreEqual("about 2 days ago", Run(new TimeSpan(2, 0, 1, 1)));
    }
}

I’ve conveniently wrapped all this up into an IValueConverter implementation, but if you’re not using WPF you can rip out the necessary methods. Please excuse the newline-enthused formatting – this blog theme has limited column width!

[ValueConversion(typeof(DateTime), typeof(string))]
public class FriendlyTimeDescription : IValueConverter
{
    public object Convert(
        object value,
        Type targetType,
        object parameter,
        CultureInfo culture)
    {
        var time = System.Convert.ToDateTime(value);
        return Describe(DateTime.Now - time);
    }

    static readonly string[] NAMES = {
                                         "day",
                                         "hour",
                                         "minute",
                                         "second"
                                     };

    public static string Describe(TimeSpan t)
    {
        int[] ints = {
                         t.Days,
                         t.Hours,
                         t.Minutes,
                         t.Seconds
                     };

        double[] doubles = {
                               t.TotalDays,
                               t.TotalHours,
                               t.TotalMinutes,
                               t.TotalSeconds
                           };

        var firstNonZero = ints
            .Select((value, index) => new { value, index })
            .FirstOrDefault(x => x.value != 0);
        if (firstNonZero == null)
        {
            return "now";
        }
        int i = firstNonZero.index;
        string prefix = (i >= 3) ? "" : "about ";
        int quantity = (int)Math.Round(doubles[i]);
        return prefix + Tense(quantity, NAMES[i]) + " ago";
    }

    public static string Tense(int quantity, string noun)
    {
        return quantity == 1
            ? "1 " + noun
            : string.Format("{0} {1}s", quantity, noun);
    }

    public object ConvertBack(
        object value,
        Type targetType,
        object parameter,
        CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}

Enjoy!

Visualising Data with Dot (Part 2 of 4)

By Rob on Wednesday, March 11, 2009

2 Comments

Filed Under: Dot

Welcome back to my four part series on Dot. In this installment, I go deeper on some of the options available regarding layout of Dot graphs.

Part 2: More Dot layout

I’m not sure that I can add value beyond what’s already available in the documentation - its simple, clear and concise. What I will do, however, is call out the things that I found most useful:

Colours, fonts and shapes (oh my?)

You have full control over the following layout attributes:

You could imagine modifying the sql that I demonstrated in part 1 to also select out some colour / formatting attributes into the nodes and edges. Another options is to generate your Dot code in an application layer – since ruby, C# or java are more expressive with SQL it’s a bit easier to generate some really interesting graphs.

HyperDot: URLs and Tooltips

Did you know that Dot can generate more than just image files? The Dot syntax supports adding tooltips and URLs into nodes, edges, and even arrowheads. However, there’s no way for this kind of information to be embedded into a gif, png or jpeg. Currently, the output formats that will let you see these are:

  • SVG, which is natively supported by all browsers except for the most important one, is a web-aware xml based vector format (that is soon to be superseded by XAML). In firefox, for example, you could be viewing a graph that will hyperlink you to the appropriate web page when you click on an item on the graph. 
  • HTML ImageMaps are elements that you can use to tack urls and tooltips onto specific areas of an image in a webpage. If you run Dot twice against the same piece of Dot code (but with different output formats), you can create the imagemap code that corresponds perfectly to the pixels drawn on the image. In Part 4, I will be talking further about how to achieve this in an ASP.Net web server. This functionality replicates what SVG provides you (without the pretty vector scaling, but with superb browser compatibility).  

Personally, I am hoping that someone will champion the Silverlight cause and write a native XAML output format for Dot :) . I have experimented with a couple of SVG to XAML converters, both XSLT files, but neither of them get the layout just right.

Next in this series: how to call into Dot from a .Net web server to dynamically generate server side images.

Getting started with iPhone development

By Rob on Sunday, December 28, 2008

3 Comments

Filed Under: How To

My wife and I have recently been trying to build a simple game on the iPhone. Neither of us have tried to do any Mac development before, so it was a little bit of a challenge just working out where to start. Here’s some tips on where to go if you’re a beginner like us.

You will need:

  • A computer running the latest version of OSX (like a Mac)
  • XCode (the Apple IDE) installed on your OSX (This comes on the OSX install CDs, but not usually on the Mac itself)
  • Some internet (such as the one you’re using to read this blog)
  • An iPhone or iPod touch (although you can get quite far just using the simulator)
  • The iPhone SDK. Once you install this, XCode will be able to create iPhone projects. It also comes with a simulator and all sorts of performance testing tools. You’ll need to register as an iPhone developer before you can download this (or a number of other things I’m linking to), but you don’t have to fork out $99 until you want to deploy your app to an iPhone.

Apple have 11 introductory videos available on the iPhone development homepage. The first few I would recommend as mandatory, the others you should watch if they look interesting.

These links will help you to learn Objective C (The language you’ll be developing in):

One blog that I’ve found very useful is Mobile Orchard

The most important thing that I recommend doing is downloading Apple’s sample code and stepping through that. Playing with some sample code and seeing what changes are effected was probably what taught us the most. There’s a gallery of UIElements to browse through the different controls that apple provide, as well as examples of using the more complex input hardware that the iPhone provides, like GPS and the accelerometers.

Please leave a comment if you’ve got any further suggestions – I’ll add them to the list!

Visualising Data with Dot (Part 1 of 4)

By Rob on Friday, November 14, 2008

4 Comments

Filed Under: Dot

Welcome to part one of a four part series on getting some sweet visualisations of workflows, using the open source digraph tool “Dot”. This first post will be about dot’s DSL (which rocks), and how to generate it. Part two will look at some advanced styling and layout. Parts 3 and 4 will be about ways to publish this data over the Internet (Dot is a command line tool).

Dot’s input language is very simple. Here’s an example of a digraph generated by dot:

 

And the code to create this graph was:

digraph G{ //declare a digraph, and give it a name of “G”
    node[fontname=arial]; //sets the default font for all nodes
    bismuth212->thallium208[label="36%"]; //create a labelled edge
    bismuth212->polonium212[label="64%"];
    thallium208->lead208; //create an unlabelled edge
    polonium212->lead208;
    lead208[color=gray] //set the colour for the (already declared) lead 208 node
}

You can also give labels to nodes – the following code creates exactly the same graph:

digraph G{
    node[fontname=arial];

    1[label=bismuth212]
    2[label=thallium208]
    3[label=polonium212]
    4[label=lead208,color=gray]

    1->2[label="36%"];
    1->3[label="64%"];
    2->4;
    3->4;
}

You can get more information about the Dot language at the graphviz site.

I have recently started working on a project that involves a simple workflow. This workflow is modelled in my database. I’ve simplified it a bit, but here’s my schema:

 image

And here’s my data:

State Transition
ID    Name
1    Start
2    Write test
3    Run test (red stage)
4    Write function
5    Run test (green stage)
6    Refactor
7    Run test (refactor stage)
8    Finish
ID    FromStateID    ToStateID    Description
2    1    2   
3    2    3   
4    3    2    Passed
5    3    4    Failed
6    4    5   
7    5    6    Passed
8    5    4    Failed
9    6    7   
10    7    8    Passed
11    7    4    Failed

 

Not that easy to follow, huh? And this is a very simple example.

However, if we run the following (simple) SQL:

 

DECLARE @crlf varchar(2)
SET @crlf = char(13)+char(10)

DECLARE @s varchar(max)
SET @s = '  node[fontname=arial];'+@crlf

SELECT @s=@s+'  '
    +convert(varchar,ID)
    +'[label="'+Name+'"];'
    +@crlf
FROM State

SELECT @s=@s+'  '
    +convert(varchar,FromStateID)
    +'->'+convert(varchar,ToStateID)
    +'[label="'+Description+'"];'
    +@crlf
FROM Transition

SELECT 'digraph G{'+@crlf+@s+'}'

we’ll get the following output:

digraph G{

  node[fontname=arial];

  1[label="Start"];

  2[label="Write test"];

  3[label="Run test (red stage)"];

  4[label="Write function"];

  5[label="Run test (green stage)"];

  6[label="Refactor"];

  7[label="Run test (refactor stage)"];

  8[label="Finish"];

  1->2[label=""];

  2->3[label=""];

  3->2[label="Passed"];

  3->4[label="Failed"];

  4->5[label=""];

  5->6[label="Passed"];

  5->4[label="Failed"];

  6->7[label=""];

  7->8[label="Passed"];

  7->4[label="Failed"];

}

Which we can run through the dot tool:

C:\Program Files\Graphviz 2.21\bin>dot -Tgif -ooutput.gif

output

It’s not pretty (yet), but it’s so much easier to follow than looking at numbers. Hopefully I’ve got you thinking about how you could rewrite that SQL statement to work against your own database tables.

I’ll be posting a guide to styling and laying out this Dot diagram soon – stay tuned!

How to fix a dzproj file when DeepZoom Composer has mangled your CompositionNode’s Ids

By Rob on Wednesday, October 8, 2008

1 Comment

Filed Under: How To

Warning: if you’re not into building deepzoom systems, stop reading. This post will BORE YOU. Was the title not a good enough indication?

Ok good, they’re gone!

The current release of the deep zoom composer has a little bug. Ok – it has a few bugs, but I hate this one the most. You close your DeepZoom project(*.dzproj), you open your project, and viola – your images have rearranged themselves (or just disappeared). The first thing to worry about is: have you moved your files? Deepzoom projects store file references as absolute paths, which is ridiculous, but not the problem I was having.

Tori talks about this problem on the “known issues page” for the composer.

I don’t know what causes it (language settings were mentioned on that thread – I’m using en-NZ), but if you look inside the dzproj file itself, the Ids of all the composition nodes are all wrong.

More like “Deep Zoom Confounder”!

Im sure theres a fix coming, but if you’re as desperate as me, then what you’ve got to do is this:

1) put :<filename> on the end of each image’s tag

2) run the following code across your dzproj file:

using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;

class Program
{
    static void Main(string[] args)
    {
        var file = string.Join(" ", args);

        XDocument d = XDocument.Load(file);

        var images = from i in d.Descendants("Image")
                     select Path.GetFileName(i.Element("File").Value);

        var imageList = images.ToList();

        var map = from e in d.Descendants("CompositionNode")
                  let tag = e.Attribute("Tag")
                  where tag != null
                  let fileName = tag.Value.Split(':').Last()
                  where images.Contains(fileName)
                  select new
                  {
                      Node = e,
                      NewId = imageList.IndexOf(fileName)
                  };

        foreach (var v in map)
        {
            v.Node.SetAttributeValue("Id", v.NewId);
        }

        d.Save(file);
        Console.Out.WriteLine("Press any key to continue");
        Console.ReadKey();
    }
}

This code will read in the xml, build a list of all the correct image ids, then update the ids of all your composition. Its not easy, but it’s a workaround!

How to get your iPhone on WiFi when your proxy uses NTLM (thanks to NTLMAPS)

By Rob on Tuesday, September 30, 2008

4 Comments

Filed Under: How To

I have been struggling to get my iPhone working against my corporate ISA proxy server. Once I had connected to WiFi, Safari would continually prompt me for my domain user name and password, and none of the apps would work (maybe because they didn’t know how to prompt me for credentials).

After a bit of poking around with fiddler, telnet, and all sorts of settings, I was certain that the problem was our ISA Proxy server’s NTLM authentication.

NTLM is an authentication mechanism from Microsoft. Microsoft’s ISA Proxy Server uses NTLM to be able to tell which active directory user is attempting to access the internet.

If you’re surfing with Internet Explorer, it picks up your username directly from your login, and you might not even realise that IE is authenticating you. If you’re using firefox, you might get prompted the first time you go on the internet. As far as I can tell, Google Chrome prompts you each time you launch it.

If you’re trying to get online with an iPhone, you’re not so lucky. Safari will prompt you for credentials every time you change domains (actually, I’m pretty impressed that it can authenticate at all – nice work Apple, don’t give up!). This gets tiresome. Whats more, anything else that wants to use the internet from your iPhone has no chance.

iPhone and ISA don’t play nicely

So how do we get around this? It’s not easy. It has the potential to let other people leech your personal bandwidth and get you into trouble, if you don’t do it right. But I HAD to get online, so i started writing my own proxy server that would “chain” to ISA. It would be capable of hiding the NTLM authentication from whatever system was using it as a proxy, and then providing a preconfigured username and password to ISA “up” the chain. In order to keep my login safe from other people who could just use my proxy server, I would have locked down which IP addresses could use my proxy server.

As it turns out, someone’s already built an “NTLM Authorization Proxy Server”, and thoughtfully called it “NTLMAPS“. It’s written in python, and it works perfectly. It even has a feature to lock down IP addresses, which I strongly recommend you use.

NTLMAPS hides ISA’s nastiness from the iPhone

So how to get started? I installed NTLMAPS on my workstation – you’ll need administrative rights.

  1. Install Python from http://www.python.org/download/
  2. Unzip the NTLMAPS release from https://sourceforge.net/project/showfiles.php?group_id=69259&package_id=68110&release_id=388621
  3. Edit the server.cfg file. You will need to change the following keys
    • PARENT_PROXY – your ISA proxy server
    • PARENT_PROXY_PORT – your ISA proxy port
    • ALLOW_EXTERNAL_CLIENTS – set this to 1 to allow yourr iphone to connect
    • FRIENDLY_IPS – put your iPhone’s current wifi IP here unless you want to let everyone on your account! You’ll have to change this a lot.
    • NT_DOMAIN
    • USER
    • PASSWORD – you can leave this blank if you want – every time you start the server it will prompt you.
  4. Edit the batch file so that it points to the correct python.exe
  5. Launch the batch file. You’ll now have the NTLMAPS proxy server up & running. It will tell you the hostname and the port. If you’ve got a firewall going, and you’re lucky, the firewall will ask if you want to unblock that port.
  6. Make sure that whatever firewall you’ve got installed allows incoming connections to NTLMAPS
  7. Setup your iPhone to use your own computer as a proxy. You can do this in settings>general>network>wifi>your current wifi network. You can turn authentication off, but the server and the port (under manual proxy) should be what ntlmaps have told you to use.

There! you should be good to go. You can make sure your iPhone is getting its internet through wifi by temporarily changing your cellular data gateway to something incorrect. Google maps, the app store, weather, they should all start working once you’ve done this, as long as NTLMAPS is running.

Let me know how it goes!