Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Windows 8–Connecting to SkyDrive

Blogs

Mike Taulty's Blog

Elsewhere

Archives

I wrote my first Windows 8 code which connected to SkyDrive the other day so I thought I’d share in case it helps anyone who’s doing a similar thing.

The main documentation set for Live is on the web so drop that into your favourites if you’re going to be working with this API set.

Simple example – let’s say that I want to grab some pictures from my local machine and upload them to a particular folder on my SkyDrive. I’ll start with a blank project;

image

and then I’ve downloaded the Live SDK so that means that I can add a reference to it from this project;

image

Setup

Then there’s some configuration that I need to do. I have a Store account so the way that I do this might be different for you but what I’m currently doing is going to the Windows 8 Dashboard site and then signing in and creating a brand new application that I have no intention of ever releasing Smile

image

and that lets me type in the details of my app. The only bits that I want are the name;

image

image

and that’s simple enough and then I follow this step;

image

and then follow that through to “Representing your app to Live Connect users”;

image

and that lets me put in details about how my app is going to represent itself to my users – for my testing purposes I can enter;

image

but, naturally, a real app would need real values.

The last setup step is to visit Visual Studio and make an association between the project that I’m working on and this newly created app in the Store. Visual Studio has a handy menu for this;

image

which lets me sign in and pick the app that I want to associate the project with;

image

and that updates the packaging section of my manifest with a bunch of IDs from the Store.

Signing In

From there, signing in to Windows Live is pretty easy. Depending on what you want to do with Live, you need to consider scopes and I think that the scopes I need for what I want here are available to me if I log in to Live as below;

      var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        
      }

and the first time this code executes, the Live bits throw up a UI which ask me as a user whether I’m ok with the application talking to Live in the way that it has specified;

image

and, as long as I agree then I’m ready to try and connect across to SkyDrive.

Getting To SkyDrive

The LiveConnectClient class bridges me across to SkyDrive content and to do that I need to feed it the LiveConnectSession that the LiveAuthClient has put together for me.

The LiveConnectClient is an interesting one because the public interface feels very generic consisting largely of GET, PUT, POST, DELETE methods along with a MOVE, COPY and then some methods for BackgroundUpload and BackgroundDownload (using the Windows system capability for background uploads and downloads).

(Not Very Tested Wrapper)

That gets you everything you need but I wanted to think in terms of files, folders and try to represent at least some of the relationship between them so I wrote a little wrapper around some of this. That wrapper (as is often the case for these blog posts) got thrown together in a bit of a hurry so don’t assume that it;

  1. is complete
  2. works properly

but here’s the code for my wrapper which formed itself into 3 classes and an enum;

SkyDriveWellknownFolder.cs;

namespace SkyDriveHelper
{
  public enum SkyDriveWellknownFolder
  {
    Root,
    CameraRoll,
    Documents,
    Photos,
    Public,
    RecentDocuments
  }
}

SkyDriveItem.cs

namespace SkyDriveHelper
{
  using System;
  using System.Linq;
  using System.Threading.Tasks;
  using Microsoft.Live;
  using SkyDriveBag = System.Collections.Generic.IDictionary<string, object>;

