Posted by admin on Tuesday, April 29, 2008 at 4:12 PM

Previously on Adventures of a Lead Programmer in a One-man Shop -
"No, please, I can't take it anymore.  I'll do it.  I'll put my code in source control.  I can't bear the .zip file shame any longer.  Just leave me enough money for milk and crackers.  What?  It's free?!?  And easy?!?  You had me at free..."

It's official.  I've begun wriggling out of my inferiority complex and scrambling up toward a shinny, new Superiority Complex.  In my previous post I said that I wanted to couple my source control solution to a Continuous Integration server that will compile my latest bugs edits into a software release.  When researching continuous integration solutions the two that I heard mentioned most often were CruiseControl.NET, an open source project from ThoughtWorks, and TeamCity from JetBrains.  CC.NET is free and JetBrains offers a free professional version so they both passed the first test.  My sense from snooping around on the web though was that TeamCity would be easier to configure.  I can't say if TeamCity was easier than CC.NET because I didn't try both, but I can say that TeamCity was easy.  There were a couple of points I found confusing though.  I've detailed them in the guide below.  I went through the actual setup twice, but if you find any errors to fix or tips to add please let me know. 

This guide is intended to be used in conjunction with the TeamCity documentation.  I have included the relevant links below.  The guide covers installation of TeamCity and installation and configuration of MySQL (also free, of course) to serve as a back end for TeamCity.  For completeness I've included the Subversion setup steps at the beginning.  Refer to Craig Shoemaker's Subversion episode for more details.  On the first run through I set up TeamCity on an instance of Windows 2003 running locally on Virtual Server 2005 R2.  The second setup was on a remote Windows 2003 server that will be used for production.  Windows 2003 is not a requirement.  All of this can be configured on a single machine (i.e., your desktop machine). 

  1. Installed Subversion using SVN 1-Click Setup option. NOTE: During the install I skipped the Tortoise installation step because the bundled version is not the most current.
  2. Installed the current version of Tortoise.
  3. Installed TeamCity 3.1 Profession Edition following their installation documentation.
    • During the setup I chose 8080 as the port because I already had IIS installed using port 80.
    • For the rest of the installation I clicked through, accepting all of the defaults including for the Build Configuration dialog at the end.
    • Logged in for the first time to accept the license agreement and create the administrative account.
  4. Setup MySQL for TeamCity following their documentation.
    • Downloaded MySQL Community Server.  I chose the default typical installation type.
    • At the end of the installation when prompted with the database configuration I chose Detailed Configuration. 
    • I accepted the defaults until I reached the Concurrent Connections option.  I chose Manual Setting and set the value to 5.  Nothing magical, I just picked the lowest one since I'll only be using this for TeamCity.  You can also manually enter any number.
    • On the Default Character Set option page of the wizard I chose the Manual option and set the value to utf8, as suggested in the TeamCity docs. 
    • On the security screen I checked the box to allow remote access.
    • I accepted the default settings on the rest of the pages in the setup wizard.
    • If you make a mistake or want to change the settings later the configuration wizard can be accessed from the start menu (Programs > MySQL > MySQL Server 5.0 > MySQL Server Instance Configuration Wizard).
  5. Downloaded and installed SQLyog MySQL GUI - Community Edition.  Yep, it's free.  I've used it before and recommend it.  This will be used to create and manage databases in your installation of MySQL. 
    • After installation I connected to my instance of MySQL with SQLyog using the root credentials that I specified in the setup.
    • I created a new database for use with TeamCity (DB > Create Database).  I named it...wait for it... TeamCity and set the character set to utf8.
    • I used the User Manager of SQLyog to create an account on the MySQL instance under which TeamCity will access the database.  The User Manager is found in the Tools menu or by clicking the User Manager button on the toolbar.  I set the account name to...you guessed it... teamcityuser and granted full permissions because at the time I had no idea which privileges were necessary.  Certainly it will need Select, Insert, Update, Delete, and Create to create the supporting tables.  I wasn't sure if it would need Drop, Alter, and Grant for the setup as well.  I'll restrict them later once I've got everything working. 
    • At this point I connected to my instance of MySQL using the new credentials to verify everything was working.  If you're following along notice that the tables directory in the newly created teamcity database is empty.
  6. Downloaded and configured the MySQL JDBC driver.  The JDBC driver allows TeamCity to connect to the MySQL database.
    • The TeamCity documentation says to put the MySQL connector jar in the WEB-INF/lib of TeamCity's web application.  The MySQL connector jar file name is mysql-connector-java-5.1.6-bin.jar and is found at the root of the unzipped directory.  I copied it to C:\TeamCity\webapps\ROOT\WEB-INF\lib of my installation. 
    • The next step is to modify the database.mysql.properties file which is found in the C:\Documents and Settings\<account name>\.BuildServer\config directory.  Before renaming and modifying the file as directed in the setup I made a copy of the file as a backup.  I opened the renamed file in notepad and made the following changes:
      1. connectionUrl=jdbc:mysql://localhost/teamcity
      2. connectionProperties.user=teamcityuser
      3. connectionProperties.password=the password I chose
      4. uncommented the connectionProperties.characterEncoding=UTF-8 line by removing the leading # character
    • Saved the changes and closed the file. 

