(Very) Tentative Steps with the Live Framework SDK

I made my first foray into the Live Framework SDK today and thought I’d share.

Warningapply a fairly large pinch of salt in reading this because I’m stumbling around in the Live Framework SDK at this point just to try and get something working. I’m a long way away ( i.e. even further than usual ) from knowing what I’m doing with this SDK πŸ™‚

Firstly, I’d say that you might want to start here;

The What and Why of Live Framework

to understand what Live Framework is trying to do.

The Live Framework SDK is in a limited CTP at the moment so you can only get it if you can get access to it. The best page to start for this is here but that’ll then lead you to the Connect site.

Once you get access, you can download a few bits and pieces.

  1. The Live Framework SDK. This is just a zip file that you extract into the equivalent of c:\program files\Microsoft SDKs
  2. The Visual Studio Tools for Live Framework – I haven’t got to the point of using these yet but my understanding is that they help in building Mesh enabled web applications (DHTML or Silverlight).

With that done, my first surprise was as follows.

I am already running Live Mesh. I have a bunch of devices ( 3 PC’s and 1 Mac ) in my Live Mesh and I’m syncing folders between them and I have some other folders that come from other folks.

The Live Framework SDK doesn’t hit against this Mesh. It seems to hit against a developer Sandbox Mesh up at;

http://user-ctp.windows.net

and that doesn’t include the devices that I already have in my Mesh. So I had to join my laptop to this new Mesh in the first instance.

With the Live Framework SDK, I write code against resources made available by a Live Operating Environment (LOE).

So, if I want to “just” use the LOE offered by the “Cloud” at http://user-ctp.windows.net then I can reference the assemblies that live in the Libraries folder and then write code such as;

using System;
using System.Linq;
using Microsoft.LiveFX.Client;
using System.Net;

class Program
{
  static void Main(string[] args)
  {
    LiveOperatingEnvironment environment = new LiveOperatingEnvironment();

    environment.ConnectCompleted += (s, e) =>
      {
        if (e.Error == null)
        {
          var query =
            from c in environment.Contacts.Entries
            where c.Resource.FamilyName == "Parry"
            select c;

          foreach (var item in query)
          {
            Console.WriteLine(item);
          }
        }
      };

    environment.ConnectAsync(new NetworkCredential(userName, password), null);

    Console.ReadLine();        
  }

and that seems to work fine ( I tried to omit my userName and password for Windows Live from the code ).

Working purely against the “Cloud” might be fine but I might also want the chance to work locally against the Local LOE which supports scenarios where I’m offline as well as online.

The local Live Operating Environment is found at http://localhost:2048.

Initially, I thought that the Live Mesh Client which I was already running prior to installing any SDK would provide a local LOE.

However, it doesn’t.

To get a local LOE at the moment you have to uninstall the Live Mesh Client and install the “Live Framework Client” instead – they will not both run together ( see this thread ). I downloaded the “Live Framework Client” from here;

https://developer.mesh-ctp.com

and installed it having uninstalled the Live Mesh Beta Client that I already had installed.

With that in place, I can try and run my code against the local LOE as in;

using System;
using System.Linq;
using Microsoft.LiveFX.Client;
using System.Net;

class Program
{
  static void Main(string[] args)
  {
    LiveOperatingEnvironment environment = new LiveOperatingEnvironment();

    environment.ConnectCompleted += (s, e) =>
      {
        if (e.Error == null)
        {
          var query =
            from c in environment.Contacts.Entries
            where c.Resource.FamilyName == "Parry"
            select c;

          foreach (var item in query)
          {
            Console.WriteLine(item);
          }
        }
      };

    environment.ConnectLocalAsync(new LiveItemAccessOptions(true), false);

    Console.ReadLine();        
  }

which I think would be “fine” other than it doesn’t work because (AFAIK) Contacts aren’t actually available from the local LOE yet which is what this topic says.

So…Contacts is a bad example to use as they don’t show up in both the local and the cloud LOE and Profiles would be a similar case.

What can I use? There’s a picture on MSDN showing the resources available;

and so maybe I can go and query into some of those items contained in Mesh.Devices or perhaps Mesh.MeshObjects and see what’s happening in there. For Devices, it seems easy enough;

using System;
using System.Linq;
using Microsoft.LiveFX.Client;
using System.Net;

class Program
{
  static void Main(string[] args)
  {
    LiveOperatingEnvironment environment = new LiveOperatingEnvironment();
    
    environment.ConnectCompleted += (s, e) =>
      {
        if (e.Error == null)
        {
          var query =
            from c in environment.Mesh.Devices.Entries
            select c;

          foreach (var item in query)
          {
            Console.WriteLine(item.Resource.Title);
            Console.WriteLine(item.Resource.LastUpdatedTime);
            Console.WriteLine(item.Resource.IsOnline);
          }
        }
      };

    environment.ConnectLocalAsync(new LiveItemAccessOptions(true), false);

    Console.ReadLine();        
  }

Note that I’m not sure that the IsOnline flag is reported correctly right now as there’s a statement on the “Add Device” website that says;

“Device Status and Live Remote are not currently enabled in the Live Framework sandbox”.

I thought I’d add another device to my developer Mesh and see if my code reflected that new device arriving into my Mesh.

In the spirit of being all cross-platform I thought I’d add my iMac but that was a bit of a mistake πŸ™‚

If you go to;

https://developer.mesh-ctp.com

there is ( or at least there was ) an option to download a Macintosh client. I downloaded it, installed it and tried to use it but it didn’t work so I asked around internally and I found that the Macintosh version should not currently be there as a download on the Developer Sandbox page so don’t download it or try to use it.

To try and be clear – there is a Macintosh version of the Consumer Mesh Client but not the Developer “Live Framework Client” at the time of writing and that download link which suggests that thre is a Developer client for MacOS is a mistake.

So, I decided to switch on my Vista desktop and try and install the client onto that machine instead which all worked fine but then I got into a slightly odd situation where my desktop was showing 3 devices ( desktop, laptop, Live Desktop ) whereas my laptop was still only showing 2 devices ( laptop, Live Desktop ). Also, my code above kept showing a LastUpdateTime for my laptop which was really old as though it wasn’t updating itself.

This one stumped me for quite a while so I ended up uninstalling the client from my laptop and reinstalling it which seemed to solve the problem. From the forums, it looked like there had been some maintenance on the developer services so perhaps my client installation had got in a bit of a tangle.

Now I’ve got 2 machines joined to my Mesh, I was curious as to how I might share data between them beyond what’s already provided for me ( like the list of Devices and so on ).

I sketched out a quick console application to share notes (i.e. a single string) between two or more applications as in;

using System;
using System.Linq;
using Microsoft.LiveFX.Client;
using System.Net;
using System.Threading;
using System.Collections.Generic;

class Program
{
  static List<MeshObject> notes;
  static LiveOperatingEnvironment environment;
  static readonly string resourceType = "Mikes Notes";
  static readonly string userName = "myUserName";
  static readonly string password = "myPassword";

