はじめに
前回 に引き続き、Live Frameworkの.NET Framework用ライブラリ.NET Kitを使用してLiveサービスへアクセスします。前回 はMesh Objectの取得や作成などを行いました。今回はMesh Objectなどが更新された場合にその通知を受け取る方法、データ同期時の衝突と解決方法を中心に紹介します。
本記事中のコードは前回 同様VB.NETを使用します。コードの内容は動作の確認程度ですのでVisual Studioのプロジェクトは前回 のものに書き加える形で問題ありません。
デバイスの取得
最初にMeshに接続されているデバイス一覧を取得してみましょう。Mesh上のデバイスは、これまでに本連載で何度か登場したDevice Ringからも確認できます(図1 ) 。
図1 Device Ring
デバイスの取得は、前回 のMesh Objectと同様の方法で参照することができます。リソースモデル(一部)を確認すると、Meshサービスの下、Mesh Objectと同じ階層にDeviceがあります(図2 ) 。
図2 リソースモデル:Device
コードはLINQを使用して次のように書くことができます。コードでは各デバイスの名前を表示しています。
Dim loe = New LiveOperatingEnvironment
loe.Connect(New NetworkCredential("userid" , "password" ), New LiveItemAccessOptions(True ))
For Each d In loe.Mesh.Devices.Entries
Console.WriteLine(d.Resource.Title)
Next
クラウド上のLive Operating Environment(LOE)(http://user-ctp.windows.net/)ではなく、クライアント上のLOE(http://localhost:2048/)に接続している場合、アプリケーションが実行されているデバイスを取得するには次のようになります。
If loe.IsLocalConnection Then
Dim device = loe.Mesh.LocalDevice
End If
クラウド上のLOEに接続している場合は、loe.Mesh.LocalDeviceの値はNothing(null)です。
変更の通知を受信
Live Frameworkでは、Mesh ObjectやData Feed・Data Entryなどのコレクションが変更されたとき、イベントとしてその変更を知ることができます。これは、同一のまたは複数のアプリケーション間でデータの共有に大いに役立つでしょう。
Mapping
変更通知を受け取るには、同期する単位であるMesh Objectに対してどのデバイスと同期するのかを個別に設定しておく必要があります。ただし、通知を受け取れないのはクライアント上のLOEと接続する場合に限ります。つまり同期対象となっていないデバイス上のクライアントLOEに接続しても、変更通知は受け取れないということです。
Mesh Objectがどのデバイスと同期対象として関連付けられているかは、Mappingというオブジェクトを使用します。リソースモデルは図3 の通りです。
図3 リソースモデル:Mapping
ひとつのMesh ObjectはMappingのコレクションを参照しています。ひとつのMappingには、ひとつのデバイス情報を持っています。また、MappingもData Entryなどと同様にリソースのひとつであり、リソースとしての基本情報を持っています。
Mesh Objectにデバイスを関連付ける、つまりMesh ObjectのMappingコレクションにMappingを追加するコードは、次のようになります。コードではMesh内にあるMesh Objectのうち最初に見つかったLive Mesh フォルダ(Resource.Typeが"LiveMeshFolder")に対してすべてのデバイスを関連付けています。
Dim mos = From mo In loe.Mesh.MeshObjects.Entries _
Where mo.Resource.Type = "LiveMeshFolder" _
Take 1
Dim meshObject = mos.SingleOrDefault
For Each d In loe.Mesh.Devices.Entries
Dim map = New Mapping
map.Device = d
meshObject.Mappings.Add(map)
Next
上記のコードでは、少なくともひとつのLive Meshフォルダが存在している必要があります。また、関連付けしようとしたデバイスが既に関連付いている場合、正しく動作しません。実際には次のようにチェックが必要です。
For Each d In loe.Mesh.Devices.Entries
Dim id = d.Resource.Id
If meshObject.Mappings.Entries.Any(Function (m) m.Device.Resource.Id = id) Then
Continue For
End If
Dim map = New Mapping
map.Device = d
meshObject.Mappings.Add(map)
Next
上記コードではすべてのリソース(ここではDevice)にあるIdプロパティを用いてチェックを行っています。
Live Mesh Betaサービスを思い出してみてください。デバイスがWindows PCの場合、エクスプローラと統合しクライアントPC上の実フォルダとしてLive Meshフォルダが存在していました。こういったMesh Objectと対象デバイスのファイルパスとの対応関係を表すには、MappingのResource.DataFeedMappingsプロパティを使用します。ただし、現在のLive Framework CTPではエクスプローラ統合機能はありませんので、Live Meshフォルダを作成してもこのプロパティには値が格納されていません。
ChangeNotificationReceived
それでは変更通知を受け取ってみましょう。変更を知るだけであれば非常に簡単です。Mesh Objectや、Data Feedなどのコレクションを表すオブジェクトにはChangeNotificationReceivedというイベントがあり、これを利用します。
Mesh Object、Data Feed、Data Entryの変更を受け取るコードは以下のようになります。コードではMesh上で最初に見つかったLive Meshフォルダに対する変更を受信するようにしています。
Dim mos = From mo In loe.Mesh.MeshObjects.Entries _
Where mo.Resource.Type = "LiveMeshFolder" _
Take 1
Dim meshObject = mos.SingleOrDefault
AddHandler meshObject.ChangeNotificationReceived, AddressOf MeshObject_ChangeNotificationReceived
AddHandler meshObject.DataFeeds.ChangeNotificationReceived, AddressOf DataFeeds_ChangeNotificationReceived
For Each df In meshObject.DataFeeds.Entries
AddHandler df.DataEntries.LiveItemCollectionChanged, AddressOf DataEntries_LiveItemCollectionChanged
Next
Private Sub MeshObject_ChangeNotificationReceived(ByVal sender As Object , ByVal e As EventArgs)
Console.WriteLine("MeshObject_ChangeNotificationReceived" )
End Sub
Private Sub DataFeeds_ChangeNotificationReceived(ByVal sender As Object , ByVal e As EventArgs)
Console.WriteLine("DataFeeds_ChangeNotificationReceived" )
End Sub
Private Sub DataEntries_ChangeNotificationReceived(ByVal sender As Object , ByVal e As EventArgs)
Console.WriteLine("DataEntries_ChangeNotificationReceived" )
End Sub
変更を受信した場合、コンソールに呼び出されたメソッド名を表示しています。実際にアプリケーションを実行して、Live Desktop からLive Meshフォルダ内のファイルの追加・削除・変更、フォルダ自身の変更などの操作をして通知されるか確認してみてください。操作から通知されるまで多少の時間は要しますが、それほど時間はかからないと思います。この仕組みを利用してチャットアプリケーションにも使用できそうです。
現在のところ、ChangeNotificationReceivedイベントで受け取るsenderオブジェクトは変更のあったMesh Objectやコレクション自身、eオブジェクトはNothing(null)となっており、具体的な変更(追加や削除など)の内容はイベントおよびその引数からはわかりません。
同期と衝突
さて、第1回目 にLive Mesh Betaサービスを紹介したときにも まったく触れていませんでしたが、データの同期には衝突(Conflict)が生じる可能性があります。たとえば、ネットワークがオフライン時にクライアント上のLOEに接続しデータを編集した場合、オンラインになった時点でLOEが自動で更新データを反映します。このとき、オフライン時に別のデバイスから対象データの更新があった場合、更新されたデータのバージョンがふたつ存在することになります。こうなると、どちらを選択するかはアプリケーションまたはユーザーが決定する必要があります。
Live Meshフォルダ内のファイルが衝突した場合は、図4 のようにMesh Barに衝突している旨の表示と、どのバージョンのファイルを残すかユーザーが選択することができます。
図4 ファイルが衝突した場合
衝突はData Entryに対して起こります。前回 に既に紹介した方法でData Entryを参照している限りでは、衝突していてもしていなくても、あるひとつのバージョンのData Entryが参照できます。少し言い換えると衝突しているData Entryも問題なく参照でき、変更も可能です。
衝突の検知
それでは、アプリケーションから衝突しているかどうかを知る方法とその解決方法を確認してみましょう。
Data Entryを列挙する際に これまで使用していたDataEntriesプロパティではなくSyncEntriesプロパティを使用します。SyncEntries.Entriesで列挙されるアイテムはDataEntries.Entriesと同じくDataEntry型です。また、すべてのDataEntriesの内容を含んでいます。ただし、既に削除されたData Entryも参照でき、同期や衝突に関する情報をDataEntry.Syncプロパティから取得することができます。以下に衝突しているData Entryを表示するコードを示します。単純に衝突しているかどうかを知るにはResource.Sync.Conflicts.Countプロパティが0か否かを確認します。
Dim mos = From mo In loe.Mesh.MeshObjects.Entries _
Where mo.Resource.Type = "LiveMeshFolder" _
Take 1
Dim dataFeed = mos.SingleOrDefault.DataFeeds.Entries.First
For Each se In dataFeed.SyncEntries.Entries
If se.Resource.Sync.Conflicts.Count > 0 Then
Console.WriteLine(se.Resource.Title)
End If
Next
アプリケーションからData Entryを区別するにはResource.Idプロパティを使用するとよいでしょう。DataEntriesとSyncEntriesからどちらからも参照できるData Entryは同じResource.Idの値になっています。
既に削除されいてるData Entryは、Resource.IsDeletedプロパティにより参照できます。値がTrueの場合、既に削除されたData Entryです。
衝突の解決
続いて衝突したData Entryを解決、つまりどのバージョンのData Entryを残すかを決定します。最も簡単な方法は、衝突しているSyncEntryのResolveAllConflicts メソッドを呼び出し、SyncEntriesのSynchronize メソッドを呼ぶことです。するとDataEntriesから参照できているData Entryのバージョンが選択されたことになり、衝突が解決されます。次にそのコードを示します。
For Each se In dataFeed.SyncEntries.Entries
If se.Resource.Sync.Conflicts.Count > 0 Then
se.ResolveAllConflicts()
End If
Next
dataFeed.SyncEntries.Synchronize()
このコードを実行後に対象SyncEntryのResource.Sync.Conflicts.Countの値を確認すると0になっているはずです。
より詳しくData Entryの内容を確認してどのData Entryのバージョンを選択するかは、Data EntryのResource.Sync.Conflictsコレクションの内容を確認します。衝突の有無の確認に使用したコレクションですね。このコレクションにはDataEntriesから参照されていないData Entry情報が格納されています。以下にそのコードを示します。
For Each se In dataFeed.SyncEntries.Entries
If se.Resource.Sync.Conflicts.Count > 0 Then
For Each s As DataEntryResource In de.Resource.Sync.Conflicts
Dim title = s.Title
Dim publishDate = s.PublishDate
Dim fistAuthor = s.Authors.First.Name
Next
End If
Next
Data Entryを含む全リソースには基本的な情報としてタイトルや更新日時、更新者などのプロパティが用意されているのでその値を参照しData Entryを選択します。
Resource.Sync.ConflictsコレクションにあるData Entryを残すデータとして選択する場合、SyncEntryのSetConflictingItemAsWinner メソッドを呼び出し、その引数に対象のコレクションアイテムを指定します。以下のコードは単純に最初のコレクションアイテムを選択した場合のものです。
For Each se In dataFeed.SyncEntries.Entries
If se.Resource.Sync.Conflicts.Count > 0 Then
se.SetConflictingItemAsWinner(de.Resource.Sync.Conflicts.First)
End If
Next
dataFeed.SyncEntries.Synchronize()
おわりに
今回はここまでです。簡単ではありますが、Mesh Objectの更新通知の受信と衝突およびその解決について主に紹介しました。このデータの同期に関する部分は、Live Frameworkの特徴というよりもFeedSyncをLive Frameworkが実施している形になっています。FeedSyncは、マイクロソフトが発表したWebベースの同期に関する仕様で、その内容は公開されており誰でも自由に利用することが可能です。
さて、次回はさらにLive Frameworkの内容についての紹介をするとともに、簡単なアプリケーションも作成していく予定です。