using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.Synchronization;
using System.IO;
using Microsoft.Synchronization.Files;
namespace SynchronizeFiles
{
public partial class FormSync : Form
{
public FolderBrowserDialog sourceFolderBrowserDialog = new FolderBrowserDialog();
public FolderBrowserDialog targetFolderBrowserDialog = new FolderBrowserDialog();
public FormSync()
{
InitializeComponent();
if (!string.IsNullOrEmpty(textBoxSource.Text))
{
LoadSourceFileList();
}
if (!string.IsNullOrEmpty(textBoxTarget.Text))
{
LoadTargetFileList();
}
}
private void buttonScoure_Click(object sender, EventArgs e)
{
sourceFolderBrowserDialog.ShowNewFolderButton = true;
DialogResult dialogResult = sourceFolderBrowserDialog.ShowDialog();
if (dialogResult == DialogResult.OK)
{
LoadSourceFileList();
}
}
private void buttonTarget_Click(object sender, EventArgs e)
{
targetFolderBrowserDialog.ShowNewFolderButton = true;
DialogResult dialogResult = targetFolderBrowserDialog.ShowDialog();
if (dialogResult == DialogResult.OK)
{
LoadTargetFileList();
}
}
public void LoadSourceFileList()
{
richTextBoxSource.Clear();
if (!string.IsNullOrEmpty(sourceFolderBrowserDialog.SelectedPath))
{
textBoxSource.Text = sourceFolderBrowserDialog.SelectedPath;
}
DirectoryInfo directoryInfo = new DirectoryInfo(textBoxSource.Text);
foreach (FileInfo fileInfo in directoryInfo.GetFiles())
{
richTextBoxSource.AppendText(fileInfo.Name);
}
}
public void LoadTargetFileList()
{
richTextBoxTarget.Clear();
if (!string.IsNullOrEmpty(targetFolderBrowserDialog.SelectedPath))
{
textBoxTarget.Text = targetFolderBrowserDialog.SelectedPath;
}
DirectoryInfo directoryInfo = new DirectoryInfo(textBoxTarget.Text);
foreach (FileInfo fileInfo in directoryInfo.GetFiles())
{
richTextBoxTarget.AppendText(fileInfo.Name);
}
}
private void buttonSync_Click(object sender, EventArgs e)
{
string replica1RootPath = textBoxSource.Text;
string replica2RootPath = textBoxTarget.Text;
string idFileName = "filesync.id";
Guid replica1Id = GetReplicaId(Path.Combine(replica1RootPath, idFileName));
Guid replica2Id = GetReplicaId(Path.Combine(replica2RootPath, idFileName));
try
{
// FileSyncOptions 提供配置文件同步提供程序的行为的选项。
// 可将包含这些选项的组合的值传递到 FileSyncProvider,以配置文件同步提供程序将对同步过程不同部分的处理方式。
//CompareFileStreams 如果设置了此值,则提供程序将基于整个文件流的内容计算每个文件的哈希值,并在变更检测期间使用此值对这些文件进行比较。此选项会占用大量资源并将使同步减速,但可提供更强大的变更检测。如果未设置此值,则比较修改次数、文件大小、文件名称和文件属性的算法将用于确定文件是否已变更。
//ExplicitDetectChanges 如果设置了此值,则仅当调用 DetectChanges 时提供程序才执行变更检测。如果不设置此值,则在首次调用提供程序的 GetChangeBatch 或 ProcessChangeBatch 方法时隐式执行变更检测。
//None 如果设置了此值,则提供程序将使用其默认配置选项。设置任何其他标志将覆盖此设置。这是默认设置。
//RecycleConflictLoserFiles 如果设置了此值,则提供程序将把冲突解决落选方文件移至回收站。如果未设置此值,则提供程序将把这些文件移至指定位置。或者,如果未指定位置,则将永久删除这些文件。
//RecycleDeletedFiles 如果设置了此值,则提供程序将把变更应用期间删除的文件移至回收站。如果未设置此值,则将永久删除这些文件。
//RecyclePreviousFileOnUpdates 如果设置了此值,则提供程序将把变更应用期间覆盖的文件移至回收站。如果未设置此值,则将就地覆盖文件并且会丢失旧文件中的所有数据。
FileSyncOptions options = FileSyncOptions.ExplicitDetectChanges |
FileSyncOptions.RecycleDeletedFiles | FileSyncOptions.RecyclePreviousFileOnUpdates | FileSyncOptions.RecycleConflictLoserFiles;
//FileSyncScopeFilter定义可用于包括或排除静态同步作用域中的文件或文件夹的筛选器。
//静态筛选器应用于变更检测,对同步作用域中的所有项都适用。对于要包括在同步中的项,它必须传递所有静态筛选器。例如,如果某个文件同时被显式排除和显式包括,则该文件将从作用域中排除。
//应该对同步社区中的所有提供程序都使用同一作用域筛选器。如果未执行此操作,则可能发生意外行为。
//传递给 FileSyncProvider 的筛选器用于在传入时根据作用域筛选器初始化提供程序对象。该筛选器的后续变更将不影响提供程序实例所使用的作用域筛选器。若要变更所使用的作用域筛选器,必须创建新的文件同步提供程序对象。
//在首次同步后变更副本作用域会产生意外的不利影响。例如,假设在首次同步后的某个时候将副本 A 的作用域变更为开始排除 *.txt。这将导致副本 A 将具有 .txt 扩展名的所有文件视为删除。实际上将不会从副本 A 中删除任何文件。但是,副本 A 中的元数据将包含 .txt 文件的逻辑删除,并且副本 A 将把删除变更发送给 .txt 文件的其他副本。如果在副本 B 中也排除 *.txt,则不起作用,因为副本 B 将从属于 .txt 文件的副本 A 中筛选变更。但是,如果副本 B 不变更其筛选器,则已删除的文件将流向它,被视为真正的变更请求,并从副本 B 中删除。
FileSyncScopeFilter filter = new FileSyncScopeFilter();
filter.FileNameExcludes.Add(idFileName); // Exclude the id file
// 从source到target路径同步
SyncFileSystemReplicasOneWay(replica1Id, replica2Id, replica1RootPath, replica2RootPath, filter, options);
// 从target到source路径同步
//SyncFileSystemReplicasOneWay(replica2Id, replica1Id,replica2RootPath, replica1RootPath, filter, options);
}
catch (Exception exception)
{
richTextBoxOutMessage.Text = "";
OutputMsg("File Sync Provider同步错误:" + exception.Message.ToString());
}
LoadTargetFileList();
}
public void OutputMsg(string context)
{
richTextBoxOutMessage.Text += "\n" + context;
}
public void DetectChangesOnFileSystemReplica(Guid replicaId, string replicaRootPath, FileSyncScopeFilter filter, FileSyncOptions options)
{
//是同步的同步提供程序,它可用于同步 NTFS、FAT 或 SMB 文件系统中的文件、文件夹和子文件夹。
//若要同步目录中的所有文件和子文件夹,请将副本 ID 和根目录传递到 FileSyncProvider(Guid,String),并将该提供程序传递到 SyncAgent 对象以处理同步会话。
//默认情况下,同步元数据存储在副本根目录下的 Metadata Storage Service 数据库文件中。若要自定义此文件的位置和名称,请使用 FileSyncProvider(Guid,String,FileSyncScopeFilter,FileSyncOptions,String,String,String,String) 进行指定。
//通过配置 FileSyncScopeFilter 并将它传递到提供程序的构造函数可控制哪些文件和文件夹包括在同步作用域内。该筛选器包含一些属性,这些属性可用于排除文件列表,排除文件夹列表以及基于文件和文件夹的属性排除这些文件和文件夹,还可用于显式包括文件列表。
//FileSyncOptions 是一组配置选项,可用于在同步期间控制提供程序的行为方式;例如,将已删除的文件移至回收站,或从文件系统中永久删除它们。
//会话期间,应用程序可利用多个事件来显示进度或动态跳过特定变更。
//同步开始前,通过将 PreviewMode 设置为 true 可将提供程序设置为预览模式。在预览模式下,提供程序将如同进行真正的同步会话一样执行所有操作,包括激发所有事件。但是,提供程序实际上没有将所有变更都应用于该目标副本。
//Note注意:不支持对同一文件存储区的并发同步操作。如果此前已经使用同一副本(即相同的目录路径值和元数据文件路径值)对另一个提供程序实例进行了初始化,但尚未释放,则构造函数将从元数据存储区引发 ReplicaMetadataInUseException。
FileSyncProvider provider = null;
try
{
provider = new FileSyncProvider(replicaId, replicaRootPath, filter, options);
provider.DetectChanges();
provider.ApplyingChange += new EventHandler<ApplyingChangeEventArgs>(provider_ApplyingChange);
}
finally
{
// Release resources
if (provider != null)
provider.Dispose();
}
}
void provider_ApplyingChange(object sender, ApplyingChangeEventArgs e)
{
throw new NotImplementedException();
}
public void SyncFileSystemReplicasOneWay(Guid sourceReplicaId, Guid destinationReplicaId,
string sourceReplicaRootPath, string destinationReplicaRootPath,
FileSyncScopeFilter filter, FileSyncOptions options)
{
FileSyncProvider sourceProvider = null;
FileSyncProvider destinationProvider = null;
try
{
sourceProvider = new FileSyncProvider(
sourceReplicaId, sourceReplicaRootPath, filter, options);
sourceProvider.DetectChanges();
destinationProvider = new FileSyncProvider(
destinationReplicaId, destinationReplicaRootPath, filter, options);
destinationProvider.DetectChanges();
destinationProvider.AppliedChange +=
new EventHandler<AppliedChangeEventArgs>(OnAppliedChange);
destinationProvider.SkippedChange +=
new EventHandler<SkippedChangeEventArgs>(OnSkippedChange);
//启动和控制同步会话。
//SyncOrchestrator 包含两个将参与同步的 SyncProvider 对象。它能够启动和控制同步会话,并向应用程序调度进度事件。
SyncOrchestrator agent = new SyncOrchestrator();
agent.LocalProvider = sourceProvider;
agent.RemoteProvider = destinationProvider;
agent.Direction = SyncDirectionOrder.Upload; // Sync source to destination
agent.SessionProgress += new EventHandler<SyncStagedProgressEventArgs>(agent_SessionProgress);
//SyncDirectionOrder指示同步的方向。 对于双向同步,还包括执行同步的顺序。
// Download 仅下载。
//DownloadAndUpload 先下载,再上载。
//Upload 仅上载。
//UploadAndDownload 先上载,再下载。
//备注:方向与同步提供程序的相对位置结合使用,可确定同步期间的变更流。 上载意味着变更源于本地提供程序,并应用到远程提供程序。 下载意味着变更源于远程提供程序,并应用到本地提供程序。
richTextBoxOutMessage.Text = "";
OutputMsg("Synchronizing changes to replica: " +
destinationProvider.RootDirectoryPath);
agent.Synchronize();
}
finally
{
// 释放资源
if (sourceProvider != null) sourceProvider.Dispose();
if (destinationProvider != null) destinationProvider.Dispose();
}
}
void agent_SessionProgress(object sender, SyncStagedProgressEventArgs e)
{
throw new NotImplementedException();
}
public void OnAppliedChange(object sender, AppliedChangeEventArgs args)
{
switch (args.ChangeType)
{
// ChangeType 枚举 表示可应用于目标副本的不同变更类型。
//Create 将创建一个文件或文件夹。
//Delete 将删除一个文件或文件夹。
//Rename 将重命名一个文件或文件夹。
//Update 将更新一个文件或文件夹。
//备注:ChangeType 枚举传递要应用的变更的类型。AppliedChange、ApplyingChange 和 SkippedChange 事件使用该枚举向事件处理程序提供变更信息。
case ChangeType.Create:
OutputMsg("-- 创建文件(夹): " + args.NewFilePath); //获取已变更的文件的新路径。该路径是相对于根目录的。
break;
case ChangeType.Delete:
OutputMsg("-- 删除文件(夹): " + args.OldFilePath); //获取已变更的文件的先前路径。该路径是相对于根目录的。
break;
case ChangeType.Update:
OutputMsg("-- 更新文件(夹): " + args.OldFilePath);
break;
case ChangeType.Rename:
OutputMsg("-- 重命名文件(夹): " + args.OldFilePath +
" as " + args.NewFilePath);
break;
}
}
public void OnSkippedChange(object sender, SkippedChangeEventArgs args)
{
OutputMsg("-- 因为错误,跳过变更类型:" + args.ChangeType.ToString().ToUpper()
+ " 对当前文件: " + (!string.IsNullOrEmpty(args.CurrentFilePath) ?
args.CurrentFilePath : args.NewFilePath));
if (args.Exception != null)
OutputMsg(" [" + args.Exception.Message + "]");
}
public static Guid GetReplicaId(string idFilePath)
{
Guid replicaId = Guid.Empty;
if (File.Exists(idFilePath))
{
using (StreamReader sr = File.OpenText(idFilePath))
{
string strGuid = sr.ReadLine();
if (!string.IsNullOrEmpty(strGuid))
replicaId = new Guid(strGuid);
}
}
if (replicaId == Guid.Empty)
{
using (FileStream idFile = File.Open(
idFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
using (StreamWriter sw = new StreamWriter(idFile))
{
replicaId = Guid.NewGuid();
sw.WriteLine(replicaId.ToString("D"));
}
}
}
return replicaId;
}
}
}