Xdrive as Data Storage Device for Windows Vista Gadgets

Mark Blomsma
November 5, 2007

Introduction

AOL Open Xdrive (Xdrive) is the always-on, always-there online storage facility offered by AOL. Xdrive gives you 5 gigabytes (GB) of online data storage, completely free of charge! All you need to do is sign up by creating an account, and you're in business. Aside from an online interface that lets you browse the web and upload and download files to the online storage space, Xdrive also offers an application programming interface (API) that you can use to access, manage, upload, and download files from within your own application.

In this article, I'll show you how to create a sample Windows Vista Sidebar gadget I call the xFavorites gadget, which lets you manage a list of favorites--that is, your list of favorite web sites--and synchronize the favorites with your Xdrive account so you can move your favorites across multiple computers. I'll also show you the challenges involved with signing in, retrieving folders, and--the biggest challenge of all--uploading your favorites file from within the Windows Vista gadget to Xdrive.

Basic knowledge of Windows Vista gadgets is assumed. The gadget user interface will be fairly basic, so you can use your own imagination to add to and improve upon this sample.

The Windows Vista Gadget

For the example in this article, I created a Windows Vista gadget called xFavorites. The purpose of the gadget is to manage a list of favorite URLs and store the list in your Xdrive account. This means that if you install the gadget on your personal computer at home, and also on your work computer, you can share your list of favorites and move it between computers. Fair warning: The sample is all about the code, and not about the look and feel of the gadget.

When the work is done, the gadget will look like this:

Tip You might want to first install the gadget and try it out to get a clear understanding of what it offers. You can download the gadget here.

Sidebar gadgets in Windows Vista are created using HTML. In the preceding image, the area on the gadget's upper right side is docked in the Sidebar. The xFavorites.html file creates this part of the gadget. The area where all the action happens is the flyout window, the left pane, which is implemented in FlyOut.html.

When you're developing a gadget for Windows Vista, debugging can get tricky. Therefore, I recommend building a primitive first version. Install the gadget, and then continue development on the installed version. This way, you can easily view the results of your labor, and testing.

To make the flyout window visible when you double-click the docked gadget, call the System.Gadget.Flyout object. This object is available in the JavaScript environment within the Windows Vista Sidebar gadget. Following is the executed code when the gadget starts. (The code calls the init() method in the onload event of the xFavorites.html page.)

function loadSettings()
{
	_username = System.Gadget.Settings.read("username");
	_password = System.Gadget.Settings.read("password");
}

function init()
{
	// wire up the settings page
	System.Gadget.settingsUI = "settings.html";
	System.Gadget.Flyout.file = "flyout.html";
	loadSettings();
	if ( _username == "" )
	{
		divMessage.innerText = 
"Use the settings page to setup your Xdrive account.";
	}
	else
	{
		divMessage.innerText = 
"Double Click in this area to view and manage your favorites.";
	}
}

function flyout()
{
    System.Gadget.Flyout.show = true; 
}
function flyin()
{
    System.Gadget.Flyout.show = false;   
}

function toggle()
{
    if (System.Gadget.Flyout.show)
	flyin();
    else
      flyout();
}

In the preceding code, you use the System.Gadget object to specify both the settings page and the flyout page. The username and password are read from the gadgets settings.

Notice that the username and password are not encrypted, as I explained in my blog post on October 3rd.

The Settings Page

The settings page is similar to the one I developed for the Xdrive usage meter gadget. In fact, I simply copied the code from this project, so if you're starting out with a new gadget you might want to do the same.

The two essential lines of code in settings.html are:

System.Gadget.Settings.write("username", username);
System.Gadget.Settings.write("password", password);

This code stores the username and password in the gadget settings file.

The Flyout Page

As I mentioned, the main action all happens on the flyout page, which reloads every time you double-click the docked gadget. The code in flyout.html contains the layout of the page, and xFavoritesFlyOut.js contains all the JavaScript needed to interact with the Xdrive services. In the onload event of the html page, the code calls the init() method in the JavaScript file to start the processing.

