By Brice Mason
February 6, 2008
Introduction
AOL's Xdrive Data Service Platform (XDSP) is a complete virtual storage system you can use to enhance the services you offer to your users. Not only does it provide you with 5 gigabytes (GB) of user-allocated storage to start (and more, if you want), Xdrive also includes a set of services you can use to create, manage, and support a unique experience for your user base. This article will walk you through the development of an Xdrive client utility developed using Yahoo! User Interface Toolkit (YUI), a popular JavaScript library used to enable rapid application development. By the time you're finished, the client you build will support many of the most popular and useful functions of the Xdrive platform, including uploading and downloading file assets, core file and contacts management, and share administration. Because the application depends on the development of a server proxy, familiarity in a server-side web development platform is helpful but not required to follow along.
Xdrive is a digital asset management tool you can use to enhance the experience you offer to users. You might think that Xdrive's only use is to back up files to a secure repository. While this is a truly great feature of Xdrive, it doesn't end there. With new features that include the option to publish and share digital assets, Xdrive makes it easy to communicate and share data with other users without having to worry about bouncing an e-mail message that was too big or tripping a filter in a user's messaging system. Because Xdrive provides the ability to manage accounts through both a web and local desktop software interface, it's also just as easy to create, manage, and share your thoughts and ideas publicly on the Xdrive network.
Getting Started
To get started building your YUI Xdrive client, you'll need to gather a few materials. Xdrive is a service provided by AOL so you'll need at a minimum an AOL screen name, and you'll need to sign up for an account at http://www.xdrive.com. When you are registered as an AOL developer, you'll also need to be aware of the technologies used in this example. Because this example uses YUI, some experience with JavaScript would be beneficial to follow along. Experience developing for the YUI is also preferred but not necessary.
Xdrive Anatomy
We'll start by reviewing some basic Xdrive anatomy. The API speaks the language of JavaScript Object Notation (JSON), a lightweight data interchange format supported by many different languages and libraries. You'll be using JavaScript and PHP, two languages with inherent support for this format, and with easy-to-use interfaces. Please note, however, that you'll need to take special consideration when using JSON with JavaScript. Because JSON is a subset of the JavaScript programming language, there is a risk of introducing malicious code into your application if the JSON code is not handled properly. The way to avoid this is to process all JSON data using any number of JSON parsers. The YUI includes its own parser, which is what I'll be using, but for an alternative solution and for more information, check out http://www.json.org.
Your first stop when developing for the Xdrive API is the gateway. The gateway is accessed through the URI http://plus.xdrive.com/json/version/method, where version is the version of Xdrive you're targeting and method is the Xdrive method you want to invoke. The data you create and receive with each Xdrive request will always be composed of Transfer Objects (TOs), which are representations of individual and composite server-side objects. This makes it easy to prepare requests and consume responses from the Xdrive system. Now you're ready to begin architecting your solution for interacting with the Xdrive API.
Building the Foundation
Start off by creating a directory structure that looks like this:
<webroot>
+--- xdrive
+--- yui
+--- build
+--- script
| +--- xdrive.js
|
+--- service
| +--- downloads
| +--- uploads
| +--- xdriveProxy.php
|
+--- style
| +--- main.css
|
+--- index.html
The first item to notice is the yui directory. This will be the root of the Yahoo! User Interface Toolkit, which you can obtain at http://developer.yahoo.com/yui. It's free and very well established and stable, so just download and extract the latest version. (I'm using 2.4.1 in the example.) The xdrive.js script will handle all of the custom JavaScript code used to build your Xdrive client, and xdriveProxy.php is a PHP script used to broker requests between the JavaScript client and the Xdrive web service. The primary user-facing page will be index.html, which makes the full path to the application http://<webroot>/xdrive/build/index.html.
AJAX Application Proxies
Because you are using YUI to handle all the dynamic facets of your application, including user interface elements and data retrieval mechanisms, it's important to note the requirements and restrictions when retrieving data from third-party resources. The YUI Connection Manager relies on XMLHttpRequest, a means of transferring data using HTTP between client-side code such as JavaScript, and a server-side platform. When using this method, restrictions are put in place to disallow directly requesting data from resources outside the client's domain. To accomplish this, you'll need to either develop a proxy (or have someone do it for you), or develop your own routine to get and process JavaScript source on-the-fly from the remote location. I'll be taking the approach of developing a proxy for the following reasons:
- It's a best practice for securely developing Asynchronous JavaScript and XML (AJAX) applications.
- It's the safest way to protect your users and your application.
- Your application might rely on a larger server-side framework; a proxy written in the target language supports tighter integration.
- A proxy might also be used for performing any extra data checks and cleansing between requests.
- The proxy can handle much of the plumbing involved in a larger application such as security and logging.
Developing a proxy might sound like double-duty for you, but it can be as simple as coming up with a generic routine. In this case, you can write a PHP script to broker requests to Xdrive, outlined in Listing 1.
Listing 1 - Xdrive PHP Proxy
<?php
// Xdrive service
$xdriveBaseUrl = "https://plus.xdrive.com/json/v1.2/";
// uploads directory
$uploadDir = getcwd() . "\\uploads\\";
// downloads directory
$downloadDir = getcwd() . "\\downloads\\";
// get the parameters posted in
$xdriveMethod = $_GET[ "xdriveMethod" ];
// start building the request url
$requestUrl = $xdriveBaseUrl . $xdriveMethod;
// add stateful elements if present
if( isset( $_COOKIE[ "JSESSIONID" ] ) && isset( $_COOKIE[ "XDRecTok" ] ) ) {
$requestUrl .= ";jsessionid=" . $_COOKIE[ "JSESSIONID" ] . "?recoveryToken=" . $_COOKIE[ "XDRecTok" ];
}
// create a curl handle
$ch = curl_init();
if( $xdriveMethod == "io.formupload" ) {
// upload the file to the local uploads directory
$xdrUpload = $uploadDir . basename( $_FILES[ "xdr_file_upload" ][ "name" ] );
move_uploaded_file( $_FILES[ "xdr_file_upload" ][ "tmp_name" ], $xdrUpload );
// create the 'destFolder' FileObject
$destFolder = Array();
$destFolder[ "destFolder" ] = Array();
$destFolder[ "destFolder" ][ "type" ] = "FileObject";
$destFolder[ "destFolder" ][ "id" ] = $_POST[ "xdr_destination" ];
// encode the file object
$jsonDestFolder = json_encode( $destFolder );
// add the destination and upload the file
$_POST[ "data" ] = $jsonDestFolder;
$_POST[ $xdrUpload ] = "@" . $xdrUpload;
}
if( $xdriveMethod == "io.download" || $xdriveMethod == "io.zipdownload" ) {
// decode the file object
$toDownload = json_decode( $_POST[ "data" ], true );
// set the file path
$filePath = $downloadDir . $toDownload[ "toDownload" ][ "filename" ];
if( $xdriveMethod == "io.zipdownload" ) {
$filePath = $filePath . ".zip";
}
// create the file pointer
$fp = fopen( $filePath, "w" );
// save output to file
curl_setopt( $ch, CURLOPT_FILE, $fp );
// set the json output to be returned to the client
$jsonDownload = Array();
$jsonDownload[ "downloadLink" ] = str_replace( "\\", "/", str_replace( getcwd(), "http://localhost/build/service", $filePath ) );
$jsonDownload = json_encode( $jsonDownload );
echo $jsonDownload;
}
// set the request options
curl_setopt( $ch, CURLOPT_URL, $requestUrl );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $_POST );
curl_setopt( $ch, CURLOPT_HEADER, false );
if( $xdriveMethod != "io.download" && $xdriveMethod != "io.zipdownload" ) {
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
}
$response = curl_exec($ch);
// close cURL resource, and free up system resources
curl_close( $ch );
if( $xdriveMethod == "io.download" || $xdriveMethod == "io.zipdownload" ) {
fclose( $fp );
}
else {
echo $response;
}
?>
This script accepts a URL parameter named xdrivemethod, which represents the Xdrive method you want to invoke. This method name is used to construct the proper location string, which is then requested using the form parameters posted to the proxy service. Also note the check for the jsessionid and XDRecTok cookies, which are also included in the request to maintain state with Xdrive (more on this later). While most of this code serves as a simple pass-through to the AOL service, there are exceptions, such as service calls for uploading and downloading files that required special handling. We'll dive into this later in the article. Generally, this routine is simple and generic, perfect for your demonstration utility. However, in a real-world situation, you would want to include some basic data validation checks of which method name is being requested, and so on.
To use the proxy service, create a generic JavaScript method doXdriveMethod using the YUI Connection Manager, demonstrated in Listing 2.
Listing 2 - Client-Side Implementation
function doXdriveMethod( reqConfig ) {
var clbk_execMethod = {
success: function( o ) {
// parse the response as JSON
try{
oResponse = YAHOO.lang.JSON.parse( o.responseText );
}
catch(e){}
// since we're using a proxy, we could have a
// successful HTTP response which contains Xdrive-specific
// exceptions.
if( oResponse.exceptions ){
var eMsg = "The following errors occurred while processing your request:\n\n";
eMsg += getExceptions( oResponse.exceptions );
alert( eMsg );
return;
}
if( reqConfig.callback ) {
reqConfig.callback();
}
},
failure: function( o ) {
// something more on the catastrophic side.
alert( "Connection to proxy failed." );
oConfig.isLoggedIn = false;
}
};
if( reqConfig.requestType == "post" ){
var execMethod = YAHOO.util.Connect.asyncRequest( reqConfig.requestType, reqConfig.requestUrl, clbk_execMethod, reqConfig.data );
}
else {
var execMethod = YAHOO.util.Connect.asyncRequest( reqConfig.requestType, reqConfig.requestUrl, clbk_execMethod );
}
}
This method accepts an object of the following keys:
| Key | Description |
|---|---|
| requestType | HTTP method, GET or POST |
| requestUrl | The URL to be requested |
| callback | An optional JavaScript function reference to be fired after the request is completed |
| data | Any data to be POSTed with the request |
Because you're using a proxy, you need to clearly understand exception handling. Completing a request to the proxy service for Xdrive has its own subtleties. When an exception in Xdrive occurs, an exceptions block is present in the response. The evaluation of success versus failure in your routine in Listing 2, however, deals purely with the status of the HTTP response itself. So, even when an exception occurs on Xdrive, YUI Connection Manager will still recognize the request as successful. To handle this confusion, the doXdriveMethod function checks for Xdrive exceptions, bundles them up, and alerts the user. Using this function, you won't need to consider any special routines to handle Xdrive exceptions as you continue your development.
Connecting to Xdrive
Now that we've reviewed the process for interacting with Xdrive, you can begin developing the plumbing of your application. Start by building an authentication system for Xdrive. First, you'll want to construct the user interface elements involved with collecting data from the user to be sent to Xdrive, which you'll accomplish using the YUI Panel control. You can implement panels using either markup or script. For this project, use the following markup:
<div id="xdr-login">
<div class="hd">Xdrive Login</div>
<div class="bd">
<table>
<tr>
<td>Username:</td>
<td><input type="text" id="txt_username" value="" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" id="txt_password" value="" /></td>
</tr>
<tr>
<td></td>
<td><input type="button" value="Login" onclick="doLogin();" /></td>
</tr>
</table>
</div>
<div class="ft"></div>
</div>
The only requirements are that you give the outer container a unique ID that you can reference when building your panel in YUI. However, you are also going to implement some CSS class names--hd, bd, and ft--that are defined in YUI styles for jazzing up the header, body, and footer of panels, respectively. The following JavaScript code performs the actual construction of your login panel.
// create the login panel
loginPanel = new YAHOO.widget.Panel( "xdr-login", {
fixedCenter: true,
underlay: "shadow",
close: false,
visible: true,
draggable: true
} );
loginPanel.render();
As you can see, to build the panel you pass in the ID of the container, along with a set of configuration options used to determine its makeup. The options in the previous code block center the container on the screen and allow dragging with the mouse. The product of this code is shown in Figure 1.