  static void Main(string[] args)
  {
    ManualResetEvent exitEvent = new ManualResetEvent(false);

    environment = new LiveOperatingEnvironment();
    
    environment.ConnectCompleted += (s, e) =>
      {
        if (e.Error == null)
        {
          ReadDisplayLoop();          
        }
        else
        {
          Console.WriteLine("Error in connecting [{0}]", e.Error.Message);
        }
        exitEvent.Set();
      };

    environment.ConnectAsync(new NetworkCredential(userName, password), null);

    exitEvent.WaitOne();     
  }
  static void PopulateNotes()
  {
    notes =
      environment.Mesh.CreateQuery<MeshObject>().
        Where(mo => mo.Resource.Type == resourceType).
        OrderBy(mo => mo.Resource.PublishDate).ToList();
  }
  static void SyncChangeNotification()
  {
    environment.Mesh.MeshObjects.ChangeNotificationReceived += (s, e) =>
      {
        Console.WriteLine("\t[Change Notification Received...notes updated]");
        PopulateNotes();
      };
  }
  static void AddNote()
  {
    Console.WriteLine("Enter the text for the note >>");
    string noteText = Console.ReadLine();
    MeshObject newMo = new MeshObject(noteText);
    newMo.Resource.Type = resourceType;
    environment.Mesh.MeshObjects.Add(ref newMo);
  }
  static void RemoveNote()
  {
    if (notes.Count > 0)
    {
      Console.WriteLine("Which note to remove? [1-{0}]",
        notes.Count);
    }
    int noteNumber;

    if (int.TryParse(Console.ReadLine(), out noteNumber) &&
      ((noteNumber >= 1) && (noteNumber <= notes.Count)))
    {
      environment.Mesh.MeshObjects.Remove(notes[noteNumber - 1]);
    }
  }
  static void ReadDisplayLoop()
  {
    bool exit = false;

    SyncChangeNotification();
    PopulateNotes();

    while (!exit)
    {
      Console.WriteLine("Select option [D]isplay, [A]dd, [R]emove [E]xit");

      string option = Console.ReadLine().ToUpper();

      switch (option)
      {
        case "D":
          for (int i = 0; i < notes.Count; i++)
          {
            Console.WriteLine("Note [{0}], Text [{1}]", i+1, notes[i]);
          }
          break;
        case "A":
          AddNote();
          break;
        case "R":
          RemoveNote();
          break;
        case "E":
          exit = true;
          break;
        default:
          break;
      }
    }
  }
}

and so what this is doing (in one big static class :-() is creating Mesh Objects of a hard-coded type “Mikes Notes” and then offering the user a simple Display/Add/Remove loop and then I can run this on both my desktop and laptop at the same time and it seems to keep itself in sync nicely even though it’s my first attempt and I’m not really sure what I’m doing πŸ™‚

Now, the code above connects to the “Cloud” LOE rather than the “Local” LOE and so I wouldn’t expect that it would work online/offline.

Having said that, I had a naive hope that if I just changed the code so that it did;

environment.ConnectLocalAsync

rather than;

environment.ConnectAsync

then it might just work magically online/offline but I don’t think it does πŸ™‚

Specifically, if I try and invoke the paths that do Add or Remove when offline then I get an exception as it times out when I call the MeshObjects.Add method.

Now…this whole thing opened up an interesting question for me around whether I should actually be creating MeshObjects here or whether I only need one MeshObject with one or more DataFeeds hanging off it. I haven’t quite got to the bottom of that yet but I found this discussion;

http://social.msdn.microsoft.com/Forums/en-US/liveframework/thread/ee869a8e-fdbe-411f-a78d-c3badc9fbb0a 

an interesting one and it did lead to me think that I was creating way too many MeshObjects and I should perhaps just created one with a DataFeed hanging off it for my data that I need to sync.

So, I rewrote my code to try and work this way instead of the original way that I had it;

using System;
using System.Linq;
using Microsoft.LiveFX.Client;
using System.Net;
using System.Threading;
using System.Collections.Generic;
using Microsoft.LiveFX.ResourceModel;
using System.Text;

class Program
{
  static MeshObject notesObject;
  static DataFeed notesFeed;
  static List<DataEntry> notes;
  static LiveOperatingEnvironment environment;
  static AutoResetEvent repopulateEvent;

  static void Main(string[] args)
  {
    ManualResetEvent exitEvent = new ManualResetEvent(false);    

    repopulateEvent = new AutoResetEvent(false);

    environment = new LiveOperatingEnvironment();
    
    environment.ConnectCompleted += (s, e) =>
      {
        if (e.Error == null)
        {
          ReadDisplayLoop();          
        }
        else
        {
          Console.WriteLine("Error in connecting [{0}]", e.Error.Message);
        }
        exitEvent.Set();
      };

    environment.ConnectLocalAsync(null);

    WaitHandle[] handles = new WaitHandle[] { repopulateEvent, exitEvent };

    while (WaitHandle.WaitAny(handles) == 0)
    {
      // This is here to repopulate my list of notes on the "main" thread
      // rather than to let it happen on a background thread. Bit of a hacky
      // way of doing things ( primitive message loop really ).
      PopulateNotes();
    }    
  }
  static void CreateOrGetNotesMeshObject()
  {
    // Seems subject to all kinds of synchronisation races to me.
    notesObject =
      environment.Mesh.MeshObjects.Entries.Where(m => m.Resource.Title == "Notes").FirstOrDefault();

    if (notesObject == null)
    {
      notesObject = new MeshObject("Notes");
      notesObject.Resource.Type = "Notes_Object";
      environment.Mesh.MeshObjects.Add(ref notesObject);
      notesObject.Update();

      notesFeed = new DataFeed("NotesFeed");
      notesFeed.Resource.Type = "Notes_Feed";
      notesObject.DataFeeds.Add(ref notesFeed);

      CreateMappingsToTestMachines();

      notesObject.Update();
    }
    else
    {
      notesFeed = 
        notesObject.DataFeeds.Entries.Where(df => df.Resource.Title == "NotesFeed").First();
    }
  }
  private static void CreateMappingsToTestMachines()
  {
    // Get my home PC.
    // Unsure of how much/little of this I need to do right now in terms of this mapping
    // but I seem to need to do some of it. 
    MeshDevice homePc =
      environment.Mesh.Devices.Entries.Where(d => d.Resource.Title == "MTHOME").First();

    MeshDevice laptopPc =
      environment.Mesh.Devices.Entries.Where(d => d.Resource.Title == "MTHPVISTA").First();

    Mapping mapping = new Mapping("Desktop_Mapping");
    mapping.Resource.DeviceLink = homePc.Resource.SelfLink;
    notesObject.Mappings.Add(ref mapping);

    mapping = new Mapping("Laptop_Mapping");
    mapping.Resource.DeviceLink = laptopPc.Resource.SelfLink;
    notesObject.Mappings.Add(ref mapping);
  }
  static void PopulateNotes()
  {
    // Not entirely sure on the calling of Load here but seems to be important
    // otherwise I seem to get change notifications which don't result in data
    // changes - i.e. maybe without calling Load() I am hitting a cached version?
    notesFeed.DataEntries.Load();
    notes = notesFeed.DataEntries.Entries.ToList();
  }
  static void SyncChangeNotification()
  {
    notesFeed.DataEntries.ChangeNotificationReceived += (s, e) =>
      {
        Console.WriteLine("\t[Change Notification Received...notes updated]");
        repopulateEvent.Set();
      };
  }
  static void AddNote()
  {
    Console.WriteLine("Enter the text for the note >>");
    string noteText = Console.ReadLine();
    DataEntry entry = new DataEntry(noteText);
    entry.Resource.Type = "Notes_Entry";
    notesFeed.DataEntries.Add(ref entry);
    notesFeed.Update();    
  }
  static void RemoveNote()
  {
    if (notes.Count > 0)
    {
      Console.WriteLine("Which note to remove? [1-{0}]",
        notes.Count);
    }
    int noteNumber;

    if (int.TryParse(Console.ReadLine(), out noteNumber) &&
      ((noteNumber >= 1) && (noteNumber <= notes.Count)))
    {
      notesFeed.DataEntries.Remove(notes[noteNumber - 1]);
    }
  }
  static void ReadDisplayLoop()
  {
    bool exit = false;

    CreateOrGetNotesMeshObject();

    PopulateNotes();

    SyncChangeNotification();

    while (!exit)
    {
      Console.WriteLine("Select option [D]isplay, [A]dd, [R]emove, [C]lear & Exit, [E]xit");

      string option = Console.ReadLine().ToUpper();

      try
      {
        switch (option)
        {
          case "D":
            for (int i = 0; i < notes.Count; i++)
            {
              Console.WriteLine("Note [{0}], Text [{1}]", i + 1, notes[i]);
            }
            break;
          case "A":
            AddNote();
            break;
          case "R":
            RemoveNote();
            break;
          case "E":
            exit = true;
            break;
          case "C":
            exit = true;
            Clear();
            break;
          default:
            break;
        }
      }
      catch (Exception ex)
      {
        DumpException(ex);
      }
    }
  }
  private static void DumpException(Exception ex)
  {
    Exception e = ex;
    StringBuilder sb = new StringBuilder();

    while (e != null)
    {
      sb.AppendFormat("Error [{0}]\n", e.Message);
      e = e.InnerException;
    }
    Console.WriteLine("Error [{0}]", sb.ToString());
  }
  private static void Clear()
  {
    // Attempts to get rid of everything in order to start again.
    try
    {
      environment.Mesh.MeshObjects.Load();

      foreach (var item in environment.Mesh.MeshObjects.Entries)
      {       
        environment.Mesh.MeshObjects.Remove(item);        
      }
    }
    catch (Exception ex)
    {
      DumpException(ex);
    }
  }

}

Now, some notes about that code.

  1. I spent ages on it. Far longer than I’d care to admit. This was ( of course ) because I don’t know what I’m doing and I spent quite a while around trying to set up those Mappings and whether I did or didn’t need them. I also spent a long time with code where the function above called PopulateNotes did not call Load on the DataEntries which seemed to leave me with stale data.
  2. I’m still pretty sure it’s not correct because from time to time it throws an exception and yet seems to still “half work” around that exception.

However…it does seem to work offline in the sense that I could run through;

  1. Run the code on my desktop.
  2. Disable the network card.
  3. Add an entry using the console ( note – exception gets thrown here ).
  4. Enable the network card.
  5. Change notification hits my laptop.
  6. Display the entries on my laptop which now reflect the offline entry added on my desktop.

But…I can’t be 100% sure that it’s working the way that I expect it to just yet but at least I’ve made a start.

I think my next step might be to try and build a GUI rather than a console application and perhaps get something more interesting than just a single line of text stored in my Mesh although I’m unsure as to whether I can go as far as to synchronise files as I’m not sure if that’s enabled in the CTP.