Let's look at the init() method:

function init()
{
	// Load local data and display
	initXMLDataIsland();

	// Load Xdrive user profile from gadget settings.
	// debugger;
	loadSettings();

	// The user has entered settings, let's logon and 
// load the user data (includes a session)
	loadXdriveUserData(_username, _password);
	
	// After favorites have been displayed, download file
	downloadFile();
	
	// Re-init and display favorites after 
// latest version of XML has been downloaded
	initXMLDataIsland();
	
}

The init() consists of 5 steps:

  1. Load local data and display favorites.
  2. Load settings (username and password) from gadget settings.
  3. Log on and load Xdrive user profile; this includes getting a valid jsessionid.
  4. Download file with favorites to local file.
  5. Load local data and display favorites.

Step 1 and 5 are the same. The reason for loading the local data first is that in a scenario in which perhaps your laptop is not online, you would still want to be able to view the last version of your favorites file.

Note that error handling in this sample is primitive or nonexistent. For a more robust offline experience more error handling is certainly needed.

Let's look at each step in more detail.

Load Local Data and Display Favorites

To load an XML file with favorites information, you first use an XML data island. Then, you parse the loaded XML and build a list of links.

The XML looks like this:

<xFavorites>
	<favorite title="AOL" url="http://dev.aol.com/blog"/>
	<favorite title="Develop-One" url="http://www.develop-one.com"/>
	<favorite title="MSDN" url="http://msdn.microsoft.com"/>
</xFavorites>

In the initXMLDataIsland, you tell the gadget to load the data from a local folder--namely, the folder where the gadget is installed. You get a reference to this folder using the System.Gadget.path property. Because the gadget runs under Full Trust, you will have access to this folder and be able to both read and write to this file.

function displayFavorite( node )
{
    divFavorites.innerHTML += '<a href="' + 
node.attributes[1].nodeValue + '">' +
node.attributes[0].nodeValue + '</a><br/>';
}

function displayFavorites()
{
    divFavorites.innerHTML = '';
    var nodes = dsoFavorites.selectNodes( '/xFavorites/favorite');
    for( i = 0; i < nodes.length; i++ )
    {
        displayFavorite( nodes[i] );
    }
}
    
function initXMLDataIsland()
{
    var gadgetPath = System.Gadget.path + '\\';
    var filename = gadgetPath + "xFavoritesData.xml";
    dsoFavorites.async = false;
    dsoFavorites.load( filename );
    displayFavorites();
}
Load Settings

Loading settings is similar to the settings actions performed in the settings.html file. The username and password are stored in global variables for later use.

function loadSettings()
{
	_username = System.Gadget.Settings.read("username");
	_password = System.Gadget.Settings.read("password");
}

Log On and Load Xdrive User Profile

The loadXdriveUserData method uses a core element of Asynchronous JavaScript + XML (Ajax), the XMLHttpRequest object, to create a request and log on to Xdrive. The result is a UserData object. This object contains a sessionid, which is essential because all subsequent API calls need to reference this sessionid.