Figure 1. Login panel
From here, you can simply reference the text field inputs by their ID to send to Xdrive for authentication. You accomplish this using the doLogin() JavaScript function:
function doLogin() {
var requestUrl = PROXY_BASE_ADDRESS + "member.login";
// get the username/password from the form
var username = document.getElementById( "txt_username" ).value;
var password = document.getElementById( "txt_password" ).value;
// create a MemberObject used for authentication with Xdrive
var oMember = { "user":
{
"type": "MemberObject",
"username": username,
"password": password
}
};
// stringify the object, prepare for posting
var postData = "data=" + YAHOO.lang.JSON.stringify( oMember );
// configure function to be executed upon success
var fncSuccess = function() {
// copy data to global object
oConfig.jsessionid = oResponse.jsessionid;
oConfig.recoveryToken = oResponse.recoveryToken;
oConfig.username = oResponse.results.user.username;
oConfig.firstName = oResponse.results.user.firstname;
oConfig.lastName = oResponse.results.user.lastname;
oConfig.email = oResponse.results.user.email;
oConfig.rootFolderId = oResponse.results.user.rootFolderId;
oConfig.userSeq = oResponse.results.user.userSeq;
oConfig.isLoggedIn = true;
// set cookies to maintain state with the proxy
setCookie( "JSESSIONID", oConfig.jsessionid );
setCookie( "XDRecTok", oConfig.recoveryToken );
// initialize the directory tree
loadTree(
{
"folderId": oConfig.rootFolderId,
"isRoot": true
}
);
getContacts();
loginPanel.hide();
mainPanel.show();
};
// execute the request
doXdriveMethod(
{
"requestType": "post",
"requestUrl": requestUrl,
"callback": fncSuccess,
"data": postData
}
);
}
The function starts off by grabbing the username and password from the login panel you created earlier. This is used to create an Xdrive Member Transfer Object, which is simply an object with a type attribute set to MemberObject, and username and password attributes mapped to the values obtained from the login panel. Using the YUI JSON parser, convert the object to a string so you can construct the data form parameter data to be POSTed to the service. A function named fncSuccess is defined as a callback to be evaluated upon successful completion of the request. This callback does a number of things, including copying the data obtained from the response into a global JavaScript object that you'll reference throughout the development of this utility. The typical response from a login request looks similar to this:
{"results":
{"user":
{"type":"MemberObject",
"username":"testuser@xdrive.com",
"email":"testuser@xdrive.com",
"firstname":"firstname",
"lastname":"lastname",
"rootFolderId":"xdr:XFS-123456789",
"userSeq":123456}
},
"recoveryToken":"xdrectokvalue",
"jsessionid":"jsessionidvalue" }
This JSON data is referenced in the callback to set your global properties through dot notation to the relevant value. For example, to access the username field of the response, you would reference response.results.username, where response is the object that contains the response from the request. Other tasks involved in initializing the application are to set cookies using the jsessionid and XDRecTok values from the response, load the Xdrive directory tree, load the contacts widget, and render the main application window. The cookies are of particular importance because they will be used to maintain state with Xdrive.
Here you are using a utility function to set a cookie with JavaScript for the jsessionid and XDRecTok values passed back in the response, which is important for maintaining state with Xdrive during each request. You'll use this cookie in your proxy service to send along with each Xdrive method call as a querystring parameter so state is not broken. Using this method, you are creating a cookie-less client with Xdrive (between your proxy and the Xdrive service). This architecture yields a unique hybrid that demonstrates the two ways of maintaining state. If you were to use the Xdrive web client on http://www.xdrive.com, then you would use pure cookies to handle session state.
Although our login routine uses the member.login Xdrive method to perform authentication, the system also supports authentication using the AOL Open Authentication API (OpenAuth) authentication system through the member.aollogin method. This method accepts an OpenAuth Transfer Object, which consists of a valid AOL developer ID, a referrer location for your application, and an OpenAuth token. This allows seamless integration with other AOL services you use in your applications.
Loading the Xdrive File Tree
As demonstrated earlier, after successful authentication you invoke a function you created earlier named loadTree to initialize the directory information obtained from Xdrive. The tree is part of the larger Panel object of the YUI, which contains the main application window. Very similar to what you used before, the following markup is used to construct the main window:
<div id="xdr-main">
<div class="hd">Xdrive YUI Client</div>
<div class="bd">
<div id="cntr_menu">
<input type="button" value="Add Contact" onclick="contactsPanel.show();" />
<input type="button" value="Logout" onclick="doLogout();" />
</div>
<div id="cntr_files">
<h4>Files</h4>
<div id="fileTree"></div>
</div>
<div id="cntr_contacts">
<h4>Contacts</h4>
<div id="contacts"></div>
</div>
</div>
<div class="ft" id="status"></div>
</div>
The container you populate with Xdrive data has the ID fileTree. Using the YUI TreeView object, you can construct a folder tree on-the-fly using data obtained from Xdrive. This process starts by constructing a new TreeView object as follows:
var fileTree = new YAHOO.widget.TreeView( "fileTree" );
The TreeView object is passed the ID of the container you want to populate with data. The loadTree function is shown in Listing 3:
Listing 3 - Loading the Tree View
function loadTree( reqConfig ) {
var requestUrl = PROXY_BASE_ADDRESS + "file.getlisting";
// requires authentication
if( !oConfig.isLoggedIn ){ return; }
// configure a file object to pass to xdrive
var oFile = { "srcFile":
{
"type": "FileObject",
"id": reqConfig.folderId
}
};
// stringify the data, prepare for posting
var postData = "data=" + YAHOO.lang.JSON.stringify( oFile );
var fncSuccess = function() {
// initialize the tree
if( reqConfig.isRoot ){
// we're passing an object to the YUI TextNode, need a 'label' field.
oResponse.results.srcFile.label = oResponse.results.srcFile.filename;
var tmpNode = new YAHOO.widget.TextNode( oResponse.results.srcFile, fileTree.getRoot(), false );
// the xdrive root folder will always ask for more nodes
tmpNode.setDynamicLoad( loadNodeData );
// update the text node map
oConfig.oTextNodeMap[ tmpNode.labelElId ] = tmpNode;
}
else {
// loop over the FileObject children to create tree nodes
for( var i = 0; i < oResponse.results.children.length; i++ ) {
oResponse.results.children[i].label = oResponse.results.children[i].filename;
var tmpNode = new YAHOO.widget.TextNode( oResponse.results.children[i], reqConfig.parentNode, false );
// if the current node is a directory, enable dynamic query for more.
if( oResponse.results.children[i].isDir ) {
tmpNode.setDynamicLoad( loadNodeData );
}
// update the text node map
oConfig.oTextNodeMap[ tmpNode.labelElId ] = tmpNode;
}
}
// draw the tree
fileTree.draw();
};
// execute the request
doXdriveMethod(
{
"requestType": "post",
"requestUrl": requestUrl,
"callback": fncSuccess,
"data": postData
}
);
}
This function accepts an object with the following characteristics:
| Key | Description |
|---|---|
| folderId | The Xdrive folder ID from which to get file listing information |
| isRoot | Boolean characters indicating whether this is the root of the Xdrive drive |
| parentNode | The node used to attach children in the TreeView object |
The Xdrive method involved with loading the tree is file.getlisting, which accepts a File Transfer Object of the following format:
{ "srcFile":
{
"type": "FileObject",
"id": <folderId>
}
}
The id property is a valid Xdrive folder ID. In this case, you're using the root folder ID captured after successful authentication. Our Xdrive FileObject is serialized to JSON and a callback function is configured. The callback is responsible for filling the tree nodes, using data in the response. This routine also captures the folder IDs with their corresponding tree node objects into your global configuration object oConfig in the oTextNodeMap property. This will be used as a look-up map you can use when performing operations against nodes in the tree, which I explain next.
Directory/File Operations
As you would expect, Xdrive supports all the common functions of a file management tool, such as create, cut, copy, and delete. Your application manages this for the directories and files in your user interface using a ContextMenu object of YUI. Use the code in Listing 4 to subscribe to the events necessary to generate a context menu, connecting all the function references in to each entry in the menu.
Listing 4 - Creating a Context Menu
function onTriggerContextMenu( oEvent ) {
oConfig.oCurrentTextNode = oConfig.oTextNodeMap[ this.contextEventTarget.id ];
}
YAHOO.util.Event.onAvailable( "fileTree", function () {
var oContextMenu = new YAHOO.widget.ContextMenu( "fileTreeContextMenu", {
trigger: "fileTree",
lazyload: true,
itemdata:
[
{ text: "New Folder", onclick: { fn: createFolder } },
{ text: "Cut", onclick: { fn: cut } },
{ text: "Copy", onclick: { fn: copy } },
{ text: "Paste", onclick: { fn: paste } },
{ text: "Delete", onclick: { fn: deleteFile } },
{ text: "<hr/>" },
{ text: "Publish", onclick: { fn: publish } },
{ text: "UnPublish", onclick: { fn: unpublish } },
{ text: "<hr/>" },
{ text: "Upload", onclick: { fn: upload } },
{ text: "Download", onclick: { fn: download } },
{ text: "<hr/>" },
{ text: "Refresh", onclick: { fn: refreshTree } },
{ text: "Properties", onclick: { fn: showProperties } }
]
} );
oContextMenu.subscribe( "triggerContextMenu", onTriggerContextMenu );
} );
When you invoke the context menu by right-clicking an asset in the Xdrive directory tree, the onTriggerContextMenu function is fired. This function's purpose is simply to save a reference to the current tree node in the oConfig.oCurrentTextNode variable. This variable is used throughout all the context menu functions to reference Xdrive data stored as metadata in the tree nodes. Take a look at the examples in the following sections.
Creating a Folder
The createFolder function (see Listing 5) uses the file.newfolder Xdrive method to create a new folder in the directory tree.
Listing 5 - Creating a Folder
function createFolder() {
var requestUrl = PROXY_BASE_ADDRESS + "file.newfolder";
// get the new folder name
var folderName = window.prompt( "Enter a new folder name: ", "" );
if( folderName == null || folderName.length == 0 ){ return; }
// configure the FileObject
var oNewFolder = {
"filename":
{
"type": "FileObject",
"filename": folderName
},
"destFolder": oConfig.oCurrentTextNode.data
};
var postData = "data=" + YAHOO.lang.JSON.stringify( oNewFolder );
// confirmation callback
var fncSuccess = function() {
alert( "The folder '" + folderName + "' was created successfully" );
refreshTree();
};
doXdriveMethod(
{
"requestType": "post",
"requestUrl": requestUrl,
"callback": fncSuccess,
"data": postData
}
);
}
When a user types a name for the folder, that action creates a FileObject that contains a filename representing the new folder to create and a destFolder, which represents the directory to attach the new folder to. The destination is populated using the data that was cached for each tree node when the tree was populated. The request to Xdrive is the same--just serialize the data, define a callback function, and send it along. The refreshTree function does just that--refreshes the data in TreeView to include the new folder you just created.
Moving Files
You'll rely on three functions you've already developed to move files around in your application: cut, copy, and paste. The cut and copy functions are similar in that they simply register the node to be acted upon, along with the action to take. No Xdrive method is invoked in either of these functions. However, after the paste function is invoked, the data retrieved from the cut-and-copy routines is used to determine which Xdrive method to invoke. Cut uses file.move and copy uses file.copy. All three functions are shown in Listing 6.
Listing 6 - Moving Files
function cut() {
// mark the current tree node as 'cuttable'
oConfig.oCutCopy = oConfig.oCurrentTextNode.data;
// define the operation so we may choose the right Xdrive method
oConfig.cutCopyOp = "cut";
} // end: cut
function copy() {
// mark the current tree node as 'copyable'
oConfig.oCutCopy = oConfig.oCurrentTextNode.data;
// define the operation so we may choose the right Xdrive method
oConfig.cutCopyOp = "copy";
} // end: copy
function paste() {
if( !oConfig.oCutCopy ) {
// just fail silently, reset the cut/copy object
oConfig.oCutCopy = null;
return;
}
// configure the objects to pass to Xdrive depending on the operation.
if( oConfig.cutCopyOp == "cut" ){
var requestUrl = PROXY_BASE_ADDRESS + "file.move";
var oFile = {
"destFolder": oConfig.oCurrentTextNode.data,
"toMove": [ oConfig.oCutCopy ]
};
}
else if( oConfig.cutCopyOp == "copy" ){
var requestUrl = PROXY_BASE_ADDRESS + "file.copy";
var oFile = {
"destFolder": oConfig.oCurrentTextNode.data,
"toCopy": [ oConfig.oCutCopy ]
};
}
// prepare the data to be posted.
var postData = "data=" + YAHOO.lang.JSON.stringify( oFile );
// configure a simple callback to refresh the tree
var fncSuccess = function() {
refreshTree();
};
doXdriveMethod(
{
"requestType": "post",
"requestUrl": requestUrl,
"callback": fncSuccess,
"data": postData
}
);
}
Deleting a File
Deleting a file is just as easy as other folder management functions and is accomplished by invoking the asset.delete Xdrive method. This method is generic in nature and can be used to delete many other object types, including contacts. By now, the process you're using to invoke Xdrive methods should be getting clearer:
- Define the Xdrive method to invoke and prepare the URL for the proxy service.
- Define Xdrive objects to be passed in to the service.
- Serialize the Xdrive objects to JSON.
- Define a callback function.
- Invoke your
doXdriveMethodJavaScript function.
Showing Xdrive File Properties
The context menu also offers you a way to review the properties of the target Xdrive file using the showProperties function, shown in the following code:
function showProperties() {
var oFile = oConfig.oCurrentTextNode.data;
var el_properties = document.getElementById( "xdr-properties" );
var el_shares = document.getElementById( "xdr-shares" );
var msg = "<table>";
for( var i in oFile ){
msg += "<tr>";
msg += "<td>" + i + ":</td>";
msg += "<td>" + oFile[i] + "</td>";
msg += "</tr>";
}
msg += "</table>";
el_properties.innerHTML = msg;
msg = "<form name='shareForm'>";
msg += "<table>";
msg += "<tr>";
msg += "<th>Contact</th><th>Has Share Permissions</th><th></th>"
msg += "</tr>";
for( var i in oConfig.oContacts ) {
msg += "<tr>";
msg += "<td>" + i + " (" + oConfig.oContacts[i].id + ")</td>";
msg += "<td><input type='checkbox' name='share' value='" + i + "' /></td>";
for( var j = 0; j < oConfig.aShares.length; j++ ) {
if( oFile.id == oConfig.aShares[j].fileObject.id && oConfig.aShares[j].grantee.id == oConfig.oContacts[i].id ) {
msg += "<td><input type='button' value='Revoke' onclick='revokeShare(" + j + ");' /></td>";
}
}
msg += "</tr>";
}
msg += "<tr>";
msg += "<td colspan='2'><input type='button' value='Save' onclick='saveShares();' /></td>";
msg += "</tr>";
msg += "</table>";
msg += "</form>";
el_shares.innerHTML = msg;
propertiesPanel.show();
}
This function relies on the generation of another Panel object, which includes a tabbed interface. The first tab will show the properties of the file, and the second tab will show the shares. An example Properties Panel is shown in Figure 2.