After making the changes I restarted the server.  I opened a web browser on the server and navigated to localhost:8080.  I was prompted to accept the license agreement again and had to recreate the administration account because the old local data store had now been successfully replaced with MySQL.  You'll also notice at this point if you've followed along that SQLyog will show the supporting tables have been created in the database you made for TeamCity.

Now what? 

Next I need to configure TeamCity to automatically perform a build of the code from my source control repository.  I'll cover that in the next thrilling Adventure of a Lead Programmer in a One-Man Shop.

Comments [2]     Categories: Continuous Integration | Source Control              
Posted by admin on Friday, April 25, 2008 at 5:00 PM

Good gravy, people.  Why didn't you tell me is was this easy to use source control?  Leave it to Craig Shoemaker and his Polymorphic Podcast to, as Denzel Washington's character in Philadelphia said, "Explain it to me like I'm a four-year-old."  I listened to his series on Design Patterns in March of 2007 and it was a revelation.  He produced a show in August of last year called Subversion Quickstart for .NET Developers that I didn't listen to at the time but made a mental note to go back to.  I'm embarrassed to say this, but up to this point I've been using .zip files as my caveman-esque source control system.  In my defense though, it's not like I didn't know what source control was or have access to it.  Visual Source Safe has been available to me for several years.  However, based on everything I'd heard a better alternative would be to print out my code and hide the reams of paper under my mattress.  So here I've been with a growing pile of .zip files and an inferiority complex.  The reason I finally returned to the topic was the browbeating I was getting from magazine articles I've been reading about Agile Programming methods.  "You are using source control, aren't you?", they asked.  I nodded, made up an excuse about having to go turn off my headlights, and pulled up Craig's Quickstart.  It was just what I expected - he made it crazy easy. 

The next question was how can I apply this to my day job?  I knew setting up a source control solution for work would be easy.  I've got access to all the servers I would need for that.  But what about HappyFish?  Whatever I found I wanted it to be:

  • On a remote site for easy access
  • Trustworthy with respect to backups and security
  • Free for closed source projects