function loadXdriveUserData( username, password )
{
	// Member Login URL
	var url = "http://plus.xdrive.com/json/v1.2/member.login";
	
	// Pass JSON Member Object Data
	var postData = 'data={"user":{"type":"MemberObject","username":"' + username + 
'","password":"' + password + '"}}'; // Create XMLHttpRequest Object var xmlRequest = new XMLHttpRequest(); // Open a POST request - false indicates we will do this synchronously xmlRequest.open("POST", url, false); // Set Headers xmlRequest.setRequestHeader("Cache-Control", "no-cache"); xmlRequest.setRequestHeader('Content-type','application/x-www-form-urlencoded'); // Post Data xmlRequest.send(postData); // Check for successful POST if (xmlRequest.status == 200) { // Evaluate JSON userdata _xdriveUserData = eval( "(" + xmlRequest.responseText + ")" ); } }
Download File with Favorites to Local File

The next step is to download your favorites file from Xdrive. The first time you run this gadget, there will be no file, so expect this part of the code to fail. This failure is not a problem because we have already put the default list of favorites on display.

function downloadFile()
{
	// get file info for xFavoritesData.txt
	getFavoritesData();

	// Member Login URL
	var url = 'http://plus.xdrive.com/json/v1.2/io.download';
	
	// Pass JSON Member Object Data
	var json ='{"toDownload":{"type":"FileObject","id":"' +
 _xdriveFavoritesData.id + '"}}'

	// build complex requestbody
	var boundary = '------------7d72b92a170f1a';
	var requestbody = boundary + '\r\n'; 
	
	requestbody += 'Content-Disposition: form-data; name="data"' + 
			   '\r\n\r\n';
	requestbody += json + '\r\n';
	requestbody += '\r\n';
	requestbody += boundary + '--';

	// Create XMLHttpRequest Object
	var xmlRequest = new XMLHttpRequest();

	// Open a POST request - false indicates we will do this synchronously
	xmlRequest.open("POST", url, false);

	// Set Headers
	xmlRequest.setRequestHeader("Content-type", 
"multipart/form-data; boundary=----------7d72b92a170f1a");

	// Set Cookies
	// this value is ignored, 
// but the step is necessary 
// (bug: http://support.microsoft.com/?id=234486)
	xmlRequest.setRequestHeader("Cookie", "any non-empty string here");
	// set all cookies here
	var cookie = "JSESSIONID=" + _xdriveUserData.jsessionid;
	xmlRequest.setRequestHeader("Cookie", cookie);

	// Post Data
	xmlRequest.send( requestbody );

	// Check for successful POST
	if (xmlRequest.status == 200) 
	{
        	saveToLocalFile( xmlRequest.responseText );
	}
}

Again, using XMLHttpRequest, post a JSON object to the Xdrive service--this time using the relevant parameters to retrieve the file. Retrieving a file happens by passing a unique file ID to the IO.Download service. However, first you need to find the file. To do this, you need to traverse your Xdrive and find the right file. The following code shows how to retrieve a folder listing from Xdrive.

function getFolderListing( id )
{
	// Member Login URL
	var url = "http://plus.xdrive.com/json/v1.2/file.getlisting";
	
	// Pass JSON Member Object Data
	// var postData = 'data={"method":"file.getlisting","params":{"srcFile":{"type":"
FileObject","id":""}}}'; var postData = 'data={"srcFile":{"type":"FileObject","id":"' + id + '"}}'; // Create XMLHttpRequest Object var xmlRequest = new XMLHttpRequest(); // Open a POST request - false indicates we will do this synchronously xmlRequest.open("POST", url, false); // Set Headers xmlRequest.setRequestHeader("Cache-Control", "no-cache"); xmlRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded"); // this value is ignored, but the step is necessary (bug: http://support.microsoft.com/
?id=234486) xmlRequest.setRequestHeader("Cookie", "any non-empty string here"); // set all cookies here var cookie = "JSESSIONID=" + _xdriveUserData.jsessionid; xmlRequest.setRequestHeader("Cookie", cookie); // Post Data xmlRequest.send(postData); // Check for successful POST if (xmlRequest.status == 200) { // Evaluate JSON folderdata return eval( "(" + xmlRequest.responseText + ")" ); } }

After downloading the file, you overwrite your old favorites file. Again, this is possible because the gadget runs in Full Trust. The code will also run in a regular web browser, but the browser will ask the user whether the HTML page is allowed to create an ActiveX control.

Note that the System.Gadget namespace is available only in your Windows Vista Sidebar environment--not in a regular web browser.

function saveToLocalFile( data )
{
	// get the path to the gadget, this will be a user accessible folder
	var gadgetPath = System.Gadget.path + '\\';
	var filename = gadgetPath + "xFavoritesData.xml";

	// create a filesystemobject in order to delete any old data
	var fso = new ActiveXObject("Scripting.FileSystemObject");
	if ( fso.FileExists( filename ) )
	{
		fso.DeleteFile( filename, true );
	}

	// save file to temporary XML file in the gadget folder
    var file = fso.CreateTextFile( filename, true );
	file.Write( data );
	file.Close();
}