Figure 2. File Properties Panel
This panel is just an enumeration dump of the properties of the target file. The completed main screen with context menu is shown in Figure 3.

Figure 3. Main application with context menu
The final file management task you'll want to explore is a new addition to the Xdrive platform. Beginning with version 1.2 you can publish file assets for public consumption using the file.publish Xdrive method. Using the code you've developed so far, you can determine whether an asset is available to the public or not. Simply right-click the folder or file, and then click Properties. If a publishid is populated in the properties listing, the asset is published. To unpublish, simply right-click the asset, and then click Unpublish.
Uploading and Downloading Content
Up to this point, you've implemented many of the most common methods you can use with the Xdrive API. Now move forward to perhaps the most important methods of all--exchanging data between your local environment and the remote Xdrive platform. This is where we start to deal with a few exceptions in the processing at the proxy level of the application. Again, the proxy is brokering requests basically between itself and Xdrive, so this requires the system to accept file uploads to be saved locally at the proxy and then be passed on to Xdrive. Of course, this also means it will need to download content to itself, then pass data back to the client.
Start by uploading some content to Xdrive. Take a look at a portion of the proxy service that deals with file upload:
if( $xdriveMethod == "io.formupload" ) {
// upload the file to the local uploads directory
$xdrUpload = $uploadDir . basename( $_FILES[ "xdr_file_upload" ][ "name" ] );
move_uploaded_file( $_FILES[ "xdr_file_upload" ][ "tmp_name" ], $xdrUpload );
// create the 'destFolder' FileObject
$destFolder = Array();
$destFolder[ "destFolder" ] = Array();
$destFolder[ "destFolder" ][ "type" ] = "FileObject";
$destFolder[ "destFolder" ][ "id" ] = $_POST[ "xdr_destination" ];
// encode the file object
$jsonDestFolder = json_encode( $destFolder );
// add the destination and upload the file
$_POST[ "data" ] = $jsonDestFolder;
$_POST[ $xdrUpload ] = "@" . $xdrUpload;
}
Upload processing begins with the expected file parameter named xdr_file_upload. This is a form field of type file that is defined in another YUI Panel constructed from a function defined in the context menu for file assets. The base name of this filename is parsed out and used in the move_uploaded_file PHP function to move the file to its defined upload directory. Next, a new FileObject is created to define the destination folder the file will be uploaded to in the Xdrive environment. This is determined through the xdr_destination form parameter POSTed in from the client. Remember, this is all driven from the context menu structure, so passing along the metadata to the proxy is a snap. If you wanted to create additional functionality against your file assets, you could easily extend it--all the information is there. After the destination folder is defined, the data is encoded to JSON and added to the $_POST array, which will be picked up by your generic routine and passed through to Xdrive. On the client side, the callback function will simply hide the upload panel and refresh the file tree, which enables you to immediately see your new file in your Xdrive account.
Downloading content from Xdrive serves as another exception to the processing rules set forth in your proxy service. Although the client code doesn't change that much from all the other requests you've been doing, there is a subtlety that deserves explaining. Xdrive supports the downloading of single files in addition to compressed archive files through the io.download and io.zipdownload Xdrive methods, respectively. Our YUI client is designed to use one of these two methods based on the context that the download function is invoked. If a file is chosen for download from its context menu, then the io.download method is used. However if a folder is chosen, then io.zipdownload is used, because you're dealing with the prospect of multiple files residing in a folder. Because of these subtleties, you'll have a few things to review on the proxy side, so I'll cover them in pieces, starting with the main block in Listing 7.
Listing 7 - Proxy Service Download Functions
if( $xdriveMethod == "io.download" || $xdriveMethod == "io.zipdownload" ) {
// decode the file object
$toDownload = json_decode( $_POST[ "data" ], true );
if( $xdriveMethod == "io.zipdownload" ) {
$filePath = $downloadDir . $toDownload[ "toDownload" ][0][ "filename" ];
$filePath = $filePath . ".zip";
}
else {
$filePath = $downloadDir . $toDownload[ "toDownload" ][ "filename" ];
}
// create the file pointer
$fp = fopen( $filePath, "w" );
// save output to file
curl_setopt( $ch, CURLOPT_FILE, $fp );
// set the json output to be returned to the client
$jsonDownload = Array();
$jsonDownload[ "downloadLink" ] = str_replace( "\\", "/", str_replace( getcwd(), "http://localhost/build/service", $filePath ) );
$jsonDownload = json_encode( $jsonDownload );
echo $jsonDownload;
}
The first thing you do is parse out the filename property from the FileObject or array of FileObjects passed to you from the client. If you have an array of FileObjects, then it's a .zip download; otherwise, you're dealing with a single file download. When the filename is obtained, it's used to construct the local path to the standard downloads directory you defined in the $filePath variable. This file path is then used to open a file handle for writing capability. Because your proxy service communicates with the Xdrive API via the cURL library, you set an option unique to downloading files named CURLOPT_FILE. This basically specifies the file where the content downloaded using cURL will be saved. Finally, a download link is crafted to point to the newly downloaded file and populated in your own JSON data structure, which will be consumed by the client. A request for a .zip download of the entire My Documents folder on Xdrive yields a screen as shown in Figure 4.

