# Sunday, March 15, 2009

A long time, and many many moons ago I took wrote some code to interface our build server with a network power switch we had laying around the office. We used this to turn on and off lava lamps to indicate the status of the build. Some might ask why we didn't use the X10 support that is already in CCNET, and the answer mostly is cost, and the fact that X10 wouldn't work in our environment.

That was 2.5 years ago. Since then, our team has become more distributed. We have one guy working in Ann Arbor, MI, and occasionally have others telecommuting. So not everyone can see the status of the lamps. Also in the 2.5 years since that code was written, a little thing called Twitter has become very popular. I did some research, and found Tom Fannings nAnt Twitter task and briefly considered using it.

But in the end, I just couldn't resist adding my own developer gold plating and thought it would be neat if we could also issue commands to the build server via tweets. So with that feature in mind, I had to write it myself.

To start out with, I used Yedda's C# Twitter library. The Yedda library is a pleasure to work with, it makes sending a tweet as simple as

new Twitter().UpdateAsXML(_username, password, messsage);

One thing the Yedda library didn't have, was the ability to query for your Twitter replies. A quick look through the source, and the Twitter API docs and I realized this would be trivial. The details of how I did it aren't important to this post, but if your curious, you can look at lines 567 - 627 of the Yedda source included with this post.

I'm not going to dive to much into how the whole project works, but here is a high level. The software runs as a Windows Service, leverages the CCTrayLib assembly for Cruise Control.NET to do all the heavy lifting. It polls the Cruise Control.NET server every 5 seconds, and fires events when things happen. The two events we want are the Polled and BuildOccurred events.

These events allow us to intern kick off our own events based on the state of the build. Based on the state, we grab the appropriate actions to run as defined in the BuildActions.xml file. This maps a build state to a set of actions. In the case of a "Building" action, we send a Twitter, with a message template of "{PROJECT} is building", and turn ports 1 on, and 2 off on our ePower switch. Easy enough.

But how do we take in commands? I pondered this for a minute than realized it would be trivial to leverage the Twitter replies API for this. But what about security, we can't have just anyone sending commands to our build server. This is where the Twitter friends API comes in handy. In order to issue commands to our build server, the account our build server uses has to have you as a friend, not just a follower.

The first action I implemented was the force build command. The idea for the grammar came from a joke reply @orand sent our Twitter bot. After that, I thought it might be nice to be able to get the list of projects, get a projects status, and ask for help. So that leaves us with a total of 4 commands.

A small bunny trail

When I first wrote the command parser, it looked something like this:

if (msg.StartsWith("force build "))
    ProcessForceBuildCommand(msg, user);
else if (msg.StartsWith("get projects"))
    ProcessProjectListsCommand(msg, user);
...

I thought about it for a while, and thought there had to be an easier way. We are using .NET 3.5 after all, with all its lamba, LINQ and new and improved delegate goodness. I did some research and came upon the Action<T> (and Func<T>) delegate type. And came up with this implementation for registering commands

var commands = new Dictionary<string, Action<string, string>>
    {
        {"force build ", ProcessForceBuildCommand},
        {"get projects", ProcessProjectListsCommand},
        {"get project status ", ProcessProjectStatusCommand},
        {"help", ProcessHelpCommand}
    };

Once we have all the commands registered, we can use some lambda and LINQ magic to act upon the commands issued

var key = _commands.Keys.Where(msg.StartsWith).First();
if (!string.IsNullOrEmpty(key))
    _commands[key].Invoke(msg, user);
else
    SendTweet(string.Format("@{0} I'm sorry, I don't understand you. Maybe you should ask for help", user));

Get to the point, I want to see the source code

The code overall is fairly well structured (if I do say so myself), although there is one area where I did violate the separation of concerns rule, the TwitterManager class knows more about Cruise Control.NET than it should. But, given that this is a very simple internal project, and not for public consumption, I'm mostly OK with that :)

