使ってみよう! Live Framework

第5回.NET Kit(2)

はじめに

前回に引き続き、Live Frameworkの.NET Framework用ライブラリ.NET Kitを使用してLiveサービスへアクセスします。前回はMesh Objectの取得や作成などを行いました。今回はMesh Objectなどが更新された場合にその通知を受け取る方法、データ同期時の衝突と解決方法を中心に紹介します。

本記事中のコードは前回同様VB.NETを使用します。コードの内容は動作の確認程度ですのでVisual Studioのプロジェクトは前回のものに書き加える形で問題ありません。

デバイスの取得

最初にMeshに接続されているデバイス一覧を取得してみましょう。Mesh上のデバイスは、これまでに本連載で何度か登場したDevice Ringからも確認できます図1⁠。

図1 Device Ring
図1 Device Ring

デバイスの取得は、前回のMesh Objectと同様の方法で参照することができます。リソースモデル(一部)を確認すると、Meshサービスの下、Mesh Objectと同じ階層にDeviceがあります図2⁠。

図2 リソースモデル:Device
図2 リソースモデル:Device

コードはLINQを使用して次のように書くことができます。コードでは各デバイスの名前を表示しています。

' Imports Microsoft.LiveFX.Client
' Imports System.Net
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
    ' クライアント上のLOEに接続している場合

    ' デバイスの取得
    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
図3 リソースモデル:Mapping

ひとつのMesh ObjectはMappingのコレクションを参照しています。ひとつのMappingには、ひとつのデバイス情報を持っています。また、MappingもData Entryなどと同様にリソースのひとつであり、リソースとしての基本情報を持っています。

Mesh Objectにデバイスを関連付ける、つまりMesh ObjectのMappingコレクションにMappingを追加するコードは、次のようになります。コードではMesh内にあるMesh Objectのうち最初に見つかったLive Mesh フォルダ(Resource.Typeが"LiveMeshFolder")に対してすべてのデバイスを関連付けています。

' Live Mesh フォルダの取得
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 ' Mapping オブジェクト作成
    map.Device = d ' デバイスの指定
    meshObject.Mappings.Add(map) ' Mapping コレクションに追加
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 ' Mapping オブジェクト作成
    map.Device = d ' デバイスの指定
    meshObject.Mappings.Add(map) ' Mapping コレクションに追加
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フォルダに対する変更を受信するようにしています。

' Live Mesh フォルダの取得
Dim mos = From mo In loe.Mesh.MeshObjects.Entries _
   Where mo.Resource.Type = "LiveMeshFolder" _
   Take 1
Dim meshObject = mos.SingleOrDefault

' Mesh Object に対する変更通知
AddHandler meshObject.ChangeNotificationReceived, AddressOf MeshObject_ChangeNotificationReceived

' Mesh Object 内の Data Feed コレクションに対する変更通知
AddHandler meshObject.DataFeeds.ChangeNotificationReceived, AddressOf DataFeeds_ChangeNotificationReceived

' Data Feed 内の Data Entry コレクションに対する変更通知
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 ファイルが衝突した場合
図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か否かを確認します。

' Live Mesh フォルダの取得
Dim mos = From mo In loe.Mesh.MeshObjects.Entries _
   Where mo.Resource.Type = "LiveMeshFolder" _
   Take 1

' Data Feed 取得
Dim dataFeed = mos.SingleOrDefault.DataFeeds.Entries.First

' これまでの方法で Data Entry を参照する場合
'For Each de In dataFeed.DataEntries.Entries
'    Console.WriteLine(de.Resource.Title)
'Next

' Sync Entry
For Each se In dataFeed.SyncEntries.Entries
    If se.Resource.Sync.Conflicts.Count > 0 Then
        ' 衝突している Data Entry がある
        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のバージョンが選択されたことになり、衝突が解決されます。次にそのコードを示します。

' Sync Entry
For Each se In dataFeed.SyncEntries.Entries
    If se.Resource.Sync.Conflicts.Count > 0 Then
        ' 衝突している Data Entry がある

        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

        ' 衝突している Data Entry の別バージョンの参照
        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メソッドを呼び出し、その引数に対象のコレクションアイテムを指定します。以下のコードは単純に最初のコレクションアイテムを選択した場合のものです。

' Sync Entry
For Each se In dataFeed.SyncEntries.Entries
    If se.Resource.Sync.Conflicts.Count > 0 Then
        ' 衝突している Data Entry がある

        ' Data Entryの選択
        se.SetConflictingItemAsWinner(de.Resource.Sync.Conflicts.First)
    End If
Next
dataFeed.SyncEntries.Synchronize()

おわりに

今回はここまでです。簡単ではありますが、Mesh Objectの更新通知の受信と衝突およびその解決について主に紹介しました。このデータの同期に関する部分は、Live Frameworkの特徴というよりもFeedSyncをLive Frameworkが実施している形になっています。FeedSyncは、マイクロソフトが発表したWebベースの同期に関する仕様で、その内容は公開されており誰でも自由に利用することが可能です。

さて、次回はさらにLive Frameworkの内容についての紹介をするとともに、簡単なアプリケーションも作成していく予定です。

おすすめ記事

記事・ニュース一覧