Figure 4. Downloading a .zip file
Administering Contacts
Xdrive also gives you the ability to administer not only contacts from your Xdrive address book, but also contacts in your AOL address book. Your main application window includes a container for listing contacts as links, which can be clicked to view the information contained within them. The Add Contact button in the main navigation section of the application is used for showing a Panel container with a form used to fill in information to add a new contact. These functions use the contact.getaollisting, contact.getlisting, contact.newaollisting, and contact.newlisting Xdrive methods. You can also group contacts using the contact.newgroup Xdrive method. To use this method, simply implement the process I outlined earlier for crafting a request to Xdrive using the doXdriveMethod. A Contact Transfer Object contains the self-explained keys firstname, lastname, nickname, email, and id.
Managing Shares
Xdrive supports the collaborative features of sharing contacts from both Xdrive and your AOL address book. Our example YUI client will demonstrate a few functions from this feature set, such as granting and revoking permissions, and listing shares.
Your client code has a function named getShares, which assembles a listing of the shares you currently have open on your account:
function getShares() {
var requestUrl = PROXY_BASE_ADDRESS + "share.listoutboundshares";
var fncSuccess = function() {
oConfig.aShares = oResponse.results.permissions;
}
doXdriveMethod(
{
"requestType": "get",
"requestUrl": requestUrl,
"callback": fncSuccess
}
);
}
It's important to note here that when getShares is executed, it gathers all the shares you have configured for your account and saves them to a global array, which is part of your oConfig global configuration object. This is the data structure you reference when constructing the user interface for managing permissions to your file assets.
Let's take a look at constructing the Sharing tab of the Properties Panel. The code in Listing 8 is a section of the main code block used to construct the Sharing tab interface. A loop is started against the repository of contact objects you have in your account. This loop is used to gather a listing of every contact that is available for you to grant permission to access the resources in your Xdrive account. To interrogate further, an inner loop is run to attempt a match of the contact object against the array of permissions you have cached in the aShares global array. If a match is found, then a Revoke button is displayed, allowing us to revoke the permission. The resulting UI is shown in Figure 5.
Listing 8 - Building the Sharing Tab Interface
msg = "<form name='shareForm'>";
msg += "<table>";
msg += "<tr>";
msg += "<th>Contact</th><th>Has Share Permissions</th><th></th>"
msg += "</tr>";
for( var i in oConfig.oContacts ) {
msg += "<tr>";
msg += "<td>" + i + " (" + oConfig.oContacts[i].id + ")</td>";
msg += "<td><input type='checkbox' name='share' value='" + i + "' /></td>";
for( var j = 0; j <' oConfig.aShares.length; j++ ) {
if( oFile.id == oConfig.aShares[j].fileObject.id && oConfig.aShares[j].grantee.id == oConfig.oContacts[i].id ) {
msg += "<td><input type='button' value='Revoke' onclick='revokeShare(" + j + ");' /></td>";
}
}
msg += "</tr>";
}
msg += "<tr>";
msg += "<td colspan='2'><input type='button' value='Save' onclick='saveShares();' /></td>";
msg += "</tr>";
msg += "</table>";
msg += "</form>";

Figure 5. The Sharing tab interface
To grant share permission to a contact, you would check the box next to their name, and then click Save. This invokes a function you've defined to handle the share.grantpermission Xdrive function. The Revoke button is used to revoke share permissions and implements the share.revokepermission Xdrive method.
Although not implemented in the YUI client, Xdrive gives you the option of mapping shares defined by you and your fellow Xdrivers through the share.map and share.unmap methods. Further, you can also use the share.sendfile method to send a friend an e-mail that contains a link to download a file from your Xdrive account.
Conclusion
We've covered a lot here, yet only scratched the surface of Xdrive. What's truly great about this example is that it is one of many different technologies you can use to implement Xdrive functionality. With no specific ties to technology or skill set, you are free to explore this incredible service using what you're comfortable with.
Resources
For more information on Xdrive visit the following resources:
