Interacting with SkyDrive

This SkyDrive Roaming Data sample demonstrates how to create a roaming app that uses SkyDrive as a repository for user data. The app demonstrates how to create a Windows Store app built for Windows using JavaScript to get and send data programmatically to and from SkyDrive. For help with writing a SkyDrive app in C# or other languages, see the SkyDrive documentation.

The sample includes these features:

  • Searching for files using friendly file names.
  • Uploading and downloading files.
  • Reading from and writing to files.
  • Caching data and displaying cached data.
  • Creating new files and folders.
  • Enabling single sign-in using aMicrosoft account.
  • Using SkyDrive session state information

Using SkyDrive as a repository for user data

The SkyDrive Roaming Data app uses SkyDrive as a repository for user data, similar to a lightweight database. SkyDrive is notintended as a repository for app data. For app data, you might want to use theroaming app data feature provided for Windows Store apps, or a Windows Azure–based solution. For more info, see App data.

Note: SkyDrive Roaming Data uses the roaming app data feature to store and retrieve the name of the most recent data file when you start your app. This feature works across different devices if you are signed into your devices using the same Microsoft account. For current info, see this topic.

App design and asynchronous programming

In the SkyDrive Roaming Data app, we use a separated presentation pattern to simplify code and improve code re-use, and to make code testing a little easier. In SkyDrive Roaming Data, these files represent the presentation layer and the data model:

  • default.js contains the presentation layer. The code in this file interacts with the view (HTML and CSS code) and the data model.
  • SkyDrive.js contains the data model. This file contains functions that search SkyDrive for data files, download data, and upload data. It also handles caching.

To help separate responsibilities, we used asynchronous methodsto call the functions in the data model. This codeisuses promises, which arerecommended for Windows Store apps. For more info, see Asynchronous programming in JavaScript.

Using search to find SkyDrive data

The recommended methods for interacting with files on SkyDrive include eitherusing the file picker control or performing searches. If the user interaction with SkyDrive includes browsing folders and files, use the file picker. For more info, see this topic.If you're creating a data-focused applikeSkyDrive Roaming Data, you can use a search function to obtain folder and file IDs.

Before you interact with data on SkyDrive, the SkyDrive Roaming Dataapp requires that the user specify the correct data file. The app runs a search function in response to user input. (The user can also create a data file if it does not yet exist.)

To implement search:

  1. Create a function to handle search.
    In the sample app, we do this in the searchForFile function in SkyDrive.js. The input parameter contains a friendly name for the data file.

functionsearchForFile(path) {

// Add code to run the search.

}

  1. Initiate a SkyDrive search for the required folder in which the data resides.
    The WL.api function is a SkyDrive API that wraps an asynchronous REST method. For asynchronous calls in Windows Store apps, you need to store the return value in a Promiseobject. The search query returns a folder ID, which you will use later to verify that the data file is in the folder named "notes."
    NoteWe recommend using a specific folder on SkyDrive to store user data. Using a specific folder makes it easier for the user to distinguish app-related files from other personal files. In the sample app, we use a folder named "notes."

varfolderName = "notes";

function _searchForFolder() {

// Runs async search for the predefined folder.

returnWL.api({

path: "/me/skydrive/search?q=" + folderName,

method: "GET"

}).then(function (response) {

if (response != null) {

var data = response.data;

// Iterate through the search results.

// The app assumes one unique matching folder name.

for (var index = 0; index < data.length; index++) {

// Make sure you have an exact match, not

// just a partial match.

if (data[index].name == folderName) {

folderUrl = data[index].id;

// Return the folder ID

returnWinJS.Promise.wrap(data[index].id);

}

}

folderUrl = null;

returnWinJS.Promise.wrap(null);

} else {

folderUrl = null;

returnWinJS.Promise.wrap(null);

}

});

}

  1. In the searchForFile function, make another asynchronous call to get the data file specified by the user.
    The friendly name for the data file is contained in the path variable. The response object is returned so that it can be processed later, after both asynchronous function calls return a result.

varskyDriveSearchPath = "/me/skydrive/search?q=" + path;

varwhenFilesFound = WL.api({

path: skyDriveSearchPath,

method: "GET"

}).then(function (response) {

if (response != null) {

returnWinJS.Promise.wrap(response);

} else {

inMemoryFile = null;

returnWinJS.Promise.wrap(false);

}

});

  1. Use the join method to join the two promises.
    Joining the promises allows the two asynchronous searches to run simultaneously. Arguments to store the return values, folderResult and fileResult, are specified.

varwhenEverythingIsReady = WinJS.Promise.join({

folderResult: whenFolderFound, fileResult: whenFilesFound

});

  1. When both asynchronous calls return, the completion handler for the joined promise runs.
    In this code, the returned folder ID is compared to the parent folder of the returned files. If there's a match, the upload and download URLs for the app are set, and a cached StorageFile object (inMemoryFile) is created to store downloaded data. The StorageFile object is also passed as an argument to the upload method if the upload method is later executed.

returnwhenEverythingIsReady.then(function (args) {

varfolderId = args.folderResult;

var response = args.fileResult;

var data = response.data;

// Package manifest specifies .dat files.

varlocalFileName = path;

if (path.substr(path.length - 4, 4) !== ".dat") {

localFileName = path + ".dat";

}

// Iterate through the returned files.

for (var index = 0; index < data.length; index++) {

if (data[index].name == localFileName) {

// Check whether the matching file is

// in the correct folder.

if (folderId == data[index].parent_id) {

// Set paths for upload and download operations.

uploadUrl = response.data[index].id;

downloadUrl = uploadUrl + "/content";

// Create a local StorageFile object.

varwhenFileCreated = _createFileObject(localFileName);

whenFileCreated.then(function (newFile) {

inMemoryFile = newFile;

});

// Wrap the return value in a promise.

returnWinJS.Promise.wrap(true);

}

}

inMemoryFile = null;

returnWinJS.Promise.wrap(false);

}

inMemoryFile = null;

returnWinJS.Promise.wrap(false);

}, function (errorResponse) {

_showError("Error getting info about: " + path + "-- Error: " + errorResponse.error.message);

});

  1. In default.html, add HTML to allow the user to specify a friendly name for the data file.
    In the sample app, we use an input box. (The CSS code is not shown here.)

inputclass="dataFile"type="text"value="Enter SkyDrive file name here"/>

  1. In default.js, specify an event handler for the input box.

varelem = document.querySelector('.dataFile');

elem.addEventListener('keypress', onEnterKey.bind(this));

  1. In the eventhandler for the input box, call SkyDrive.searchForFile, passing the suggested data file name in the path variable. For the complete code, see the sample app.

SkyDrive.searchForFile(path).then(function (success) {

if (success) {

showStatus(" Now click Get or Send to get/send sticky notes...");

faceEl.src = "ms-appx:///images/happy.png";

roamingSettings.values["datafileSetting"] = path;

}

else {

showStatus(getHelpMessage());

faceEl.src = "ms-appx:///images/unsure.png";

}

updateCmds(false);

},

Getting data

To get data from SkyDrive, you download the user-specified data file by using the correct file ID.

  1. Create a function to handle the file download operation.
    In the sample app, we add the _download function in SkyDrive.js.

function _download(storageFile)

// . . .

}

  1. Add code to return the result of the download.
    To download the data file, we use the WL.backgroundDownload function of the SkyDrive API. For this app, it's important to provide the StorageFile object as input to the backgroundDownloadfunction. That way, when the download operation completes, the response stream is automatically stored in the specified StorageFile object.
    We can then read lines from the object and return the result.

returnWL.backgroundDownload({

path: downloadUrl,

// Must be a valid StorageFile object.

file_output: storageFile

}).then(

function (response) {

// The specified StorageFile object receives the output after

// a successful download. Now you can

// read the contents.

// Nestedasync call needs to be returned.

returnWindows.Storage.FileIO.readLinesAsync(storageFile).then(

function (result) {

// Saves the data in the StorageFile object.

inMemoryFile = storageFile;

// For this app, we pass just the first note

// (excluding the header) to the Tile updaters.

if (result[1] !== undefined) {

Tiler.start(result[1]);

}

_showStatus("Downloaded...");

returnWinJS.Promise.wrap(result);

}, function (err) {

_showError("Error reading file: " + err.error.message);

returnWinJS.Promise.wrap("");

});

},

function (responseFailed) {

// In your app, you might want to give the user the option

// to show the cached data if a response fails.

// Here, we just show an error.

_showError("Error calling API: " + responseFailed.error.message);

returnWinJS.Promise.wrap("");

});

  1. In default.js, add code to the event handler for the get operation to display the result.
    This code initiates an asynchronous call to getSkyDriveFile, which calls the _download function.
    For the complete code, see the sample app.