  public class SkyDriveItem
  {
    public async Task LoadAsync()
    {
      // We load whether already loaded or not.
      LiveOperationResult result = await this.CxnClient.GetAsync(this.Id);

      this.ItemDetails = result.Result;

      this.ItemState = ItemStateType.Loaded;
    }
    public async Task SaveAsync()
    {
      CheckItemState();

      LiveOperationResult result = await this.CxnClient.PutAsync(
        this.Id, this.ItemDetails);

      this.ItemDetails = result.Result;

      this.ItemState = ItemStateType.Loaded;
    }
    public string Name
    {
      get
      {
        CheckItemState();
        return ((string)this.ItemDetails[NAME_KEY]);
      }
      set
      {
        CheckItemState();
        this.ItemDetails[NAME_KEY] = value;
      }
    }
    public string Description
    {
      get
      {
        CheckItemState();
        return ((string)this.ItemDetails[DESCRIPTION_KEY]);
      }
      set
      {
        this.ItemDetails[DESCRIPTION_KEY] = value;
      }
    }
    public Uri LinkLocation
    {
      get
      {
        CheckItemState();
        return (new Uri((string)this.ItemDetails[LINK_KEY]));
      }
    }
    public Uri UploadLocation
    {
      get
      {
        CheckItemState();
        return (new Uri((string)this.ItemDetails[UPLOAD_KEY]));
      }
    }
    public DateTimeOffset CreatedTime
    {
      get
      {
        CheckItemState();
        return (DateTimeOffset.Parse(
          (string)this.ItemDetails[CREATED_KEY]));
      }
    }
    public DateTimeOffset UpdatedTime
    {
      get
      {
        CheckItemState();
        return (DateTimeOffset.Parse(
          (string)this.ItemDetails[UPDATED_KEY]));
      }
    }
    public async Task DeleteAsync()
    {
      CheckItemState();
      await this.CxnClient.DeleteAsync(this.Id);
      MarkStale();
    }
    protected enum SkyDriveItemType
    {
      folder,
      album
    }
    protected SkyDriveItem(LiveConnectClient cxnClient)
    {
      this.CxnClient = cxnClient;
    }
    virtual internal string Id
    {
      get
      {
        return ((string)this.ItemDetails[ID_KEY]);
      }
      set
      {
        throw new NotImplementedException();
      }
    }
    internal void MarkStale()
    {
      if (this.ItemState == ItemStateType.Loaded)
      {
        this.ItemState = ItemStateType.Stale;
      }
    }
    protected LiveConnectClient CxnClient
    {
      get;
      private set;
    }
    protected SkyDriveBag ItemDetails
    {
      get;
      set;
    }
    protected enum ItemStateType
    {
      Unloaded,
      Loaded,
      Stale
    }
    SkyDriveItem()
    {
    }
    protected ItemStateType ItemState
    {
      get;
      set;
    }
    protected void CheckItemState()
    {
      if (this.ItemState != ItemStateType.Loaded)
      {
        throw new InvalidOperationException("Unexpected item state - refresh the item with LoadAsync?");
      }
    }
    static protected bool IsAnyType(SkyDriveBag item, params SkyDriveItemType[] types)
    {
      return (types.Any(t => IsType(item, t)));
    }
    static protected bool IsType(SkyDriveBag item, SkyDriveItemType type)
    {
      return ((string)item[TYPE_KEY] == Enum.GetName(typeof(SkyDriveItemType), type));
    }
    
    protected static readonly string ID_KEY = "id";
    protected static readonly string TYPE_KEY = "type";
    protected static readonly string NAME_KEY = "name";
    protected static readonly string DESCRIPTION_KEY = "description";
    protected static readonly string UPLOAD_KEY = "upload_location";
    protected static readonly string LINK_KEY = "link";
    protected static readonly string CREATED_KEY = "created_time";
    protected static readonly string UPDATED_KEY = "updated_time";
  }
}

SkyDriveFile.cs;

namespace SkyDriveHelper
{
  using System.Threading.Tasks;
  using Microsoft.Live;
  using Windows.Storage;
  using SkyDriveBag = System.Collections.Generic.IDictionary<string, object>;