SourceForge would probably have been the answer if not for the closed source requirement, but I'm not ready to open HappyFish yet.  My hosting provider seemed like a good place to start.  I use webhost4life.  I've heard good things about other hosts, and even tried one, but no other host gives the same level of features that they offer for the price.  And no other host provides as much user control over their set up in a shared hosting environment.  (If you sign up for their hosting and start with this link then I'll get a credit toward my hosting.  Many thanks.)   At any rate, I checked with them and they don't offer a free source control solution.  Undaunted, I searched on and eventually found Unfuddle.  This is another one of those mysteries of the web - 200MB of free, secure, closed source control hosting space. 

There you have it - from .zip files to true source control in zero dollars.  Thanks Craig.  Thanks Unfuddle.  But it doesn't stop there.  This whole mess started with me reading articles about agile programming techniques.  What good is source control with out a continuous integration solution to go along with it?  I found a free solution for that too.  I'll tell you all about it in the next episode.

Comments [1]     Categories: Source Control              
Posted by admin on Tuesday, April 22, 2008 at 8:38 PM

As I've mentioned before, I started my professional life as a biologist.  Blame it on watching too many episodes of M.A.S.H. for giving me the idea that I wanted to be a surgeon.  I won't waste a lot of time laying out my employment history.  Suffice it to say that after various lab jobs in both industry and academia I made the career change to programming and have been content ever since.  That was about three or four years ago.  Currently I'm the sole programmer in a group working on a patient therapy and outcome registry at the Markey Cancer Center at the University of Kentucky.  As the sole programmer I'm also the lead programmer, which is a lot like being the fastest banana slug. 

As I also mentioned in my earlier post, I've decided to attempt something close to regular posts about what I do and how I do it.  This is the first, and I'm starting off with a post about my equipment.  I'm a laptop-based developer.  I upgraded from a Dell Optiplex GX270 late last year to a Dell Precision M6300.  I was a little wary at first about committing to a laptop, but so far so good.  I've got a 17" screen, 4GB of RAM and a 2.6 GHz Core2 Duo processor.  The graphics card is an nVidia Quadro FX 1600M with 256MB of RAM - more than enough for Aero (which I stopped using).  I have a 5.1 Windows Experience score.  The OS is 64-bit Windows Vista Enterprise.  Over the past week I gave 64-bit Windows 2008 a try because I use a lot of virtual servers and this processor has the virtualization feature that works with Hyper-V.  Ultimately though I returned to Vista.  There is too much friction in daily use to make Windows 2008 worth it for me.  In reloading my OS I remembered a major pitfall I first encountered getting a 64-bit OS working on this machine so I'm going to post my setup procedure here in case anyone else runs into the same issue I did.  I'm also posting other apps and tweaks that I find useful.

The main problem I had during a 64-bit OS install on this machine is that I have to load the SATA driver for the hard disk before I load the OS.  It took me a while to figure that out when I first got this machine (it came with 32-bit Vista Ultimate).  I'd finish the OS load and on first reboot the thing would just sit there with a black screen and a flashing underscore in the top left corner.  The hard drive driver is R154201.  I unzip it in a directory on a USB stick for use in the install routine.  Once the OS is loaded I install the chipset, and the Turbo Memory driver (version A02) then the Matrix Storage driver in that order.  After that I just move through the rest as I come to them: Ethernet, WiFi, System Utilities, sound, graphics, and touchpad.  The touchpad driver (R157048) is nice because it adds the horizontal and vertical scroll features for the edges of the touchpad. 

Applications:

I also include the current releases of Visual Studio add-ons:

I used to install Microsearch Color Picker, but it won't install on 64-bit Vista. 

For flexibility I keep my documents on an external hard drive (Iomega) along with backups of my virtual servers which I run off of my hard drive.  I've tried running them off of an external drive but saw no real performance improvement since I'm using an external drive for Visual Studio solutions.  I also have a seed virtual hard drive file for XP, Vista, and Windows 2003.  If I kill one of my servers during an experiment I just delete it and make another copy of the hard drive seed.  The mobility of a laptop combined with Virtual Servers means that I can do my job in the office or at home without interruption.  The dual monitors (20" and 24") on my desk typically make the walk from the parking deck worth the effort though.

Other than this, all I really need to do my job is a network connection so that I can listen to Radio Paradise and a strong cup of coffee around noon.

Comments [1]     Categories: Equipment              
Posted by admin on Tuesday, April 08, 2008 at 7:26 PM

In the process of adding new features to HappyFish I found myself in need of a control that would allow a user to rate an item.  I've seen these in various contexts, usually on web pages, but couldn't find one that worked well in my case.  I needed on that could be used on a ToolStrip.  This of course was just the excuse I needed to make my own.

Star Rating ControlHere's the finished product.   The yellow star and delete images came from the Silk collection of free icons from FamFamFam.  The gray star is a grayscale version of the yellow one.  Each star has a tool tip as does the delete button to give user feedback as to the current rating and how clicking on other images will change the rating.  Initially I wasn't sure how and where I would be using the control so I added the ability to orient the control vertically instead of horizontally via a property setter.  I'll include the source for this project in both VB and C#. 

chooseimagedialog I started by creating a new class library project in Visual Studio.  I added a user control (StarRating) and set its background color to transparent.  I added six picture boxes to hold the star and delete icons and set their images.  By choosing the "Project Resource File" option then clicking Import the images you add will also be available as resources elsewhere in the project.  I set each star's picture to the yellow variant, but that was not necessary as you'll see later.  With all of this done I spent what seemed like and hour fidgeting with the layout spacing a padding. 

A big part of the user control coding involves setting the correct ToolTip on each star depending on whether is selected or not.  I wanted the currently selected star to say something like "Rated 3 Stars" (past tense).  I wanted the rest of the stars to say something like "Rate 4 Stars" to indicate clicking on that star would change the rating.  As for the delete button I wanted it to say "No Rating" or "Remove Rating" as appropriate.  The StarRating user control class also exposes a public event RatingValueChanged, a property for laying out the control vertically or horizontally (and supporting enumerator), and the control's rating property.  I also exposed the control's BorderStyle property for good measure.

toolstripitemsdropdownTwo other classes are included in the project.  One is RatingChangedEventArgs, which inherits the EventArgs class and provides the new rating and the old rating for the RatingValueChanged events.  The other supporting class is ToolStripStarRating.  It inherits the ToolStripControlHost class.  ToolStripStarRating wraps the StarRating user control and does the work of making it available as a ToolStrip item via the ToolStripItemDesignerAvailabilityAttribute attribute.  It also handles connecting the events of the StarRating control to its own RatingChanged event handler.

Note that even though the ToolStripStarRating class makes the StarRating user control available as a ToolStrip item, the StarRating user control can be used by itself on a WinForm as well.  You will find a demonstration of this in the sample WinForm application included in the project.

To use the control add a reference to the ThirstyCrow.WinForms.Controls.dll found in the bin directory of the ThirstyCrow.WinForms.Controls project to your project.  You should then see the StarRating control in the ToolStrip item drop down selector.

A link to the source code along with a sample WinForm demonstrating the control are below.  Both C# and VB.net are included in the same zip.
Download: StarRatingControlCode.zip (402.79 KB)

Comments [2]     Categories: WinForms              
Posted by admin on Monday, April 07, 2008 at 10:14 PM

UPDATE #2: I've added a link to an improved version of the source code sent in by Hafthor Stefensson at the end of the article.

UPDATE: I've made a few changes to this article (at the end) based on comments kindly left by Scott Hanselman.  The project source files and compiled macro dll have the updates discussed.

Danny nagged me into getting a Twitter account a few weeks ago.  The traffic content runs from interesting to spam.  Around that same time I made the switch from Community Server to dasBlog for this site, resolving to post more (read: more than once per quarter).  The convergence of my curiosity regarding customizing dasBlog and the Twitter API along with my desire to ease getting content onto my blog led me to this post.  Danny pointed out hashtags, a service that will use the hash/pound character (#) as an indicator for a metatag in a Twitter post, similar to a code comments.  After looking a the javascript based badge for displaying your Twitter posts on a web site I though, why not write a dasBlog macro that will selectively pull in Twitter posts to show on my blog?  I could prefix the posts I want to show up with a special character.  That way visitors to my blog don't have to read "I love Kashi GoLean, but Kashi GoLean doesn't love me.", but can see the link to the VS2008 Hot-fix Roll-up.  And better yet I could use a caret (^) as the special character because then I could call it CaretStick, you know, because the caret would let me stick it in my blog.  It's too cheesy to pass up.  Anyway, none of this is particularly difficult, but it does touch on several programming concepts for those looking for working examples of HttpWebRequest, Regular Expressions, and XML in the context of .net.

I started by familiarizing myself with the dasBlog macro guide article.  It is a thorough post, and there's no point in me repeating the content here so read it first if you're not familiar with the process.  This article will assume you have.  I will point out the specifics of this implementation however and one additional modification I had to make to the web.config file at the end.

I started by creating a class library and vainly named it ThirstyCrow.DasBlogMacros.  I named the class DasCaretStick and created a single public virtual control called CaretStick with two arguments, a string for the Twitter feed URL and a boolean to indicate whether to suppress error messages. 

public virtual Control CaretStick(string twitterURL, bool suppressErrors) {
    return new LiteralControl(ParseTwitterFeed(twitterURL, suppressErrors));
}

Inside the CaretStick control I call the method that does all the work - ParseTwitterFeed.  The Twitter API has a lot of info about pulling different content out of an account and also about how to make posts to an account.  For this macro though all I'm really interested in is parsing the existing content, so rather than make an authenticated request I chose instead to parse the publicly available RSS stream output from my account.  This is done using the HttpWebRequest class. 

private string ParseTwitterFeed(string twitterURL, bool suppressErrors) {

    StringBuilder sticks = new StringBuilder();
    sticks.Append("<div class=\"caretStickWrapper\">\r\n");

    try {

        HttpWebRequest request = WebRequest.Create(twitterURL) as HttpWebRequest;

        using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) {

            StreamReader reader = new StreamReader(response.GetResponseStream());

            XmlDocument doc = new XmlDocument();
            doc.Load(reader); 

You'll notice I've also instantiated a string builder that will be used to hold the HTML that will be output to the control.  StringBuilder performs better than string when doing successive concatenation operations, which we'll be doing as we parse the Twitter feed items.  I've started the HTML output by creating a wrapper div for the entire content and applied the class caretStickWrapper.  This will allow us to style the output with CSS.  You'll also notice the opening of a try catch block used to handle any errors that may arise.  More on that later.  By the last line of this block we've requested the RSS feed (an XML document) from the twitterURL, gotten the response stream from the response, and loaded it into an instance of an XmlDocument.

The RSS specification lays out the structure of an RSS feed.  In this case we're interested in the item nodes of the document.  We can use an XPath query to select all of the item nodes into an XmlNodeList that we can loop through.  We pass in the XPath query as an argument of the SelectNodes method of the XmlDocument we're working with.  The double slashes (//) indicate to select all item nodes, regardless of where they are in the document.

XmlNodeList nodeList = doc.SelectNodes("//item");

Under an item node there are several possible child nodes.  We'll need the "description" node, which has the Twitter post in it and the "pubDate" node which has the date and time the Twitter post was made.  How did I know the Twitter post was in the description?  I simply took a look at the raw feed output by pulling it up in my browser and using the View Page Source function.  (The Twitter post is duplicated in the item's title node too).  To select the description nodes we use the SelectSingleNode method of the XmlNode we're working with and grab the node's inner text in a string variable.

foreach (XmlNode xn in nodeList) {
    string rawMessage = xn.SelectSingleNode("description").InnerText;

Next we check whether the message contains a caret.  Testing whether the message starts with a caret will fail because in the feed output all of the messages are formatted username: message.  The earliest we would see our caret would be after the colon following the username.  It is still a useful delimiter for chopping off the text we don't want to show at the front of the message.  Each message will be wrapped in an individual div of the class caretStick.

//use contains instead of startswith because message is prefixed by account name
if (rawMessage.Contains("^")) {
    //*********************************************
    //process the message
    //*********************************************

    // remove everything up to the leading caret
    rawMessage = rawMessage.Substring(rawMessage.IndexOf('^') + 1, rawMessage.Length - rawMessage.IndexOf('^') - 1);

What if we put a link in the Twitter message?  Twitter doesn't include the markup necessary to make it active on our page, but that's easily fixed using regular expressions.  The $0 notation refers to the string matching the regular expression. 

// activate any links
Regex rx = new Regex(@"(http|https|ftp)(://(\w|\.|/)+)");
string processedMessage = rx.Replace(rawMessage, "<a href=\"$0\">$0</a>");

sticks.Append("<div class=\"caretStick\">\r\n" + processedMessage + "\r\n");

At this point we've got our tagged post with all links activated in the processedMessage string.  Next we'll append the publication date information.  This is held in the pubDate subnode.  We'll select it using the same technique we used to grab the description node above then convert it to a date.  With the pubDate in hand we can subtract it from the current time do determine how old the post is and format a friendly message about the age of the post.

//parse the pubdate
DateTime pubDate;
if (DateTime.TryParse(xn.SelectSingleNode("pubDate").InnerText, out pubDate)) {

    //format a friendly message about the post's age
    TimeSpan span = DateTime.Now.Subtract(pubDate);

    sticks.Append("<div class=\"caretStickPubDate\">");

    if (span.TotalSeconds < 60.0) {
        sticks.Append(span.TotalSeconds.ToString("0") + "sec ago");
    }
    else if (span.TotalHours < 1.0) {
        sticks.Append(span.TotalMinutes.ToString("0") + "min ago");
    }
    else if (span.TotalHours < 24.0) {
        sticks.Append(span.TotalHours.ToString("0") + "hr " + span.Minutes.ToString("0") + "min ago");
    }
    else {
        sticks.Append("on " + pubDate.ToShortDateString() + " at " + pubDate.ToShortTimeString());
    }

    sticks.Append("</div>\r\n");

}

sticks.Append("</div>\r\n");

We end the loop iteration by closing the caretStick div.

In the case of an error we have a couple of options.  We can suppress the error message or show it in our output to the control.  Because there are several places that the error could occur with respect to the div's we're making it is safer to discard any HTML up to the point of the error so that we don't end up with an unclosed div that could wreck the page layout.  This code could be written to handle errors at various stages and salvage the output up to the point of failure, but I just want a simple all or nothing solution in this case.

catch (Exception exception) {
    if (suppressErrors) {
        sticks = new StringBuilder();
        sticks.Append("<div class=\"caretStickWrapper\">\r\n");
    }
    else {
        sticks = new StringBuilder();
        sticks.Append("<div class=\"caretStickWrapper\">\r\n");
        sticks.Append("<div class=\"caretStickError\">An error occurred: " + exception.Message + "</div>\r\n");
    }
}
sticks.Append("</div>\r\n");
return sticks.ToString();

We end up by closing the outermost caretStickWrapper div and returning the output from the StringBuilder to the calling method. 

To make use of the finished project modify the web.config file based on the instructions in the macro guide article.  The syntax for this macro is:

<add macro="DasCaretStick" type="ThirstyCrow.DasBlogMacros.DasCaretStick, ThirstyCrow.DasBlogMacros" />

One other modification I had to make to the web.config file was in the trust setting.  To get this macro to work since it is issuing an HttpWebRequest under Medium trust I had to specify the originUrl as follows:

<trust level="Medium" originUrl=".*" />

Drop the ThirstyCrow.DasBlogMacros.dll file in your site's bin directory and add the macro to your template.

<%CaretStick("your twitter feed URL here", "false")|dascaretstick%> (see below)

And that should be it.  Another issue I ran into is that the control output does not change the document modified date of the default.aspx page on the server so browsers will simply show the cached version of the page even though new Twitter posts may be available.  I added a no-cache directive in the response header (code not shown above) using the SharedBasePage reference.  If anyone can think of a more elegant solution please let me know and I'll update the code and this post.  You can see a working version of this macro in the sidebar under the Microblog heading.

UPDATE: Twitter allows for 70 hits per minute max so caching the content of your feed is necessary if you get more than one or two visitors.  I've restructured the code and added a method to cache the results of the ParseTwitterFeed method.  DasBlog stores posts in a directory called "content".  Create a subdirectory of the "content" directory called "caretsticks" to hold the cached results.  (The code checks for the existence of the caretsticks directory and attempts to create it.  However, at least for me, a permissions error is thrown under medium trust when attempting to create the directory). The template code has changed based on a new argument that is passed in indicating how long you want the cache to live.  I personally chose 30 minutes.  The template code syntax should now look like this:

<%CaretStick("your twitter feed URL here", "false", "30")|dascaretstick%>

Despite looking at the dasBlog source and googling forever, I still can not figure out how to force the macro's literal control to refresh without adding the no-cache directive to the header.  I'll investigate more as time permits.

You can download the complete Visual Studio solution here: DasBlogMacros.zip (722.15 KB)
You can download the compiled dll here: CompiledDasBlogMacro.zip (3.39 KB)

New improved version from reader Hafthor Stefansson: HS_DasCaretStick.zip (2.20 KB)

Comments [4]     Categories: dasBlog Macro | Twitter