Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Folding@Home User Stats Gadget for Vista

3.75/5 (3 votes)
29 Mar 20075 min read 1   120  
An article on a Gadget for monitoring Folding@Home user statistics
Screenshot - folding_gadget.jpg

Introduction

The Folding@Home User Stats Gadget for Vista accesses the XML data feed from ExtremeOverClockers' folding website. It formats the XML in a fashion similar to the sigimages provided by the EOC site.

Background

Folding@Home is a distributed computing project run by the Pande Group at Stanford University. You can find more information at the project website.

Using the code

The code provided was originally written for use in Vista Gadgets, and so can be used like any other Vista Gadget. Functionally, it is a simple XML parser written in JavaScript, which gathers, identifies, and assigns values to the appropriate locations in the HTML.

It was designed to be portable; as such, the Gadget Settings paradigm is encapsulated and can be replaced with other settings storage methodologies. Two ideas already have been to embed this into Google's desktop widgets, or to implement it for Konfabulator.

The visual representation is pretty simple - it uses a table to organize the content that we'll eventually fetch from the XML data source. The items in the table were named after the values expected to be found in the XML. The background image is a cropped & alpha-ed version of an image found on the Folding@Home website.

HTML
<body background="fah.png" onload="showXML();interval = setInterval('showXML()', 900000);" 
                    onunload="clearInterval(interval);">
<table id="gadgetContent">
    <tr>
        <th colspan=2><b><span id="User_Name" /></b></th>
    </tr>
    <tr>
        <td><u>Team Rank</u>:<br><span id="Team_Rank" /></td>
        <td><u>Points</u>:<br><span id="Points" /></td>
    </tr>
    <tr>
        <td><u>Change 7d</u>:<br><span id="Change_Rank_7days" /></td>
        <td><u>24hr Avg</u>:<br><span id="Points_24hr_Avg" /></td>
    </tr>
    <tr>
        <td><u>User Rank</u>:<br><span id="Overall_Rank" /></td>
        <td><u>Today</u>:<br><span id="Points_Today" /></td>
    </tr>
</table>
<span id="errorText" />
</body>

I used setInterval rather than setTimeout. There wasn't a performance difference, and I liked the name more.

I think error handling is important, so I'm going to mention that prior to the actual functionality.

The error handling for the Gadget was managed by the main process, showXML(), but there were some helper functions for managing the interface. Also, the helper function for getting the Gadget settings does not handle its own errors. Instead, it passes the buck back to the main process if it identifies any problems. I appropriated the RangeError exception for this, but not for any good reason.

JavaScript
function setGadgetProperties()
{
    username = System.Gadget.Settings.read("username");
    teamID = System.Gadget.Settings.read("teamID");

    if ( username.length == 0 || teamID.length == 0)
        throw RangeError("Have you set your options?");
}

function displayError(message)
{
    errorText.innerHTML = message;
    gadgetContent.style.display = "none";
}

function clearError()
{
    errorText.innerText = "";
    gadgetContent.style.display = "";
}

The primary code block in ShowXML() is wrapped completely within a try block. The catch is below. Given that the target audience for this Gadget may or may not have any particular technical prowess, I wanted to keep the messages simple and to the point, but clear as to what's likely happening.

JavaScript
if ( e instanceof TypeError )
{
    if ( e.message.indexOf("documentElement") >= 0 )
        displayError("Did you spell your name right?");
    else if ( e.message.indexOf("resource") >= 0 )
        displayError("Are you connected to the internet?");
    else
        displayError("Quick, tell someone!<br>" + e.message);
}
else if ( e instanceof RangeError )
    displayError(e.message);
else
    displayError("Something's wrong.<br>Restart this gadget instance.");

With that out of the way, let's look at ShowXML(). We'll approach it in 3 parts.

Part 1: Get the data

Jason over at EOC does a regular pull from the Folding@Home reporting servers, collating the data into something usable. He has made an XML data source available to folks, so that's what we're leveraging for this Gadget. To get the data our user wants to see, we need to get our user's input first, so that we can tell Jason what information we want.

There's nothing much tricky going on here, but see Point of Interest #1 to learn something you might not know about MSXML2.XMLHTTP.

JavaScript
//capture username & team from settings
setGadgetProperties();

clearError();

//get the data stream from EOC
xmlhttp.open("GET", <a href="">http://folding.extremeoverclocking.com/xml/user_summary.php?un=</a> 
            + username + "&t=" + teamID,false);