  public class SkyDriveFile : SkyDriveItem
  {
    internal SkyDriveFile(LiveConnectClient cxnClient, SkyDriveBag item)
      : base(cxnClient)
    {
      this.ItemDetails = item;
      this.ItemState = ItemStateType.Loaded;
    }
    public async Task<IStorageFile> DownloadAsync()
    {
      LiveDownloadOperationResult result = await this.CxnClient.BackgroundDownloadAsync(this.Id);
      return (result.File);
    }
    public async Task DownloadAsync(IStorageFile file)
    {
      await this.CxnClient.BackgroundDownloadAsync(
        this.Id + CONTENT_SPECIFIER, file);
    }
    public async Task<SkyDriveFile> CopyAsync(SkyDriveFolder destination)
    {
      LiveOperationResult result = await this.CxnClient.CopyAsync(this.Id, destination.Id);
      this.MarkStale();
      destination.MarkStale();
      return (new SkyDriveFile(this.CxnClient, result.Result));
    }
    public async Task<SkyDriveFile> MoveAsync(SkyDriveFolder destination)
    {
      LiveOperationResult result = await this.CxnClient.MoveAsync(this.Id, destination.Id);
      this.MarkStale();
      destination.MarkStale();
      return (new SkyDriveFile(this.CxnClient, result.Result));
    }
    static readonly string CONTENT_SPECIFIER = "/content";
  }
}

SkyDriveFolder.cs;

namespace SkyDriveHelper
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Threading.Tasks;
  using Microsoft.Live;
  using Windows.Storage;
  using SkyDriveBag = System.Collections.Generic.IDictionary<string, object>;

  public class SkyDriveFolder : SkyDriveItem
  {
    static SkyDriveFolder()
    {
      _foldersLookup = new Dictionary<SkyDriveWellknownFolder, string>()
      {
        { SkyDriveWellknownFolder.Root,             ROOT_FOLDER },
        { SkyDriveWellknownFolder.CameraRoll,       ROOT_FOLDER + "/camera_roll" },
        { SkyDriveWellknownFolder.Documents,        ROOT_FOLDER + "/my_documents" },
        { SkyDriveWellknownFolder.Photos,           ROOT_FOLDER + "/my_photos" },
        { SkyDriveWellknownFolder.Public,           ROOT_FOLDER + "/public_documents" },
        { SkyDriveWellknownFolder.RecentDocuments,  ROOT_FOLDER + "/recent_docs" }
      };
    }
    public SkyDriveFolder(LiveConnectClient cxnClient, SkyDriveWellknownFolder wellKnownFolder) :
      base(cxnClient)
    {
      this.Id = _foldersLookup[wellKnownFolder];
    }
    public uint Count
    {
      get
      {
        CheckItemState();
        return ((uint)(int)(this.ItemDetails[COUNT_KEY]));
      }
    }
    public async Task<IEnumerable<SkyDriveFile>> GetFilesAsync()
    {
      List<SkyDriveFile> files = new List<SkyDriveFile>();

      await this.EnumerateFolder(
        bag => !IsAnyType(bag, SkyDriveItemType.album, SkyDriveItemType.folder),
        bag =>
        {
          files.Add(new SkyDriveFile(this.CxnClient, bag));
        }
      );
      return (files);
    }
    public async Task<IEnumerable<SkyDriveFolder>> GetFoldersAsync()
    {
      List<SkyDriveFolder> folders = new List<SkyDriveFolder>();

      await this.EnumerateFolder(
        bag => IsAnyType(bag, SkyDriveItemType.folder, SkyDriveItemType.album),
        bag =>
        {
          folders.Add(new SkyDriveFolder(this.CxnClient, bag));
        }
      );
      return (folders);
    }
    public async Task<SkyDriveFolder> MoveAsync(SkyDriveFolder destination)
    {
      LiveOperationResult result = await this.CxnClient.MoveAsync(this.Id, destination.Id);
      this.MarkStale();
      destination.MarkStale();
      return (new SkyDriveFolder(this.CxnClient, result.Result));
    }
    // TODO: must be a better way of doing this than getting all the files
    // and then removing the ones we don't want :-)
    public async Task<SkyDriveFile> GetFileAsync(string fileName)
    {
      var files = await GetFilesAsync();
      return (files.FirstOrDefault(
        f => string.Compare(f.Name, fileName, StringComparison.OrdinalIgnoreCase) == 0));
    }
    public async Task<SkyDriveFolder> GetFolderAsync(string fileName)
    {
      var folders = await GetFoldersAsync();
      return (folders.FirstOrDefault(
        f => string.Compare(f.Name, fileName, StringComparison.OrdinalIgnoreCase) == 0));
    }
    public async Task<SkyDriveFolder> CreateFolderAsync(string folderName)
    {
      SkyDriveBag bag = new Dictionary<string, object>();
      bag.Add(NAME_KEY, folderName);
      LiveOperationResult result = await this.CxnClient.PostAsync(this.Id, bag);

      this.MarkStale();

      return (new SkyDriveFolder(this.CxnClient, result.Result));
    }
    public async Task<SkyDriveFile> UploadFileAsync(StorageFile file,
      string skyDriveFileName = null,
      OverwriteOption overwrite = OverwriteOption.DoNotOverwrite)
    {
      LiveOperationResult result = await this.CxnClient.BackgroundUploadAsync(this.Id,
        string.IsNullOrEmpty(skyDriveFileName) ? file.Name : skyDriveFileName,
        file,
        overwrite);

      this.MarkStale();

      return (new SkyDriveFile(this.CxnClient, result.Result));
    }
    internal override string Id
    {
      get
      {
        string id = null;
        if (base.ItemState == ItemStateType.Unloaded)
        {
          return (this._id);
        }
        else
        {
          id = base.Id;
        }
        return (id);
      }
      set
      {
        if (base.ItemState != ItemStateType.Unloaded)
        {
          throw new InvalidOperationException("Folder populated");
        }
        this._id = value;
      }
    }
    async Task EnumerateFolder(
      Predicate<SkyDriveBag> selector,
      Action<SkyDriveBag> callback)
    {
      var result = await this.CxnClient.GetAsync(this.Id + FILES_SPECIFIER);

      var resultDictionary = (SkyDriveBag)result.Result;
      var resultData = (List<object>)resultDictionary[DATA_KEY];

      foreach (SkyDriveBag item in resultData)
      {
        if (selector(item))
        {
          callback(item);
        }
      }
    }

    SkyDriveFolder(LiveConnectClient cxnClient, SkyDriveBag item) :
      base(cxnClient)
    {
      this.ItemDetails = item;
      this.ItemState = ItemStateType.Loaded;
    }

    string _id;

    static Dictionary<SkyDriveWellknownFolder, string> _foldersLookup;
    static readonly string ROOT_FOLDER = "me/skydrive";
    static readonly string FILES_SPECIFIER = "/files";
    static readonly string DATA_KEY = "data";
    static readonly string COUNT_KEY = "count";
  }
}

Trying Out My Wrapper

In terms of exercising those classes. Here’s some examples of doing some basic file/folder operations.

Details of the root folder;

     var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        // Get hold of the root folder from SkyDrive. 
        // NB: this does not traverse the network and get the full folder details.
        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        // This *does* traverse the network and get those details.
        await root.LoadAsync();

        string id = root.Name;
        string desc = root.Description;
        DateTimeOffset update = root.UpdatedTime;
        uint count = root.Count;
        Uri linkLocation = root.LinkLocation;
        Uri uploadLocation = root.UploadLocation;
      }

Creating a folder;

      var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        // Get hold of the documents folder from SkyDrive. 
        // NB: this does not traverse the network and get the full folder details.
        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Documents);

        SkyDriveFolder subFolder = await root.CreateFolderAsync("subFolder");
      }

Uploading a file into that folder;

 var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        // Get hold of the documents folder from SkyDrive. 
        // NB: this does not traverse the network and get the full folder details.
        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Documents);

        SkyDriveFolder subFolder = await root.GetFolderAsync("subFolder");

        FileOpenPicker picker = new FileOpenPicker();
        picker.FileTypeFilter.Add(".jpg");
        StorageFile file = await picker.PickSingleFileAsync();

        SkyDriveFile skyDriveFile = await subFolder.UploadFileAsync(file);
      }

Downloading a file from that folder;

     var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        // Get hold of the documents folder from SkyDrive. 
        // NB: this does not traverse the network and get the full folder details.
        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Documents);

        SkyDriveFolder subFolder = await root.GetFolderAsync("subFolder");
        SkyDriveFile skyDriveFile = await subFolder.GetFileAsync("flickrPicture.jpg");

        FileSavePicker picker = new FileSavePicker();
        picker.FileTypeChoices.Add("Jpeg", new List<string>() { ".jpg" });
        StorageFile localFile = await picker.PickSaveFileAsync();

        await skyDriveFile.DownloadAsync(localFile);
      }

Moving that sub folder to another folder;

     var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        SkyDriveFolder docs = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Documents);

        SkyDriveFolder subFolder = await docs.GetFolderAsync("subFolder");

        // Load full details of the root folder to get its ID populated. This
        // is a bit opaque in my wrapped API :-(
        await root.LoadAsync();

        subFolder = await subFolder.MoveAsync(root);
      }

Moving the file from the sub-folder to the root folder;

    var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        SkyDriveFolder subFolder = await root.GetFolderAsync("subFolder");

        SkyDriveFile file = await subFolder.GetFileAsync("flickrPicture.jpg");

        // Populate the root folder's ID (opaque :-()
        await root.LoadAsync();

        file = await file.MoveAsync(root);
      }

and copying it back into the sub-folder;

    var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        SkyDriveFile file = await root.GetFileAsync("flickrPicture.jpg");

        SkyDriveFolder subFolder = await root.GetFolderAsync("subFolder");

        file = await file.CopyAsync(subFolder);
      }

and then deleting that file and the containing folder;

      var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        SkyDriveFolder subFolder = await root.GetFolderAsync("subFolder");

        SkyDriveFile file = await subFolder.GetFileAsync("flickrPicture.jpg");

        await file.DeleteAsync();
        await subFolder.DeleteAsync();

      }

and, enumerating files;

  var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        SkyDriveFolder subFolder = await root.GetFolderAsync("private");

        foreach (SkyDriveFile file in await subFolder.GetFilesAsync())
        {
          string n = file.Name;
        }
      }

and folders/albums (which I treat the same way – not sure if that’s the correct thing to do or not);

      var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        foreach (SkyDriveFolder folder in await root.GetFoldersAsync())
        {
          string n = folder.Name;
        }
      }

and changing the properties of a file (or folder);

     var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        SkyDriveFile file = await root.GetFileAsync("flickrPicture.jpg");

        file.Name = "MyPicture.jpg";
        file.Description = "Picture Description";
        await file.SaveAsync();
      }

Back to the Initial Scenario

Finally, back to the business of selecting a bunch of pictures from my local machine and updating them to some folder (say ‘foo’) on SkyDrive. Here’s trying that;

      var scopes = new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" };

      LiveAuthClient authClient = new LiveAuthClient();

      LiveLoginResult result = await authClient.LoginAsync(scopes);

      if (result.Status == LiveConnectSessionStatus.Connected)
      {
        LiveConnectClient cxnClient = new LiveConnectClient(authClient.Session);

        SkyDriveFolder root = new SkyDriveFolder(
          cxnClient, SkyDriveWellknownFolder.Root);

        SkyDriveFolder fooFolder = await root.GetFolderAsync("foo");

        if (fooFolder == null)
        {
          fooFolder = await root.CreateFolderAsync("foo");
        }
        FileOpenPicker picker = new FileOpenPicker();
        picker.FileTypeFilter.Add(".jpg");
        var files = await picker.PickMultipleFilesAsync();
        var taskList = new List<Task<SkyDriveFile>>();

        foreach (var file in files)
        {
          taskList.Add(fooFolder.UploadFileAsync(file));
        }
        await Task.WhenAll(taskList);
      }

and that seems to work reasonably well for a first attempt Smile


Posted Wed, Sep 26 2012 1:01 AM by mtaulty