I've included the source to our entire build monitor, I hope you find it useful.  We are using a very old version (1.1) of Cruise Control.NET in our environment. If your using a more recent version, you will probably need to swap out the Cruise Control CCTrayLib and Remote assemblies for something more recent, and invert some of the commented out code in the SetupCruiseControl method of BuildServerMonitor.cs

If you have any questions, or find/fix any bugs, please feel free to leave a comment, or send me a tweet, my username on Twitter is akcoder.

Download the Afhcan.BuildMonitor

.NET | C# | HowTo
Sunday, March 15, 2009 7:59:22 PM (Alaskan Standard Time, UTC-09:00)
# Friday, February 20, 2009

One of our applications has a Windows service in it. To make debugging and running this service easy,we have a winform in the service which can be activated by passing a command line switch. Simple enough. Our service does all its logging with log4net. I wanted to be able to put the output of the logging on our development form, but how?

After looking at various things, I realized what I needed to do was create a log4net appender, and add the appender to the log4net logger and at regular intervals, grab the contents of the logger.

Solution

I created the below appender which uses a StringBuilder as the backing store. It takes in one bool param in the contructor which allows you to specify if you want the log to be built up in reverse.  This is useful if you want to display the most recent event at the top.

public class StringBuilderAppender : log4net.Appender.AppenderSkeleton
{
    private System.Text.StringBuilder _builder = new System.Text.StringBuilder();
    private readonly bool _invert;

    public StringBuilderAppender(bool invert) { _invert = invert; }

    public string Text { get { return _builder.ToString(); } }

    protected override void Append(log4net.Core.LoggingEvent loggingEvent)
    {
        var msg = loggingEvent.RenderedMessage;
        
        if (_invert)
            _builder = new System.Text.StringBuilder().AppendLine(msg).Append(_builder);
        else
            _builder.AppendLine(msg);
    }
}

Now we need to add our new appender to the logger. I found this helper method someone wrote.

public static void AddAppender(string loggerName, IAppender appender)
{
    log4net.ILog log = log4net.LogManager.GetLogger(loggerName);
    log4net.Repository.Hierarchy.Logger l = (log4net.Repository.Hierarchy.Logger)log.Logger;

    l.AddAppender(appender);
}

Finally, lets put it all together:

StringBuilderAppender appender = new StringBuilderAppender(true);
AddAppender("MyLogger", appender);

while(true) {
    System.Threading.Thread.Sleep(5000);
    someControl.Text = appender.Text;
}
.NET | HowTo | Logging
Friday, February 20, 2009 10:18:15 PM (Alaskan Standard Time, UTC-09:00)
# Tuesday, February 10, 2009

At our organization, we have to globalize our software. Making sure you've gotten all the strings globalized can be a real pain. You have to create a new language resource that looks nothing like your native language, then set the Thread.CurrentThread.CurrentUICulture to the culture of the new language resource you created.  Such a pain.

While pondering that this afternoon, I came upon something better. The .NET CultureManger looks for the most specific resource file, then works its way back to the least specific. For example, given the following resource files:

  • i18n
  • i18n.en
  • i18n.en-US

If your current culture was en-GB, the CultureManger would use the resource file for i18n.en, since there is no i18n.en-GB. But, if your current culture was da-DK, it would use i18n.

In our software, we have i18n, and i18n.da-DK resource files, plus i18n.fr-FR which is a special, internal resource file. What's so special about the fr-FR resource file you ask? The fr-FR resource file is really the i18n resource file which as been transformed to replace all the localized text with dashes.

Why did we do this? Because with all the English text replaced with dashes, it makes it very easy to see which text in the application hasn't been globalized. The down side to this, is we have to change the CurrentUICulture (and CurrentCulture) to fr-FR in order to test this.

Solution

The solution is actually quite simple, rename the fr-FR resource file to i18n.en-US (or what ever the ISO code for your culture is).  Now when your testing, the CultureManager will pick the most specific resource file, and use that. But, don't forget to remove en-US folder from the final build folder before you deploy your application, lest users get your debug language resource.

kick it on DotNetKicks.com
Tuesday, February 10, 2009 2:11:48 PM (Alaskan Standard Time, UTC-09:00)