同步代理的核心职责是什么
一个本地文件同步代理不是简单地复制粘贴文件,它要持续观察本地目录变化、比对云端状态、按需上传下载,并处理冲突和断点续传。关键在于:它必须区分「变更事件」和「最终状态」——
FileSystemWatcher触发的
Changed事件非常频繁且可能重复,不能直接拿它当同步指令;真正该同步的是文件内容哈希或最后修改时间戳确认过的终态。 同步粒度应基于文件元数据(大小 +
ETag或
MD5)而非仅靠时间戳,Windows 文件系统时间精度低,NFS 或虚拟机场景下容易误判 必须维护本地状态缓存(如 SQLite 数据库存储每个文件的
local_hash、
remote_version、
is_deleted),否则每次启动都要全量扫描比对 所有网络操作必须带重试(指数退避)和取消令牌(
CancellationToken),避免卡死整个代理进程
如何用 FileSystemWatcher 安全捕获真实变更
FileSystemWatcher是入口,但默认配置极易漏事件或触发多次。它本身不保证事件顺序,也不合并连续写入(例如 Word 保存会触发 3–4 次
Changed)。 设置
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,禁用
FileName和
DirectoryName,减少重命名/移动带来的干扰 必须启用
IncludeSubdirectories = true,但为避免递归监听导致句柄耗尽,建议只监听用户指定的根路径,子目录由程序逻辑遍历发现 对每个
Changed事件,立刻启动一个带延时的去抖任务(例如
Task.Delay(1000, token)),等静默期结束后再读取文件大小和哈希——这是避免“写一半就同步”的唯一可靠方式 删除事件(
Deleted)需单独处理并记录到本地状态库,不能依赖后续扫描推断
上传/下载如何避免阻塞和重复
同步代理常因大文件上传卡住 UI 或拖慢扫描。所有 I/O 必须异步且可中断,同时防止同一文件被多次排队。
使用HttpClient.PutAsync()或分块
POST上传,配合
IProgress<long></long>更新进度,上传前先校验本地哈希是否已存在于云端(查
/api/v1/file/info?hash=xxx) 下载使用
Range请求支持断点续传,把临时文件写到
.sync_tmp_abc123,完整校验哈希后再原子重命名为目标名 维护一个内存中
ConcurrentDictionary<string task></string>(key 为文件相对路径),提交新任务前先
TryRemove旧任务并
await其完成,防止并发上传同一文件
冲突检测与用户干预点在哪
自动覆盖不是好策略。Dropbox 式行为是:当本地和云端都修改过同一文件,保留两者,生成类似
report.docx (Your Conflicted Copy 2024-05-22).docx的副本。 冲突判定依据是:本地有修改(
local_hash != remote_hash)且云端也有更新(
remote_version > local_version) 不要尝试自动合并二进制文件,文本文件也仅在明确标记为
text/plain且启用 diff 工具时才走合并逻辑 把冲突文件路径写入一个
conflicts.json清单,供 GUI 层弹窗提示,或 CLI 输出警告行 —— 这个清单本身也要同步到云端,让多端知道“此处有未决冲突”
本地状态库结构稍复杂,但少它一天都跑不稳;哈希计算别图省事用
File.ReadAllBytes(),大文件必须流式计算;还有,别忘了 Windows 上长路径(>260 字符)需要在
app.manifest里启用
longPathAware。