SkyDrive.getSkyDriveFile().then(function (result) {

// Add code to process the result and display it.

}

Sending data

To send data to SkyDrive, you need to upload the user-specified data file by using the correct file ID.

  1. Add a function to cache the data.
    In the sample app, we add this function to SkyDrive.js. In this code, we write the input to the cached StorageFile object before uploading the data file. In the sample app, this step is required before uploading because we need to pass the StorageFile object as input to the upload function.

function _cacheDataAndSend(notes) {

returnWindows.Storage.FileIO.writeTextAsync(inMemoryFile, notes).then(

// The result here is always undefined.

function (result) {

return _upload();

});

}

  1. Create a function to handle the file upload operation.
    In the sample app, we add the _upload function in SkyDrive.js.

function _upload()

// . . .

}

  1. Add code to upload the file.
    To upload the file, we use the WL.backgroundUploadmethodof the SkyDrive API. We pass the StorageFile object as input to the backgroundUpload method, along with the file name and the upload URL (the file ID).
    If the upload is successful, we just return true.

returnWL.backgroundUpload({

path: uploadPath,

file_name: inMemoryFile.fileName,

file_input: inMemoryFile,

overwrite: overwriteArg

}).then(

function (response) {

_showStatus("Uploaded...");

uploadUrl = response.id;

downloadUrl = uploadUrl + "/content";

returnWinJS.Promise.wrap(true);

},

function (responseFailed) {

if (overwriteArg == true) {

_showError("Error calling API: " + responseFailed.error.message);

}

else {

_showStatus("Does file exist? If yes, press Enter to set data file.");

}

// If you want to handle an error in the error handler

// of the calling function, use wrapError instead of wrap.

returnWinJS.Promise.wrap(false);

});

  1. In default.js, add code to the event handler for the send operation to upload the file.
    This code initiates an asynchronous call to sendSkyDriveFile, which calls the _upload function.
    For the complete code, see the sample app.

SkyDrive.sendSkyDriveFile(msg).then(function (result) {

// Add code to update UI status on successful completion.

}

Caching data

The app uses a copy of the data file in the Documents folder as a local cache. This is easy to implement in the SkyDrive Roaming Data sample because we're using StorageFile objects to upload and download data. To create the StorageFile object, we use this code:

function _createFileObject(name) {

return Windows.Storage.KnownFolders.documentsLibrary.createFileAsync(name,

Windows.Storage.CreationCollisionOption.openIfExists);

}

Before sending data, you need to write the current notes to the StorageFile object so that you can pass the updated object to the upload function. To cache the notes, we use this code:

function _cacheDataAndSend(uploadPath, notes, overwrite) {

returnWindows.Storage.FileIO.writeTextAsync(inMemoryFile, notes).then(

// The result here is always undefined.

function (result) {

return _upload(uploadPath, overwrite);

});

}

With notes already cached locally, you can easily read the file when a request is made to the cache.

functiongetCachedData(name) {

varlocalName = _fixFileName(name);

return _openFileObject(localName).then(

function(result) {

returnWindows.Storage.FileIO.readLinesAsync(result).then(

function (result) {

_showStatus("Loaded from cache...");

returnWinJS.Promise.wrap(result);

}, function (err) {

_showError("Error reading file: " + err.message);

returnWinJS.Promise.wrap(null);

});

}, function (err) {

_showError("Error opening file: " + err.message);

returnWinJS.Promise.wrap(null);

});

}

For the complete code used to display the data, see the sample app.

Other considerations

The SkyDrive Roaming Data sample also includes code to handle single sign-in with a Microsoft account, and it includes functions to create folders and files on SkyDrive. For the complete code, see the sample app.

SkyDrive Roaming Data is intended to demonstrate searching, uploading, downloading, and caching data, and isn't a complete sample. The sample doesn't implement the following features:

  • Downloading and uploading multiple files in a single operation. For example code that does this, see the PhotoSky sample or the SkyDrive documentation.
  • Downloading and uploading files by using a file picker.
  • Checking the available storage capacity on SkyDrive before performing an operation. For more info, see Common tasks in accessing Microsoft SkyDrive from your app.
  • Canceling longasynchronous operations. For more info, see this topic.