将文件同步提供程序与其他提供程序进行同步

本主题说明如何创建一个托管应用程序,通过它可以将文件同步提供程序与其他 Sync Framework 提供程序进行同步。在此情况下,该其他提供程序为简单提供程序,但您也可以使用标准自定义提供程序。有关简单提供程序的更多信息,请参见实现简单自定义提供程序

该应用程序有两个主要要求:

  • 此应用程序必须实现 IFileDataRetriever 作为两个提供程序之间的数据传输接口。

  • 简单提供程序必须与文件同步提供程序使用相同的 ID 格式:对于副本 ID,使用 16 字节的 GUID;对于项 ID,使用具有 8 字节前缀的 16 字节 GUID;对于变更单位 ID,使用 4 字节整数。

托管代码示例

本节中的示例显示与将文件同步提供程序和简单提供程序进行同步相关的一些代码,并重点介绍 ID 和传输接口要求。若要在完整应用程序的上下文中查看此代码,请参见 "Sync 101 - Synchronizing a File Synchronization Provider with a Simple Provider" 应用程序(可从 Code Gallery(代码库)获得)。

下面的代码示例说明简单提供程序的 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

下面的代码示例显示简单提供程序所需的两个方法:LoadChangeDataInsertItem。这两个方法中的大多数代码专用于实现简单提供程序,但需要注意两个方面:

  • 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 类,该类使用 AbsoluteSourceFilePathRelativeDirectoryPath 以标识文件位置,并使用 FileDataFileStream 来传输实际数据。

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 接口并使用适当的 ID 格式,以确保以正确的方式传输数据。

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

请参阅

概念

实现简单自定义提供程序
集成来自不同提供程序的数据
同步文件