Synchronization in the Live Framework

A core feature exposed in the Live Framework is the synchronization of data feeds. Synchronization in Live Framework is provided by its implementation of the FeedSync specification developed by Microsoft to support synchronization of Atom and RSS feeds. This post will use Atom feeds in any examples, and will make no further mention of RSS.

FeedSync

FeedSync specifies several extension elements which can be added to an Atom feed. The Atom Syndication Format specification mandates that Atom processors "MUST NOT change their behavior as a result" of the presence of foreign markup such as the FeedSync elements. The FeedSync documentation is pretty comprehensive and the specification itself is readable and short, and anyone interested in Live Framework synchronization really ought to read it as background.

A crucial feature of feed synchronization is the ability to handle conflicts arising when the same entry is updated in two or more representations of a feed without any intermediate synchronization of the feed. FeedSync specifies how a FeedSync processor should handle the merging of these feed representations when synchronization eventually does occur. The FeedSync specification states how a FeedSync processor should use the FeedSync extension elements to describe any conflicts in a way that no information is lost prior to a final decision being made, either programmatically or with human intervention, about which entry is chosen as the winner in the conflict.

The following is an example sync entry from the FeedSync specification.

<feed xmlns="http://www.w3.org/2005/Atom"  xmlns:sx="http://feedsync.org/2007/feedsync">
...
<entry> <title>Buy groceries - DONE</title> <content>Get milk, eggs, butter and bread</content> <updated>2005-05-21T12:43:33Z</updated> <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0aa0</id> <author>
<name>Ray Ozzie</name> </author> <sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="4">
<sx:history sequence="4" when="2005-05-21T12:43:33Z" by="GPM7383"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
<sx:conflicts>
<entry>
<title>Buy groceries</title>
<content>Get milk, eggs, butter and rolls</content>
<updated>2005-05-21T12:43:33Z</updated>
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0aa0</id>
<author>
<name>Ray Ozzie</name>
</author>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="4">
<sx:history sequence="4" when="2005-05-21T12:43:33Z" by="JEO2000"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</entry>
</sx:conflicts> </sx:sync> </entry>

An sx:sync element indicates to a feed processor that the feed contains FeedSync data. sx:sync is the most important element in FeedSync because it contains the information required for synchronization. An sx:sync element contains the change history in a collection of sx:history elements. It also contains an sx:conflicts element that in turn contains a copy of each conflicting entry discovered during the FeedSync synchronization. The complete version of these entries, including their sync histories is stored in the conflict entry. The deletion of an entry is indicated by the creation of a tombstone which merely describes the addition of an attribute/value pair deleted="true" to an sx:sync element.

FeedSync specifies how these entries are maintained during synchronization and also specifies the behavior when conflict resolution is performed automatically. The behavior described in the specification can be overridden by human intervention if the SyncFeed processor provides that capability.

Live Framework API

The Live Framework API exposes FeedSync data in the DataFeed class as follows:

The DataFeed class has two public properties defined as follows:

    public DataEntryCollection DataEntries { get; }
    public SyncEntryCollection SyncEntries { get; }

Both DataEntryCollection and SyncEntryCollection are derived directly from LiveItemCollection<DataEntry,DataEntryResource> so both represent collections of DataEntryResources with additional functionality specific to their roles as data entry containers and sync entry containers respectively.

DataEntries is a collection of every data entry in the data feed while SyncEntries is the collection of every sync entry in the data feed. When no conflict has been identified in a data entry its corresponding sync entry will be identical except that the sync entry will have a sx:sync element containing the sx:history elements describing the change history of the element. When a conflict has been detected the sync entry will additionally contain a complete description of the conflict as shown in the example above. Furthermore, when a conflict has been detected involving the deletion of a data entry in which the deletion dominates the conflict then the data element will not be present in the DataEntries collection but will be present in the SyncEntries collection. The sync entry will be removed from SyncEntries only when the conflict has been resolved with a definitive deletion of the entry. The data entry will be added back into DataEntries if the conflict is resolved with the revival of the sync entry.

The DataEntryResource class exposes the sx:sync in its Sync property defined as follows:

public Sync Sync { get; set; }

The Sync class exposes the information from the sx:sync element in various properties defined as follows:

public IList<IFeedSyncItem> Conflicts { get; }
public Boolean Deleted { get; set; }
public IList<History> Histories { get; }
public Boolean NoConflicts { get; set; }
public History TopMostHistory { get; }
public UInt32 Updates { get; set; }

These are pretty self-evident. The Deleted, NoConflicts, and Updates properties represent the equivalent attribute on an sx:sync element. Histories contains the list of sx:history elements and, presumably, TopMostHistory contains the most recent history. Conflicts exposes the IFeedSyncItem interfaces for a list of DataEntryResources, each representing an entry contained in the sx:conflicts element. As a reminder, DataEntryResource is actually defined as:

public sealed class DataEntryResource : Resource, IFeedSyncItem

Consequently, the DataEntryResource of a sync element can be accessed through

syncEntry.Resource

while the DataEntryResource of the first conflict could be accessed as:

syncEntry.Resource.Sync.Conflicts.ElementAt(0) as DataEntryResource

Conflict Resolution

The Live Framework API provides several DataEntry methods supporting automatic conflict resolution:

public void ResolveAllConflicts();
public void ResolveConflict(IFeedSyncItem conflictItem);
public void SetConflictingItemAsWinner(IFeedSyncItem winningConflict);

These are invoked on a sync entry. Their names are self-explanatory with ResolveAllConflicts resolving every conflict, ResolveConflict resolving only a specified conflict from the Conflicts list, and SetConflictingItemAsWinner specifying that a conflict from the Conflicts list is the winning conflict. The sync feed must be synchronized after one of these methods has been invoked to that the data entry is correctly synchronized everywhere.

An example of this is as follows:

DataFeed dataFeed = (from df in meshObject.DataFeeds.Entries
                                 select df).First();

IEnumerable<DataEntry> syncEntries = (  from de in dataFeed.SyncEntries.Entries

foreach (DataEntry syncEntry in syncEntries)
{
    if (syncEntry .Resource.Sync.Conflicts.Count > 0)
    {
        IList<IFeedSyncItem> conflicts = syncEntry .Resource.Sync.Conflicts;
        syncEntry .SetConflictingItemAsWinner(conflicts[0]);
    }
}

dataFeed.SyncEntries.Synchronize();

As an aside. I have had problems resolving a conflict so that an entry will be deleted. This could be my error caused by the way I created conflicts almost simultaneously locally and on the cloud. However, when I looked at the sync data I thought the delete should have won. I will put it down to user error.

Updating a Data Entry Using HTTP

Jake Shelby has a useful post on an MSDN Forum thread showing the necessity of setting the ETag when updating and deleting data entries using raw HTTP as in the Live Fx Resource Browser. The ETag is provided in the resource header when a data entry is retrieved and MUST be passed in the request header of a PUT to prove that the update is being applied to the correct version of the data. The ETag is specified using the If-Match header and is inserted as a number without surrounding quotation marks as in:

If-Match: 24

where 24 is the value of the ETag.

UPDATE: An earlier version of this section suggested incorrectly that an ETag was also needed to perform a DELETE.

Live Framework Documentation

The Live Framework documentation for synchronization is here for the .Net API and here for HTTP.

Technorati Tags: ,

About Neil Mackenzie

Cloud Solutions Architect. Microsoft
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s