This post is intended to give a step-by-step example of linking D with a C library. If you want to mimic my windows development environment, to start with, you’ll need to download Tango and DigitalMars C. I used the below versions, but you may also want to check for the latest. I’m also toying with the idea of creating a Rack-style D http server, and coming up with a D web framework. Download dm850c.zip from here, and tango-0.99.6-bin-win32-dmd.1.029.zip from here.

Amusingly, i’d like to highlight that here we’re combining Ragel, which creates the Mongrel HTTP parser C code, which was intended for a Ruby web server, finally linking it with D.

Download this article’s code here
Make sure you read this first, too.

The (awesome) Mongrel HTTP parser

I grabbed parser.c and parser.h from Ebb, as i had problems with the Mongrel one. You can get it here.
Place the two files into a folder and run the following command to compile them with this command:
\dm\bin\dmc -c parser.c -I.
This will produce the parser.obj if you’ve installed dmc (Digitalmars’ C) correctly.

Bindings between C and D

To make bindings to D, I basically placed the contents of the parser.h into an ‘extern(C)’ block and deleted all the ‘const’ references, like so:

extern(C)
{
  enum { MONGREL_CONTENT_LENGTH
       , MONGREL_CONTENT_TYPE
       , MONGREL_FRAGMENT
       , MONGREL_HTTP_VERSION
       , MONGREL_QUERY_STRING
       , MONGREL_REQUEST_PATH
       , MONGREL_REQUEST_METHOD
       , MONGREL_REQUEST_URI
       };

  typedef void (*field_cb)(void *data, char *field, size_t flen, char *value, size_t vlen);
  typedef void (*element_cb)(void *data, int type, char *at, size_t length);

  struct http_parser {
    int cs;
    int overflow_error;
    size_t body_start;
    size_t content_length;
    size_t nread;
    size_t mark;
    size_t field_start;
    size_t field_len;
    size_t query_start;

    void *data;

    field_cb http_field;
    element_cb on_element;
  };

  void http_parser_init(http_parser *parser);
  int http_parser_finish(http_parser *parser);
  size_t http_parser_execute(http_parser *parser, char *data, size_t len, size_t off);
  int http_parser_has_error(http_parser *parser);
  int http_parser_is_finished(http_parser *parser);
}

Since mongrel uses a couple of call-back functions, they must also be within ‘extern(C)’ blocks:

extern (C) void http_field_cb(void *data, char *field, size_t flen, char *value, size_t vlen)
{ ...code here... }

extern (C) void on_element(void *data, int type, char *at, size_t length)
{ ...code here... }

The D Server

I’m using Tango in the simplest possible way to listen to port 80, feed the input to Mongrel, and return an html page. Here’s the entire serve.d:

import tango.io.Stdout;
import tango.stdc.stringz;
import tango.net.ServerSocket, tango.net.SocketConduit;

// Stuff for the mongrel parser
extern(C)
{

  enum { MONGREL_CONTENT_LENGTH
       , MONGREL_CONTENT_TYPE
       , MONGREL_FRAGMENT
       , MONGREL_HTTP_VERSION
       , MONGREL_QUERY_STRING
       , MONGREL_REQUEST_PATH
       , MONGREL_REQUEST_METHOD
       , MONGREL_REQUEST_URI
       };

  typedef void (*field_cb)(void *data, char *field, size_t flen, char *value, size_t vlen);
  typedef void (*element_cb)(void *data, int type, char *at, size_t length);

  struct http_parser {
    int cs;
    int overflow_error;
    size_t body_start;
    size_t content_length;
    size_t nread;
    size_t mark;
    size_t field_start;
    size_t field_len;
    size_t query_start;

    void *data;

    field_cb http_field;
    element_cb on_element;
  };

  void http_parser_init(http_parser *parser);
  int http_parser_finish(http_parser *parser);
  size_t http_parser_execute(http_parser *parser, char *data, size_t len, size_t off);
  int http_parser_has_error(http_parser *parser);
  int http_parser_is_finished(http_parser *parser);
}
// End of stuff for the mongrel parser

// My callback functions

extern (C) void http_field_cb(void *data, char *field, size_t flen, char *value, size_t vlen)
{
  char[]* display = cast(char[]*)data;
  *display ~= "<i>http_field_cb</i> <b>";
  *display ~= field[0..flen];
  *display ~= "</b> = ";
  *display ~= value[0..vlen];
  *display ~= "<br>";
}

