同步處理檔案同步處理提供者與其他提供者
本主題描述如何建立 Managed 應用程式,讓檔案同步處理提供者能夠與其他 Sync Framework 提供者同步處理。在此情況下,雖然其他提供者是簡單提供者,不過您也可以使用標準自訂提供者。如需簡單提供者的詳細資訊,請參閱實作簡單的自訂提供者。
此應用程式有兩個主要需求:
此應用程式必須將 IFileDataRetriever 實作成兩個提供者之間的資料傳輸介面。
簡單提供者與檔案同步處理提供者必須使用相同的識別碼格式:16 位元組 GUID (代表複寫識別碼)、前面加上 8 位元組前置詞的 16 位元組 GUID (代表項目識別碼),以及 4 位元組整數 (代表變更單位識別碼)。
Managed 程式碼範例
本章節中的範例會顯示一些與同步處理檔案同步處理提供者和簡單提供者有關的程式碼,並將焦點放在識別碼和傳輸介面需求上。若要在完整應用程式的內容中檢視這段程式碼,請參閱程式碼庫 (英文) 所提供的 "Sync 101 - Synchronizing a File Synchronization Provider with a Simple Provider"
應用程式。
下列程式碼範例會顯示簡單提供者的 IdFormats 屬性定義如何符合先前所述的需求。
public override SyncIdFormatGroup IdFormats
{
get
{
SyncIdFormatGroup idFormats = new SyncIdFormatGroup();
idFormats.ItemIdFormat.Length = 24;
idFormats.ItemIdFormat.IsVariableLength = false;
idFormats.ReplicaIdFormat.Length = 16;
idFormats.ReplicaIdFormat.IsVariableLength = false;
idFormats.ChangeUnitIdFormat.Length = 4;
idFormats.ChangeUnitIdFormat.IsVariableLength = false;
return idFormats;
}
}
Public Overrides ReadOnly Property IdFormats() As SyncIdFormatGroup
Get
Dim FormatGroup As New SyncIdFormatGroup()
FormatGroup.ItemIdFormat.Length = 24
FormatGroup.ItemIdFormat.IsVariableLength = False
FormatGroup.ReplicaIdFormat.Length = 16
FormatGroup.ReplicaIdFormat.IsVariableLength = False
FormatGroup.ChangeUnitIdFormat.Length = 4
FormatGroup.ChangeUnitIdFormat.IsVariableLength = False
Return FormatGroup
End Get
End Property
下列程式碼範例會顯示簡單提供者所需的兩種方法:LoadChangeData 和 InsertItem。雖然這些方法中的許多程式碼都是實作簡單提供者特有的,不過您應該注意其中兩個區域:
LoadChangeData 是用來載入已經從本機存放區 (簡單提供者所服務的存放區) 列舉的資料。這個方法會將資料傳回成
SimpleFileDataRetriever
物件,也就是範例的 IFileDataRetriever 實作。public override object LoadChangeData(ItemFieldDictionary keyAndExpectedVersion, IEnumerable<SyncId> changeUnitsToLoad, RecoverableErrorReportingContext recoverableErrorReportingContext) { // Figure out which item is being asked for string localRelativePath; long expectedLMT; ParseDictionary(keyAndExpectedVersion, out localRelativePath, out expectedLMT); string localPath = Path.Combine(this.rootFolder, localRelativePath); string currentVersion = File.GetLastWriteTimeUtc(localPath).Ticks.ToString(); // Check if it changed --- race condition! if (File.GetLastWriteTimeUtc(localPath).Ticks != expectedLMT) { recoverableErrorReportingContext.RecordRecoverableErrorForChange( new RecoverableErrorData(null)); return null; } // Return return new SimpleFileDataRetriever(localRelativePath, null, localPath, File.GetAttributes(localPath)); }
Public Overrides Function LoadChangeData(ByVal keyAndExpectedVersion As ItemFieldDictionary, ByVal changeUnitsToLoad As IEnumerable(Of SyncId), ByVal recoverableErrorReportingContext As RecoverableErrorReportingContext) As Object ' Figure out which item is being asked for Dim localRelativePath As String = "" Dim expectedLMT As Long ParseDictionary(keyAndExpectedVersion, localRelativePath, expectedLMT) Dim localPath As String = Path.Combine(Me.rootFolder, localRelativePath) Dim currentVersion As String = File.GetLastWriteTimeUtc(localPath).Ticks.ToString() ' Check if it changed --- race condition! If File.GetLastWriteTimeUtc(localPath).Ticks <> expectedLMT Then recoverableErrorReportingContext.RecordRecoverableErrorForChange(Nothing) Return Nothing End If ' Return Return New SimpleFileDataRetriever(localRelativePath, Nothing, localPath, File.GetAttributes(localPath)) End Function
InsertItem 是用來從遠端存放區 (檔案同步處理提供者所服務的存放區) 將資料插入本機存放區中。這個方法會將它所接收的項目資料轉換成 IFileDataRetriever 物件。此外,InsertItem 方法也會轉換資料。
public override void InsertItem(object itemData, IEnumerable<SyncId> changeUnitsToCreate, RecoverableErrorReportingContext recoverableErrorReportingContext, out ItemFieldDictionary keyAndUpdatedVersion, out bool commitKnowledgeAfterThisItem) { // Figure out where to create it IFileDataRetriever fileData = (IFileDataRetriever)itemData; string localPath = Path.Combine(this.rootFolder, Path.Combine(fileData.RelativeDirectoryPath, fileData.FileData.Name)); // Check if it is already there --- name collision if (File.Exists(localPath)) { recoverableErrorReportingContext.RecordConstraintError( ConstructDictionary(Path.Combine(fileData.RelativeDirectoryPath, fileData.FileData.Name))); keyAndUpdatedVersion = null; commitKnowledgeAfterThisItem = false; return; } // Create it File.Copy(fileData.AbsoluteSourceFilePath, localPath); // Return particulars to Simple Provider framework keyAndUpdatedVersion = ConstructDictionary( Path.Combine(fileData.RelativeDirectoryPath, fileData.FileData.Name), File.GetLastWriteTimeUtc(localPath).Ticks); commitKnowledgeAfterThisItem = false; }
Public Overrides Sub InsertItem(ByVal itemData As Object, ByVal changeUnitsToCreate As IEnumerable(Of SyncId), ByVal recoverableErrorReportingContext As RecoverableErrorReportingContext, ByRef keyAndUpdatedVersion As ItemFieldDictionary, ByRef commitKnowledgeAfterThisItem As Boolean) ' Figure out where to create it Dim fileData As IFileDataRetriever = DirectCast(itemData, IFileDataRetriever) Dim localPath As String = Path.Combine(Me.rootFolder, Path.Combine(fileData.RelativeDirectoryPath, fileData.FileData.Name)) ' Check if it is already there --- name collision If File.Exists(localPath) Then recoverableErrorReportingContext.RecordConstraintError(ConstructDictionary(Path.Combine(fileData.RelativeDirectoryPath, fileData.FileData.Name))) keyAndUpdatedVersion = Nothing commitKnowledgeAfterThisItem = False Exit Sub End If ' Create it File.Copy(fileData.AbsoluteSourceFilePath, localPath) ' Return particulars to Simple Provider framework keyAndUpdatedVersion = ConstructDictionary(Path.Combine(fileData.RelativeDirectoryPath, fileData.FileData.Name), File.GetLastWriteTimeUtc(localPath).Ticks) commitKnowledgeAfterThisItem = False End Sub
下列程式碼範例會建立 SimpleFileDataRetriever
類別,然後此類別會使用 AbsoluteSourceFilePath
和 RelativeDirectoryPath
來識別檔案位置,並且使用 FileData
和 FileStream
來傳輸實際資料。
class SimpleFileDataRetriever : IFileDataRetriever, IDisposable
{
private string _relativeLocalFilePath;
private Stream _sourceStream;
private string _absoluteSourceFilePath;
private FileAttributes _attributes;
public SimpleFileDataRetriever(string relativeLocalFilePath, Stream sourceStream, string absoluteSourceFilePath, FileAttributes attributes)
{
this._relativeLocalFilePath = relativeLocalFilePath;
this._sourceStream = sourceStream;
this._attributes = attributes;
this._absoluteSourceFilePath = absoluteSourceFilePath;
}
#region IFileDataRetriever Members
// If the local store has no concept of absolute file path then return a NotImplementedException here.
// The FSP will instead use the stream for file copying.
// If implemented, return absolute local path including file name.
public string AbsoluteSourceFilePath
{
get
{
return this._absoluteSourceFilePath;
}
}
public FileData FileData
{
get
{
FileInfo fi = new FileInfo(_absoluteSourceFilePath);
//For the relative path on FileData, provide relative path including file name
return new FileData(
_relativeLocalFilePath,
_attributes,
fi.CreationTimeUtc,
fi.LastAccessTimeUtc,
fi.LastWriteTimeUtc,
fi.Length);
}
}
public System.IO.Stream FileStream
{
get
{
if (this._sourceStream == null)
{
this._sourceStream = new FileStream(this._absoluteSourceFilePath, FileMode.Open);
}
return _sourceStream;
}
}
// Must return the relative path without the filename
public string RelativeDirectoryPath
{
get
{
return Path.GetDirectoryName(_relativeLocalFilePath);
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
if (this._sourceStream != null)
{
this._sourceStream.Close();
}
}
#endregion
}
Class SimpleFileDataRetriever
Implements IFileDataRetriever
' Implements IDisposable
Private _relativeLocalFilePath As String
Private _sourceStream As Stream
Private _absoluteSourceFilePath As String
Private _attributes As FileAttributes
Public Sub New(ByVal relativeLocalFilePath As String, ByVal sourceStream As Stream, ByVal absoluteSourceFilePath As String, ByVal attributes As FileAttributes)
Me._relativeLocalFilePath = relativeLocalFilePath
Me._sourceStream = sourceStream
Me._attributes = attributes
Me._absoluteSourceFilePath = absoluteSourceFilePath
End Sub
#Region "IFileDataRetriever Members"
' If the local store has no concept of absolute file path then return a NotImplementedException here.
' The FSP will instead use the stream for file copying.
' If implemented, return absolute local path including file name.
Public ReadOnly Property AbsoluteSourceFilePath() As String Implements IFileDataRetriever.AbsoluteSourceFilePath
Get
Return Me._absoluteSourceFilePath
End Get
End Property
Public ReadOnly Property FileData() As FileData Implements IFileDataRetriever.FileData
Get
Dim fi As New FileInfo(_absoluteSourceFilePath)
'For the relative path on FileData, provide relative path including file name
Return New FileData(_relativeLocalFilePath, _attributes, fi.CreationTimeUtc, fi.LastAccessTimeUtc, fi.LastWriteTimeUtc, fi.Length)
End Get
End Property
Public ReadOnly Property FileStream() As System.IO.Stream Implements IFileDataRetriever.FileStream
Get
If Me._sourceStream Is Nothing Then
Me._sourceStream = New FileStream(Me._absoluteSourceFilePath, FileMode.Open)
End If
Return _sourceStream
End Get
End Property
' Must return the relative path without the filename
Public ReadOnly Property RelativeDirectoryPath() As String Implements IFileDataRetriever.RelativeDirectoryPath
Get
Return Path.GetDirectoryName(_relativeLocalFilePath)
End Get
End Property
#End Region
#Region "IDisposable Members"
Public Sub Dispose()
If Me._sourceStream IsNot Nothing Then
Me._sourceStream.Close()
End If
End Sub
#End Region
End Class
下列程式碼範例會同步處理這兩個提供者。此同步處理程序就如同您同步處理兩個檔案同步處理提供者或兩個簡單提供者:實作 IFileDataRetriever 介面並使用適當的識別碼格式可確保資料會以正確的方式傳輸。
static void DoBidirectionalSync(string pathA, Guid replicaA, string pathB, Guid replicaB)
{
SyncOperationStatistics stats;
MySimpleFileProvider providerA = new MySimpleFileProvider(replicaA, pathA);
FileSyncProvider providerB = new FileSyncProvider(replicaB, pathB);
//Set the custom provider's conflict resolution policy to custom in order to show
//how to perform complex resolution actions.
providerA.Configuration.ConflictResolutionPolicy = ConflictResolutionPolicy.ApplicationDefined;
//Register callbacks so that we can handle conflicts if they are detected, and other events.
RegisterCallbacks(providerA);
RegisterCallbacks(providerB);
//Synchronize the two providers that are specified.
Console.WriteLine("Sync {0} and {1}...", pathA, pathB);
SyncOrchestrator agent = new SyncOrchestrator();
//To avoid writing conflict resolution logic in your matching provider it is good practice to always sync from custom provider
//to FSP provider first. That way the FSP will handle all the conflicts itself. Here we do the opposite to show our custom
//constraint conflict resolution.
agent.Direction = SyncDirectionOrder.UploadAndDownload;
agent.LocalProvider = providerB;
agent.RemoteProvider = providerA;
stats = agent.Synchronize();
//Display the statistics from the SyncOperationStatistics object that is returned
//by Synchronize().
Console.WriteLine("Download Applied:\t {0}", stats.DownloadChangesApplied);
Console.WriteLine("Download Failed:\t {0}", stats.DownloadChangesFailed);
Console.WriteLine("Download Total:\t\t {0}", stats.DownloadChangesTotal);
Console.WriteLine("Upload Total:\t\t {0}", stats.UploadChangesApplied);
Console.WriteLine("Upload Total:\t\t {0}", stats.UploadChangesFailed);
Console.WriteLine("Upload Total:\t\t {0}", stats.UploadChangesTotal);
}
Private Shared Sub DoBidirectionalSync(ByVal pathA As String, ByVal replicaA As Guid, ByVal pathB As String, ByVal replicaB As Guid)
Dim stats As SyncOperationStatistics
Dim providerA As New MySimpleFileProvider(replicaA, pathA)
Dim providerB As New FileSyncProvider(replicaB, pathB)
'Set the custom provider's conflict resolution policy to custom in order to show
'how to perform complex resolution actions.
providerA.Configuration.ConflictResolutionPolicy = ConflictResolutionPolicy.ApplicationDefined
'Register callbacks so that we can handle conflicts if they are detected, and other events.
RegisterCallbacks(providerA)
RegisterCallbacks(providerB)
'Synchronize the two providers that are specified.
Console.WriteLine("Sync {0} and {1}...", pathA, pathB)
Dim agent As New SyncOrchestrator()
'To avoid writing conflict resolution logic in your matching provider it is good practice to always sync from custom provider
'to FSP provider first. That way the FSP will handle all the conflicts itself. Here we do the opposite to show our custom
'constraint conflict resolution.
agent.Direction = SyncDirectionOrder.UploadAndDownload
agent.LocalProvider = providerB
agent.RemoteProvider = providerA
stats = agent.Synchronize()
'Display the statistics from the SyncOperationStatistics object that is returned
'by Synchronize().
Console.WriteLine("Download Applied:" & vbTab & " {0}", stats.DownloadChangesApplied)
Console.WriteLine("Download Failed:" & vbTab & " {0}", stats.DownloadChangesFailed)
Console.WriteLine("Download Total:" & vbTab & vbTab & " {0}", stats.DownloadChangesTotal)
Console.WriteLine("Upload Total:" & vbTab & vbTab & " {0}", stats.UploadChangesApplied)
Console.WriteLine("Upload Total:" & vbTab & vbTab & " {0}", stats.UploadChangesFailed)
Console.WriteLine("Upload Total:" & vbTab & vbTab & " {0}", stats.UploadChangesTotal)
End Sub