-#1434
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
改动了好多,只是大概的看了下代码逻辑,感觉没问题就合了,等全部弄完后,得手动测试一下 |
|
感谢。 |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
d574ba5
补了对应单测,覆盖条件删除 header/preflight、冲突转换、tombstone 条件写入、meta 失败后的 guarded cleanup、Limiter 透传 delete options。 9313d79
6032f6e 13804b9
ff61d4d避免 补了自愈逻辑:
这能避免“删除冲突/半提交后,下一次、下下次一直无法正常同步”的一类实际风险。普通非冲突路径仍然主要靠 script digest 快速跳过;只有 meta digest 变化时才额外读取 meta 判断 tombstone。 补了两条测试覆盖:
08e0844tombstone 已经被缓存、但远端
补了对应测试:
88033bd
f86d788
8db7c9b
7da6dbf
补了对应测试:
d59f7d4用户明确选择脚本备份时,如果某个 uuid 不存在,现在会直接失败并记录日志,不再静默跳过。这样不会影响正常存在脚本的导出,但能避免“备份成功但内容不完整且用户无感”。 |
概要
本 PR 修复云同步在多设备并发写入时可能出现的静默覆盖、状态污染和错误推进本地同步状态问题。
核心目标是:云同步写入脚本、元数据和
scriptcat-sync.json时,不再无条件基于过期远端状态覆盖;而是根据各 provider 能力使用 version / digest / rev / ETag 等信息建立写入前置检查或条件写入。若远端已被其他设备修改,当前设备会识别为冲突或失败,并停止继续更新scriptcat-sync.json与本地file_digest,避免把错误状态写回本地或云端。本 PR 同时补齐 provider 条件写入能力、删除幂等性、请求错误类型化、Google Drive 重名保护、同步失败通知、认证并发去重、选中脚本导出失败处理以及相关测试。
主要改动
1. 扩展 filesystem 通用接口
FileInfo新增version?: stringFileCreateOptions新增:expectedDigestexpectedVersioncreateOnlyFileCreateOptions.overwriteFileSystemError新增unsupportedfileConflictError()unsupportedConditionalWriteError()isUnsupportedError()conflict/unsupported标识写入冲突和 provider 不支持的场景2. 条件写入 header 生成逻辑复用
buildConditionalHeaders(opts?: FileCreateOptions)If-None-Match: *:用于createOnlyIf-Match:用于expectedVersion/expectedDigestIf-None-Match,继续使用 webdav client 的overwrite: false处理 create-only 语义buildConditionalHeaders()单元测试,覆盖:createOnly优先级高于 expected tokenexpectedVersion优先于expectedDigestexpectedDigest时生成If-Match3. 认证流程并发去重
AuthVerify()新增authTokenPromises4. Provider 写入前置条件与冲突处理
S3
create()透传完整FileCreateOptionslist()暴露:digest: 去除引号后的 ETagversion: provider 原始 ETagPUT写入支持:If-None-Match: *用于createOnlyIf-Match用于expectedVersion/expectedDigest409/412统一转换为fileConflictError("s3", ...)list()不再为每个对象额外发送HEAD读取 metadata createtime,避免目录列表产生额外请求;创建时间使用对象LastModifiedWebDAV
create()透传FileCreateOptionslist()将 ETag 暴露为versionIf-Match条件更新createOnly创建保护overwrite: false实现409/412统一转换为fileConflictError("webdav", ...)Dropbox
create()透传FileCreateOptionslist()将 Dropboxrev暴露为versionoverwritemode,不再先做 metadataexists()preflightexpectedVersion时使用 DropboxupdatemodecreateOnly时使用addmodeexpectedDigest但没有expectedVersion时通过unsupportedConditionalWriteError()明确报unsupported_conditional_writeincorrect_offset以及已类型化的FileSystemError(conflict: true)会统一转换 / 保持为 Dropbox 冲突错误Google Drive
create()透传FileCreateOptionslist()请求version字段,并暴露 opaque version token:fileId:versiondelete()遇到 typed not-found 时保持幂等成功,并清理 stale path cachefindFileInDirectory()改为:findFilesInDirectory()fileConflictError("googledrive", ...)expectedVersion解析出fileId和versionversion412 versionMismatchgenerateIdsIf-None-Match: *createOnly会在创建后再次检查同名文件OneDrive
create()透传FileCreateOptionslist()将 eTag 暴露为:digestversiondelete()对 typed not-found 保持幂等成功If-None-Match: *用于createOnlyIf-Match用于expectedVersion/expectedDigestfailreplaceBaidu
expectedVersion明确标记为不支持,并通过unsupportedConditionalWriteError()抛出unsupported_conditional_writeexpectedDigest通过写入前list()做 best-effort preflightcreateOnly:rtype=0,要求百度服务端拒绝覆盖fileConflictError("baidu", ...)expectedDigest通过 preflight 后仍使用默认覆盖语义rtype=3delete()对文件不存在 errno 保持幂等成功5. 云同步写入正确性改进
getWriteOptions(modifiedDate, remoteFile)createOnlyversion:使用expectedVersionversion但有digest:使用expectedDigestpushScript()现在接收远端脚本 / meta 文件信息,并分别带写入前置条件:${uuid}.user.js${uuid}.meta.jsoncreateOnlyversion/digest作为写入前置条件scriptcat-sync.json写入也改为使用远端version/digest前置条件list()远端状态,再带前置条件写入6. 同步失败时避免污染本地状态
syncOnceInternal()会检查 push / pull / status sync 的 rejected taskscriptcat-sync.jsonfile_digestscriptcat-sync.json写入失败时会被捕获:pullScript()失败后不再静默吞掉异常,而是继续抛出,让上层停止状态推进7. digest cache 与 tombstone digest 处理
FileDigestMapupdateFileDigest()会先读取云端列表fs.list()结果中,会再重试一次 liststorage.setfile_digest或scriptcat-sync.json的成功状态8. 选中脚本备份导出失败处理
9. 同步失败通知与文案
notifySyncFailed(hasConflict: boolean, rejectedCount: number)notification.script_sync_failednotification.script_sync_failed_descnotification.script_sync_conflict_descde-DEen-USja-JPru-RUvi-VNzh-CNzh-TW10. Service Worker alarm 错误处理
cloudSyncalarm 调用链增加.catch()测试覆盖
本 PR 补充了各 provider、认证流程、filesystem utils、备份导出和同步流程的单元测试,覆盖:
version暴露buildConditionalHeaders()行为FileSystemError(conflict: true)unsupportedversion保留原始 ETag,digest保留去引号 ETagexpectedDigest成功时仍使用rtype=3pushScript()对新建文件使用createOnlypushScript()对已有文件传递远端expectedVersionscriptcat-sync.json使用 create-only 或 expectedVersion 条件写入scriptcat-sync.json写入失败时通知并跳过 digest 更新解决的问题
scriptcat-sync.json