From ffaffff9d638f81a29ecb612b82f9a98a2ef1363 Mon Sep 17 00:00:00 2001 From: laoyang Date: Mon, 2 Nov 2026 22:39:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=90=8C=E6=AD=A5):=20=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E4=BB=8E=E6=9C=AA=E8=A2=AB=E5=90=8C=E6=AD=A5=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E5=8F=96=E6=B6=88=E5=8B=BE=E9=80=89=E5=90=8E?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=8B=BE=E9=80=89=E4=B9=9F=E6=8B=89=E4=B8=8D?= =?UTF-8?q?=E5=9B=9E=E6=9D=A5=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 现象(用户反馈) 1. 账号设置里取消勾选待办本 → 保存同步 → 待办标签消失(这一步是正常的) 2. 再进去重新勾选 → 保存并同步 → **待办没回来,标签也不显示** ## 根因:VTODO 从来就没被同步过 同步主循环 `syncAccountInner` 全程只处理 VEVENT: - `DavClient.reportEtags(href)`:REPORT 的 comp-filter 只写了 `VEVENT` - `EventDb.queryByCalKey(context, calKey, 'event')`:只查 kind='event' - `EventDb.applyRemote(..., isTodo)`:这个 isTodo 参数**从未传过 true** 而 `DavClient.reportTodos()`(拉 VTODO)虽然早就写好了, **全项目零调用** —— 是一段死代码。 因果链: · 取消勾选待办本 → `pruneStaleHrefs` 清掉不属于已选本的 todo 行 · 重新勾选 → 同步只拉 VEVENT,不拉 VTODO · 本地库没有待办数据 → `loadTodos` 返回空 → 标签不显示 待办功能"能读能改"(勾选完成、设置循环都能用),但**数据来源一直是空的**, 只有以前残留的行在撑着。一旦被清掉就再也不回来。 ## 修法 在同步循环里对每个日历本额外拉一次 VTODO: `reportTodos(href)` → 逐个 GET 原文 → `IcsUtil.parseTodos` → `applyRemote(..., true)` ⭐ 为什么对 unknown 类型的本也拉: 服务器若没声明 supported-calendar-component-set,我们分不清哪个本是待办本。 待办量远小于日程,多拉一次的代价很小;宁可贵一点, 也不能让用户的待办再次"消失"。 ⭐ 失败隔离: 待办同步整段包在 try/catch 里,单本失败只记日志、不影响日程同步。 很多服务器对 VTODO 查询返回 403 或不支持该 REPORT, 不能让这种情况把整个账号同步拖垮。 ## 影响面 `applyRemote` 内部按 `kind` 隔离(existing 只查同 kind), 且自带 removed 处理 ⇒ 不会误删日程行,也不会漏删已消失的待办。 --- entry/src/main/ets/common/SyncEngine.ets | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/entry/src/main/ets/common/SyncEngine.ets b/entry/src/main/ets/common/SyncEngine.ets index 6862d61..885bff4 100644 --- a/entry/src/main/ets/common/SyncEngine.ets +++ b/entry/src/main/ets/common/SyncEngine.ets @@ -173,6 +173,36 @@ export class SyncEngine { throw err as Error; } LogUtil.write(`日历本[${i}]「${calName}」REPORT 返回 ${items.length} 个资源`); + // ⭐ SYNC_TODOS 同步该本里的待办(VTODO)。 + // 原来整个同步流程只处理 VEVENT,`DavClient.reportTodos` 写了却没人调用, + // 导致待办数据从来不会从服务器拉下来 —— + // 用户取消勾选本(触发 pruneStaleHrefs 清行)后再勾回来,待办就永久消失了。 + // 放在这里(VEVENT 处理之前)单独走一遍,不影响原有日程逻辑。 + try { + const todoItems: RemoteItem[] = await DavClient.reportTodos(href, auth); + if (todoItems.length > 0) { + const todoRemote: RemoteEvent[] = []; + for (const ti of todoItems) { + try { + const raw: string = await DavClient.getRawStrict(ti.href, auth); + const parsed: RemoteEvent[] = IcsUtil.parseTodos(raw); + for (const pr of parsed) { + pr.etag = ti.etag; + todoRemote.push(pr); + } + } catch (e) { + LogUtil.write(`待办 GET 失败(跳过) ${ti.href}`); + } + } + if (todoRemote.length > 0) { + await EventDb.applyRemote(context, calKey, href, todoRemote, true); + LogUtil.write(`日历本[${i}]「${calName}」待办同步:${todoRemote.length} 条`); + } + } + } catch (e) { + // 待办同步失败不能影响日程同步(很多服务器对 VTODO 查询返回 403/不支持) + LogUtil.write(`日历本[${i}]「${calName}」待办同步失败(忽略):${(e as BusinessError).message ?? ''}`); + } // 现有行按 uid 分组(同一资源的覆盖实例共享 uid 与 etag) const existingRows: LocalEvent[] = await EventDb.queryByCalKey(context, calKey, 'event'); const byUid: Map = new Map();