Synchronisieren von Dateisynchronisierungsanbietern mit anderen Anbietern
In diesem Thema wird erläutert, wie Sie eine verwaltete Anwendung erstellen, mit der ein Dateisynchronisierungsanbieter mit einem anderen Sync Framework-Anbieter synchronisiert werden kann. In diesem Fall handelt es sich bei dem anderen Anbieter um einen einfachen Anbieter. Es könnte jedoch auch ein benutzerdefinierter Standardanbieter verwendet werden. Weitere Informationen zu einfachen Anbietern finden Sie unter Implementieren eines benutzerdefinierten einfachen Anbieters.
Es bestehen zwei Hauptanforderungen für die Anwendung:
Die Anwendung muss IFileDataRetriever als Datenübertragungs-Schnittstelle zwischen den beiden Anbietern implementieren.
Der einfache Anbieter muss das gleiche Format für IDs wie der Dateisynchronisierungsanbieter verwenden: eine 16-Byte-GUID für Replikat-IDs, eine 16-Byte-GUID mit einem 8-Byte-Präfix für Element-IDs und eine 4-Byte--Ganzzahl für Änderungseinheit-IDs.
Beispiele für verwalteten Code
Die Beispiele in diesem Abschnitt enthalten Teile des Codes, der relevant ist, um einen Dateisynchronisierungsanbieter und einen einfachen Anbieter zu synchronisieren, mit Schwerpunkt auf den Anforderungen bezüglich ID und Übertragungsschnittstelle. In der "Sync 101 - Synchronizing a File Synchronization Provider with a Simple Provider"
-Anwendung, die in der Codegalerie verfügbar ist, finden Sie diesen Code im Kontext einer vollständigen Anwendung.
Im folgenden Codebeispiel wird verdeutlicht, wie die Definition der IdFormats-Eigenschaft für den einfachen Anbieter die zuvor genannten Anforderungen erfüllt.
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
Die folgenden Codebeispiele enthalten zwei der Methoden, die für einfache Anbieter erforderlich sind: LoadChangeData und InsertItem. Der Großteil des Codes in diesen Methoden bezieht sich auf die Implementierung eines einfachen Anbieters. Es sind jedoch zwei Bereiche zu beachten:
LoadChangeData wird verwendet, um Daten zu laden, die vom lokalen Speicher aufgelistet wurden (der Speicher, der vom einfachen Anbieter bedient wird). Diese Methode gibt Daten als
SimpleFileDataRetriever
-Objekt zurück. Dabei handelt es sich um die Implementierung von IFileDataRetriever für das Beispiel.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 wird verwendet, um Daten vom Remotespeicher (der Speicher, der vom Datensynchronisierungsanbieter bedient wird) in den lokalen Speicher einzufügen. Diese Methode wandelt die empfangenen Elementdaten in ein IFileDataRetriever-Objekt um. Von der InsertItem-Methode werden die Daten ebenfalls umgewandelt.
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
Im folgenden Codebeispiel wird die SimpleFileDataRetriever
-Klasse erstellt, die AbsoluteSourceFilePath
und RelativeDirectoryPath
zum Identifizieren von Dateispeicherorten sowie FileData
und FileStream
zum Übertragen der eigentlichen Daten verwendet.
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
Im folgenden Codebeispiel werden die beiden Anbieter synchronisiert. Der Synchronisierungsvorgang ist der gleiche wie beim Synchronisieren von zwei Dateisynchronisierungsanbietern oder zwei einfachen Anbietern: durch die Implementierung der IFileDataRetriever-Schnittstelle und die Verwendung der entsprechenden ID-Formate wird sichergestellt, dass Daten in korrekter Weise übertragen werden.
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
Siehe auch
Konzepte
Implementieren eines benutzerdefinierten einfachen Anbieters
Integrieren von Daten von verschiedenen Anbietern
Synchronisieren von Dateien