extern (C) void on_element(void *data, int type, char *at, size_t length)
{
  char[]* display = cast(char[]*)data;

  char[] line;
  line ~= "<i>on_element</i> <b>";
  if (type==MONGREL_CONTENT_LENGTH) line ~= "MONGREL_CONTENT_LENGTH";
  if (type==MONGREL_CONTENT_TYPE) line ~= "MONGREL_CONTENT_TYPE";
  if (type==MONGREL_FRAGMENT) line ~= "MONGREL_FRAGMENT";
  if (type==MONGREL_HTTP_VERSION) line ~= "MONGREL_HTTP_VERSION";
  if (type==MONGREL_QUERY_STRING) line ~= "MONGREL_QUERY_STRING";
  if (type==MONGREL_REQUEST_PATH) line ~= "MONGREL_REQUEST_PATH";
  if (type==MONGREL_REQUEST_METHOD) line ~= "MONGREL_REQUEST_METHOD";
  if (type==MONGREL_REQUEST_URI) line ~= "MONGREL_REQUEST_URI";
  line ~= "</b> = " ~ at[0..length] ~ "<br>";
  *display ~= line;
}

void main()
{
  // Set up the server
  Stdout("Now open your browser to http://localhost/").newline();
  Stdout("Ctrl-Break or Ctrl-C to quit").newline();
  auto server = new ServerSocket (new InternetAddress(80));

  while(1)
  {
    // wait for requests
    SocketConduit request = server.accept;

    // wait for the 'http get ...'
    char[1024] response;
    uint len = request.read (response);
    Stdout(response[0..len]).newline; // you'll want to comment this out if you run apachebench against this...

    // parse it
    char[] display;
    display = "";
    http_parser parser;
    http_parser_init(&parser);
    parser.data = &display; // data that is sent to the callback function
    parser.http_field = &http_field_cb;
    parser.on_element = &on_element;
    http_parser_execute( &parser
                       , cast(char*)response
                       , len
                       , 0
                       );
    display ~= http_parser_has_error(&parser) ? "-mongrel error;" : "-no mongrel error;";
    display ~= http_parser_is_finished(&parser) ? "mongrel finished-" : "mongrel not finished-";;

    // send HTML
    request.output.write("HTTP/1.1 200 OK

<html>
<style>
  body {font-family:trebuchet ms;background:#524D4A;color:#fff;}
  .comment {font-size:80%;margin:0;color:#cccccc;}
  form {background:#6B7552; margin:20px 0 20px 0; padding:5px;}
</style>
<body>
  <h1>D + Mongrel's HTTP parser</h1>

  <form method='get'>
    <h2>GET form</h2>
    <input name='blah' value='test value'>
    <input type='submit'>
  </form>

  <form method='post'>
    <h2>POST form</h2>
    <input name='blah' value='test value'>
    <input type='submit'>
  </form>  

  <form method='post'>
    <h2>POST form with file</h2>
    <input type='file' name='blah'>
    <input type='submit'>
  </form>

  <form>
    <h2>Mongrel Parser Results:</h2>
    " ~ display ~ "
  </form>
  </body>
</html>");

    request.close();
  }
}

Compiling it all together

To compile it together, use the following command, which will produce serve.exe:
\dmd\bin\dmd serve.d parser.obj
Then open your web browser to http://localhost/ and you should see this:

Voila! That’s all until next time.

Summary:
The other day, I needed to access an oracle database from a C# console application, and google found a whole heap of different ways to achieve this. Here’s the best way i found that works, and you don’t even need to install any oracle software. I’ve deliberately kept this entry short, to emphasise that this is the simple and straightforward way.

Oracle bits:
Full credit: This part uses details from http://www.codeproject.com/KB/database/C__Instant_Oracle.aspx

You’ll need the following files, which can be obtained from within the ‘basic lite’ version of oracle’s instant client:
oci.dll
orannzsbb11.dll
oraocci11.dll
oraociicus11.dll
In my case, i downloaded the ‘instantclient-basiclite-win32-11.1.0.6.0.zip’ file from their website, the link is here.
You’ll need to place these DLL’s in the same folder as your application’s .EXE. If you’re using asp.net, put it in the ‘Bin’ folder of your application. The important thing to note is that you don’t need to install the instant client, or register these DLLs or anything, just grab them from the zip file and ignore the rest of its contents.

Visual Studio bits:
Note that i’m using .Net 2, with Visual Studio 2005. There may be differences in other versions, however i’m hoping for your sake that it is similar enough to figure out.
In the solution explorer, right click on ‘References’ and choose ‘Add Reference…’ as below:
Add Reference
Then scroll through the list until you find ‘System.Data.OracleClient’ and click OK.
Add Reference

Code:
Now for the code. You’ll need this ‘using’ at the top of your file:

using System.Data.OracleClient;

Here’s a simple function I wrote to create an oracle connection string, it may be useful to you:

public string OracleConnString(string host,string port,string servicename,string user,string pass)
{
  return String.Format(
    "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={0})" +
    "(PORT={1}))(CONNECT_DATA=(SERVICE_NAME={2})));User Id={3};Password={4};",
    host,
    port,
    servicename,
    user,
    pass);
}

And here’s the gist of opening a connection, and reading all the lines. You’ll notice the interface is pretty much the same as accessing SQL server.

string connectionstring = OracleConnString("aaa","1521","bbb","ccc","ddd");
string sql = "select * from some_table";

using (OracleConnection conn = new OracleConnection(connectionstring)) // connect to oracle
{
  conn.Open(); // open the oracle connection
  using (OracleCommand comm = new OracleCommand(sql, conn)) // create the oracle sql command
  {
    using (OracleDataReader rdr = comm.ExecuteReader()) // execute the oracle sql and start reading it
    {
      while (rdr.Read()) // loop through each row from oracle
      {
        Console.WriteLine( rdr[0] );             // You can do this
        Console.WriteLine( rdr.GetString(0); );  // or this
        Console.WriteLine( rdr["column_name"] ); // or this
      }
      rdr.Close(); // close the oracle reader
    }
  }
  conn.Close(); // close the oracle connection
}

That’s all!

Copying from Oracle to Sql Server
Here’s an example of copying a 2-column table from Oracle to Sql server. Lets pretend this table is called ‘foo’, and has two integer columns ‘x’ and ‘y’.

private void CopyOracleToSql()
{
  string connectionstring = OracleConnString("host","1521","servicename","user","pass");
  using (OracleConnection conn = new OracleConnection(connectionstring)) // connect to oracle
  {
    conn.Open(); // open the oracle connection
    string sql = "select x,y from foo";
    using (OracleCommand comm = new OracleCommand(sql, conn)) // create the oracle sql command
    {
      using (OracleDataReader rdr = comm.ExecuteReader()) // execute the oracle sql and start reading it
      {
        using (SqlConnection sql_conn = new SqlConnection(sqlconnstring)) // connect to the sql 2005 database
        {
          sql_conn.Open(); // open the sql database

          // Read the data from oracle, and every 100 rows send it to the sql database
          StringBuilder sb = new StringBuilder(); // build the insert statements with this
          int row = 0;
          while (rdr.Read()) // loop through each row from oracle
          {
            sb.AppendFormat(
              "insert into foo(x,y) values({0},{1});",
              rdr[0], rdr[1]);
            row++;

            if (row >= 100) // send to the DB every 100 rows
            {
              using (SqlCommand sql_comm = new SqlCommand(sb.ToString(), sql_conn))
              {
                sql_comm.ExecuteNonQuery();
                row = 0;
                sb = new StringBuilder();
              }
            }
          }

          // get the remaining few rows and send them to the DB
          if (sb.Length > 0)
          {
            using (SqlCommand sql_comm = new SqlCommand(sb.ToString(), sql_conn))
            {
              sql_comm.ExecuteNonQuery();
            }
          }

          rdr.Close(); // close the oracle reader
          sql_conn.Close(); // close the sql connection
        }
      }
    }
    conn.Close(); // close the oracle connection
  }
}

Firstly, a note: The aim of this article is more of a step by step ‘first steps with D’ kinda thing, i’m not trying to make too much of a point about D’s performance here. Calm down, redditors! ;)

Environment

I’m working with Windows XP here, which adds a few complications you wouldn’t see on other platforms.
Using the Digitalmars’ C compiler (dm850c.zip) from here, unzipped to c:\dm.
Also using the Tango D package (tango-0.99.6-bin-win32-dmd.1.029.zip) from here, unzipped to c:\dmd.

Libev

Libev is a high performance event loop that i’m using to wait for incoming port 80 connections.
It’s here: http://software.schmorp.de/pkg/libev.html
Docs are here: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
Download the CVS version (it contains fixes so it’ll compile) here: http://cvs.schmorp.de/libev.tar.gz?view=tar

Unzip it all into a folder, and create a ‘myev.c’ file in that folder, with the following contents:

#define EV_STANDALONE 1 // so i don't need a config.h file
#define EV_SELECT_IS_WINSOCKET 1 // windows
//#define EV_USE_POLL 1 // uncomment me for unix
//#define EV_USE_EPOLL 1 // uncomment me for gnu/linux
//#define EV_USE_KQUEUE 1 // uncomment me for bsd/osx
//#define EV_USE_PORT 1 // uncomment me for solaris
#define EV_STAT_ENABLE 0 // disable file watching
#include "ev.c"

To compile it, do the following from that folder (you’ll get a couple of warnings which you can ignore):

\dm\bin\dmc myev.c -c

This will produce a myev.obj which you’ll need later

Server

1: Create a new folder for the simple ‘D’ server. You’ll need the ev.d with the libev bindings, which you can download or copy n paste from here.

2: Copy the ‘myev.obj’ that you compiled earlier from the libev folder into this folder.

3: You’ll then create the ’server.d’ which is the main point of this article:

import tango.io.Console;
import tango.net.ServerSocket, tango.net.SocketConduit;
import ev; 

extern (C)
{
   static void libev_cb (ev_loop_t *loop, ev_io *w, int revents)
   {
     callback();
   }

  // http://msdn.microsoft.com/en-us/library/bdts1c9x(VS.71).aspx
  int _open_osfhandle (long osfhandle, int flags);
}

ServerSocket listener;
void main()
{
  listener = new ServerSocket (new InternetAddress(80));

  int fd = _open_osfhandle(listener.fileHandle,0); // for win32: convert from socket to file descriptor

  // Start libev
  ev_loop_t* loop = ev_default_loop(0);
  ev_io io_watcher;
  ev_io_init(&io_watcher, &libev_cb, fd, READ);
  ev_io_start(loop, &io_watcher);
  ev_loop(loop, 0);
} 

void callback()
{
  // accept the connection
  SocketConduit request = listener.accept;

  // wait for the 'http get ...'
  char[1024] response;
  uint len = request.input.read (response);
  Cout (response[0..len]).newline; // you'll want to comment this out if you run apachebench against this...

  // send HTML
  request.output.write("HTTP/1.1 200 OK

<html>
<style>body {font-family:trebuchet ms;background:#424242;color:#fff;text-align:center;margin-top:10em;}</style>
<body><h1>Your 'D' web server is alive!</h1></body>
</html>");

  request.close();
}

To compile and run all this, do the following:

\dmd\bin\dmd server.d ev.d myev.obj
server

Next test it in your browser, by browsing to ‘localhost’:

D Server is alive

Voila! And if i do an apachebench (ab -n 10000 -c 100), my requests-per-second is 2071 for an 2 years old P4-3ghz, and it only uses 2.4mb of memory, which is pretty impressive considering each Ruby on Rails Mongrel typically uses ~30megs last time i checked.
I guess the next question is: why doesn’t someone make a super-scalable web framework using the speed/efficiency and ease of D?

If any of this doesn’t work, make sure you post a comment so i can sort it out - cheers.

Travelling from Rails 1 to Rails 2

I’ve recently been working on converting my ‘rosters’ rails 1.x app to rails version 2, and there are plenty of pitfalls that i spent lots of time googling around trying to find the solutions.
So, for just as much need for my own future reference as anyone else’s, here are all the specific traps *I* ran into, and their solutions, in the one place.
If you’re interested in the source code to the whole app, you can see the rails 1.x version here:
http://rosters.rubyforge.org/
The new version is running here, and if enough people request it, i’ll release the code.

Basics

Firstly, I created a new rails 2 skeleton app, using ‘rails myappname’ from the command line. Then i copied the contents of all my app and public folders over from the old app to the new one. Finally i went through the db and config directories, modifying the new files manually, rather than simply overwriting them with the old ones. This way you’ll get to see how the configuration files (especially routes!) have changed, and won’t be overwriting them with legacy code (it feels strange to refer to rails 1 as ‘legacy’!)

What? Rails is embracing security now?

Forms now have a special ‘key’ that needs to be present for POSTs, to prevent cross-site-request-forgery (try saying that 10 times fast!). The gist of it is that you can’t just have code like this in your views any more:

<form name="f" action="/account/login" method="post" onsubmit="wait();">
...
</form>

Now what you want is more like this:

<% form_tag("/account/login", :name=>'f', :onsubmit=>"wait();") { %>
...
<% } %>

The above code takes care of the new CSRF-proof keys and all that. As you can see, my example is a bit more complex than usual, with a form name and an onsubmit javascript callback, but hey, its real code!
For a simpler, bare-bones form, Rails 2 style, here you go:

<% form_tag :action=> "new" do %>
...
<% end %>

Where on earth did ‘find_first’ go?

Back in controller-land, i hit problems with all my find_first’s that i like to use. This kind of thing simply wont work any more:

u = find_first(["login = ? AND password = ?", login, sha1(pass)])

You need something more like this nowadays:

u = find :first, :conditions=>["login = ? AND password = ?", login, sha1(pass)]

It appears that all the find_* functions (find_first,find_all,etc) have all been rolled into the one-function-to-rule-them-all ‘find’ function. I guess it is neater.

No more @session or @request

This one’s simple. Simply replace these kind of things:

@session
@request

With this:

session
request

One big ‘find-in-files’ (Ctrl-Shift-F in Notepad++) will sort you out with this one.

Redirect

Another simple one, but its likely you’ll have one of these in each of your updating actions:

redirect_to_url '/blah/foo'
...becomes...
redirect_to '/yada/yada'

RIP Pagination

Apparently pagination wasn’t quite good enough to meet muster for Rails 2, so its been scrapped totally.
There’s no simple replacement for this one, but hopefully you’re only really using it in your admin scaffolds, so what i did was a simple pagination-dectomy in my controllers:

@user_pages, @users = paginate :users, :per_page => 30, :order=>'login'

Became:

@users = User.find :all, :order=>'login'

Its pretty simple, and you’ll need to remove all references to pagination in your views. If you really *really* need pagination, you’ll need something more complex, but there isn’t anything out-of-the-box for you.
For more details: http://wiki.rubyonrails.org/rails/pages/HowtoPagination

End Results

Well, now the site’s up and running, you’re all welcome to have a look:
http://rosters.morphexchange.com/

DanceInforma

DanceInforma is a web-zine devoted to dancers (hence the name).
My role consists of maintaining the back end database involved with maintaining the membership details of all members, ~3-4000 at last count.
http://danceinforma.com/

Client Analyser

FreelanceSwitch contracted me to develop their Client Analyser, which is publicly accessible here:

http://clientanalyser.freelanceswitch.com/

This is another Rails application, like most of my public work.

Online Rostering System

Here is the online rostering system in its current evolution:

http://rosters.morphexchange.com/

Source code to an earlier version can be found here:
http://rosters.rubyforge.org/

There’s a bit of a story behind this one:

An Online Rostering System for cafe’s / small shops / anywhere that needs a roster. Has a manager’s login to make the rosters with, and a user’s login where they can look at their roster for any given week.

This is an online rostering system that is aimed at cafe’s, small shops, any place really that has a roster and wishes to make it accessible to their employees via the web. The point is, that they won’t have to come in or ring up to check their roster. Having worked in retail when younger, I can relate that this can be a problem.

This application was written for a client, but never ended up being used or paid for. So i decided to donate it to the community under the GPL for learning purposes.

This is a fully functional application that is aimed to be of use as a learning application for new Rails users, but there’s nothing to stop you using it as your roster!

So here it is, a nice full application in Rails for all you newbies to have a look at and hopefully learn from!

Rocketsale

Here’s an oldie:

http://rocketsale.com.au

This is a basic online classifieds site, created by me as an exercise in using Rails.

How good is this???

I likey…

http://rubyrags.com/products/2

Here’s a novel use for RSS / Atom: providing a feed of up-to-the-minute
information from your intranet database. This is so that right on your desktop you can see,
as an example, how many sales have been made today, or whatever is applicable to
wherever you work. Plus: this will score you some points with management if you set it up
so they can see the feed from their desktop, with any luck.
As a rule, people in power love statistics. It’ll give them a way of ‘keeping a finger on the pulse’.

First up, to keep you interested, here’s the net result we’re after:

Rss Widget

The idea is that every 5 minutes, this thing will refresh, giving you an up-to-date
snapshot of how business is going. Here are some ideas:

  • Sales made
  • Dollars in the till
  • Progress on long-running tasks
  • Number of hits on your website
  • Number of outstanding support tickets

First up, you’ll want to create a program that’ll reach into your database, grab
some statistics, and generate RSS output. As a refresher, RSS output looks like this:

<rss version="2.0"
  xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>My statistics feed</title>
    <link>http://abc/</link>
    <description>My statistics feed</description>
    <item>
      <title>Today's Orders: 523</title>
      <link>http://abc</link>
      <dc:date>2007-01-01T12:00:00</dc:date>
      <description>Today's Orders: 523</description>
    </item>
  </channel>
</rss>

I use a short C# console-mode application for this purpose.
What it does is connect to the database, pull some statistics down, and spit them out in RSS format.
Note that i’m displaying the time spent generating the statistics, and i’m using (nolock) in the SQL statements.
This is so that i can quickly check that my queries aren’t using too much of the SQL server’s time, and so
that i’m not locking the tables.
You could write this application in any language, really.

using System;
using System.Collections;
using System.Text;
using System.IO;using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;

class Rss
{
  ArrayList arr;
  public Rss()
  {
    arr = new ArrayList();
  }
  public void Add(string title)
  {
    arr.Add(title);
  }
  public void AddFirst(string title)
  {
    arr.Insert(0, title);
  }
  public string Render()
  {
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("<rss version=\"2.0\"" +
     " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">");
    sb.AppendLine("  <channel>");
    sb.AppendLine("    <title>My Statistics</title>");
    sb.AppendLine("    <link>http://abc/</link>");
    sb.AppendLine("    <description>My Stats</description>");
    foreach (string s in arr)
    {
      sb.AppendLine  ("    <item>");
      sb.AppendFormat("      <title>{0}</title>", s);
      sb.AppendLine();
      sb.AppendFormat("      <link>http://abc/</link>", DateTime.Now);
      sb.AppendLine();
      sb.AppendFormat("      <dc:date>{0:yyyy-MM-ddTHH:mm:ss}</dc:date>",
        DateTime.Now);
      sb.AppendLine();
      sb.AppendFormat("      <description>{0}</description>", s);
      sb.AppendLine();
      sb.AppendLine("    </item>");
    }
    sb.AppendLine("  </channel>");
    sb.AppendLine("</rss>");
    return sb.ToString();
  }
}
class DataPull
{
  public Rss rss;
  DateTime dttm;
  SqlConnection conn;
  public DataPull()
  {
    rss = new Rss();
  }
  public void Execute()
  {
    string connstring =
      "Persist Security Info=False;uid=XXX;pwd=YYY;Initial Catalog=AAA;Server=BBB;";
    conn = new SqlConnection(connstring);
    conn.Open();
    DateTime dttm_start = DateTime.Now;
    string sql;
    sql = @"select count(1) from sales(nolock)";
    GetValue(sql,"Sales Made: {0}");
    sql = @"select sum(value) from sales(nolock)";
    GetValue(sql, "Sales Value: {0:c}");
    DateTime dttm_end = DateTime.Now;
    TimeSpan period = dttm_end-dttm_start;
    rss.AddFirst(
      String.Format("Details as at: {0:d/M/yyyy h:mm tt} ({1:0.0}s)",
        dttm_start, period.Milliseconds / 1000.0));
    conn.Close();
  }
  private void GetValue(string sql,string name)
  {
    rss.Add(String.Format(name, ExecuteScalar(sql)));
  }
  private object ExecuteScalar(string sql)
  {
    SqlCommand comm = new SqlCommand(sql, conn);
    return comm.ExecuteScalar();
  }
}
class Program
{
  static void Main(string[] args)
  {
    DataPull dp = new DataPull();
    dp.Execute();
    Console.WriteLine(dp.rss.Render());
  }
}

Once you’ve got your application that grabs your statistics for you, you’ll want
to schedule it to run every half an hour, and redirect the output to a spot on your
web server. What i did, was to create a folder on the web server called ‘/rss’, and
redirect the output from this program to the index.htm in that folder.

To get you started, here is a two line batch file that i made to do this:

c:\rssgenerator\rssgenerate.exe > c:\webroot\rss\index.tmp
copy /y c:\webroot\rss\index.tmp c:\webroot\rss\index.htm

And here is the command used to schedule this batch to run every 5 minutes:

schtasks /create /tn rss /ru system /sc minute /mo 5 /tr c:\rssgenerator\go.bat

Okay, you should now have an RSS feed being generated from your database every 5 minutes
with up-to-date statistics. Next you’ll need an RSS reader, and just point it at your web server!

I personally install Yahoo widgets, and install the ‘NewsStand’ widget, because its simple, clean,
and can run in the background. Plus it looks good! Make sure to set it to update every 5 minutes. And you
may have to play with Yahoo widget’s proxy settings to make it work in an intranet like my situation.

Now all this is done from the perspective of an intranet, using SQL server and IIS
on Windows servers, but the general ideas will all transfer to a Unix/GNULinux setup too
with minor changes.

Drop me a line (or comment) and let me know if this idea works for you!