xmlhttp.send(null);

Part 2: Organize the data

The XML returns with Team and User information. I was only interested in the User information, so that's all I captured. But there's no reason why someone could not also capture the Team info, and one could easily write a similar insertTeamXML() function to do the same thing for a team table.

JavaScript
//create the base XML doc
xmldoc = xmlhttp.responseXML;

//capture only the user XML
userxml.async = false;
userxml.loadXML(xmldoc.documentElement.getElementsByTagName("user")[0].xml);

//fill in the table
insertXML(userxml.documentElement);

Part 3: Display the data

insertUserXML() encapsulates the manipulation of the gadgetContent table. Note: If there's a better way to capture the data in the XML based on the span IDs in the HTML, I'd love to hear it - the more flexible, the better.

JavaScript
//set username & username size
User_Name.innerText = data.getElementsByTagName("User_Name")[0].text;

if ( User_Name.innerText.length < 10 )
    User_Name.style.fontSize = 'medium';
else if ( User_Name.innerText.length < 16 )
    User_Name.style.fontSize = 'small';
else if ( User_Name.innerText.length < 22 )
    User_Name.style.fontSize = 'x-small';

//fill in data
Team_Rank.innerText = data.getElementsByTagName("Team_Rank")[0].text;
Points.innerText = data.getElementsByTagName("Points")[0].text;
Change_Rank_7days.innerText = data.getElementsByTagName("Change_Rank_7days")[0].text;
Points_24hr_Avg.innerText = data.getElementsByTagName("Points_24hr_Avg")[0].text;
Overall_Rank.innerText = data.getElementsByTagName("Overall_Rank")[0].text;
Points_Today.innerText = data.getElementsByTagName("Points_Today")[0].text;

//tweak data
if ( Change_Rank_7days.innerText.charAt(0) != '-'
        && Change_Rank_7days.innerText.charAt(0) != '0')
{
    Change_Rank_7days.innerText = "+" + Change_Rank_7days.innerText;
}

In all current cases (though the PS3 users might prove this wrong eventually...), the pre-set font size for all of the values worked okay. However, since usernames are extremely variable, I had to make sure that the username would display properly; to that end, I hacked a "best guess" for name sizes. So far, it looks like I guessed pretty well.

Also, I wanted to emulate the +/- display for the Change_Rank_7days that happens in the forum sigimages that EOC can generate. This was purely aesthetic, but I think it's something that would have been missed if it wasn't included.

The Settings Page is so utterly boring, it's barely worth a mention. That's why I left it for the end. However, it's the place where the Vista-specific coding is happening, so we'll glance at it.

JavaScript
<body onload="showSettings();">
    <label for="username">Username:</label><br>
    <input type="text" name="username" id="username" length="40" /><br>
    <label for="teamID">Team ID#:</label><br>
    <input type="text" name="teamID" id="teamID" length="40" />
</body>

The body is just two fields for user input. When the Gadget is first launched, the users will have to come here first to set up their information. Once the information is set, however, as long as they don't manually close the Gadget, the information will be maintained on any subsequent shutdowns or reboots. See Point of Interest #2 for more information.

The script below simply shows how the Gadget settings are loaded when the window opens (see the onload event from the HTML body), and saved when the window is closed (note the event handler assignment at the top.

JavaScript
System.Gadget.onSettingsClosing = settingsClosing;

function showSettings()
{
    username.value = System.Gadget.Settings.read("username");
    teamID.value = System.Gadget.Settings.read("teamID");
}

function settingsClosing(event)
{
    if (event.closeAction == event.Action.commit)
    {
        System.Gadget.Settings.write("username", username.value);
        System.Gadget.Settings.write("teamID", teamID.value);
    }
}

Points of Interest

  1. MSXML2.XMLHTTP objects will cache their results. This can be good AND bad, since cached might cause confusion when a user's internet service provider is inaccessible.
  2. Vista stores a list of the configurations for the currently-running Gadgets in a Settings.ini file in the Windows Sidebar directory. These settings will be specific to every user. This is likely the physical location from which the System.Gadget.Settings interface reads/writes.

History

  • 1.1.1 - Minor modifications for CodeProject - no functional changes
  • 1.1.0 - Improved error handling (thanks to totoro for trial & feedback!)
  • 1.0.0 - Launched to TechReport's Distributed Computing Forum (go Team 2630!)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here