After downloading the file, you can reload the local file and update the display.

Adding Favorites

In this article, I won't go into the code for adding a favorite, but suffice it to say that the XML in the data island gets updated. After adding a favorite, you'll need to upload your favorites file again. This is where things get interesting.

Uploading Files Using Ajax

Research on the Internet suggests that uploading files using Ajax is impossible. But the reason is that a regular web browser will not have access to the local file system. Because this Windows Vista gadget is running in the Vista Sidebar and not in a regular browser, you don't have this restriction, so you can defy browser restrictions and use XMLHttpRequest to upload a file.

function uploadFile()
{
	// reload user data in order to make sure 
// we've still got a valid jsessionid
	loadXdriveUserData( _username, _password );

	// get folder listing for My Documents folder
	getMyDocumentsFolder();	

	// Member Login URL
	var url = 'http://plus.xdrive.com/json/v1.2/io.formupload';
	
	// Pass JSON Member Object Data
	var json ='{"destFolder":{"type":"FileObject","id":"' +
     _xdriveMyDocumentsFolder.id + 
	'"},"statusToken":{"type":"StatusObject","token":"0.00908775924407874"}}'

        var data = loadXFavoritesData();

	/* NOTE: THE BOUNDARY HAS TWO MORE THAN THE BOUNDARY IN THE HEADER */
	// build complex requestbody
	var boundary = '------------7d72b92a170f1a';
	var requestbody = boundary + '\r\n'; 
	
	requestbody += 'Content-Disposition: form-data; name="data"' +
   '\r\n\r\n';
	requestbody += json + '\r\n';

	requestbody += boundary + '\r\n'; 
	requestbody += 'Content-Disposition: form-data; name="files0"; filename="
xFavoritesData.txt"' + '\r\n'; requestbody += 'Content-Type: text/xml' + '\r\n'; requestbody += '\r\n'; requestbody += data; requestbody += '\r\n'; requestbody += '\r\n'; requestbody += boundary + '--'; // Create XMLHttpRequest Object var xmlRequest = new XMLHttpRequest(); // Open a POST request - false indicates we will do this synchronously xmlRequest.open("POST", url, false); // Set Headers /* NO DASHES OR TWO LESS THAN THE ONE IN THE MULTIPART */ xmlRequest.setRequestHeader("Content-type", "multipart/form-data; boundary=----------7d72b92a170f1a"); // Set Cookies // this value is ignored, // but the step is necessary // (bug: http://support.microsoft.com/?id=234486) xmlRequest.setRequestHeader("Cookie", "any non-empty string here"); // set all cookies here var cookie = "JSESSIONID=" + _xdriveUserData.jsessionid; xmlRequest.setRequestHeader("Cookie", cookie); // Post Data xmlRequest.send( requestbody ); // Check for successful POST if (xmlRequest.status == 200) { // Evaluate JSON folderdata result = eval( '(' + xmlRequest.responseText + ')' ); return result; // this is a file object } }

The preceding code shows the creation of an XMLHttpRequest object, which then proceeds to create a multipart/form-data request body. Doing this requires two things that you are likely to mess up:

  1. The end of line characters must be /r/n.
  2. There must be 2 dashes less in the header definition of the boundary than on the body of the request.

Certainly the second item is not obvious, and it caused me quite a headache; thanks to Maria Vazquez from AOL for helping me find these two requirements.

Wrap-Up

The example in this article has accomplished a variety of things: It created a Windows Vista gadget; it gave it a flyout window; it created JavaScript code to log on to Xdrive using a JSON object; it retrieved folder and file information from your Xdrive account; and it downloaded a file and uploaded a file. You saw that error handling can definitely be improved in this sample, but by caching your favorites file on the local drive, you can even offer an offline experience.

Downloads

Download the gadget here.
Download the source code as a zip file here.

vista

I have vista!
Bst Rgds,
Michael B.