- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
导读
本篇文章聚焦 MooToolnext-compose桌面实现中一个关键的状态同步问题:Vault(JSON 文档库与随手记文档库)在定时自动 Git pull 成功后,如何让 Vault 树与干净编辑器立刻感知磁盘变化并完成重载。DIFF-408 通过引入「Vault 树刷新 tick」通知机制,把自动 pull 与手动 Git 面板、刷新按钮统一到同一条刷新信号链路上,补齐了 Compose 端此前依赖VaultRevisionMonitor但缺少显式对齐刷新信号的空档。读完本文,你将掌握该机制的调用链、源码实现、判定规则与测试验证方式,可直接在 Compose Desktop 工程中复现或扩展同类模式。
背景:自动 pull 与 UI 刷新之间的信号缺口
MooTool 的 Electron 实现中,pullJsonVault/pullQuickNoteVault在 pull成功或进入 merge/冲突态后,会通过broadcast('*-vault:changed')事件驱动 Vault 树加载与干净编辑器重载;Compose(next-compose)端则依赖VaultRevisionMonitor轮询来发现外部写入。然而,Compose 的自动 pull完成后并没有显式的刷新信号 —— 树中看不到远程新增/删除的文件,编辑器也得不到重载提示。这与手动 Git 面板onVaultRefresh/persistFilter的行为不一致,构成了一个明确的交互缺口。
DIFF-408 的目标正是补上这个缺口:让自动 pull 成功或进入 merge/冲突态时,主动触发与手动刷新完全一致的「重抓 snapshot + persistFilter」链路。
核心行为设计
DIFF-408 定义了三层协作关系:
- 判定层:
VaultGitAutoPull.shouldNotifyVaultAfterPull负责回答「本次 pull 结果是否需要通知 UI」,判定条件为 pull 成功(pullResult.success)或 pull 后进入merging/conflicts > 0状态。 - 通知层:
AppContainer.notifyJsonVaultTreeChanged()/notifyQuickNoteVaultTreeChanged()分别递增 JSON 与随手记的 tick 计数,模拟 Electron 的broadcast('json-vault:changed')/broadcast('quick-note-vault:changed')。该 tick 与手动 Git 刷新(DIFF-415)共用。 - 消费层:JSON / 随手记屏的
LaunchedEffect观察到 tick 变化后,在 IO 线程重抓文档库snapshot,随后调用persistFilter()(含干净时重载当前文件、递增 Git 角标 rev 等副作用)。
这三层形成了「判定 → 通知 → 重载」的完整闭环,保证无论刷新信号来自自动 pull、手动 Git 面板还是刷新按钮,UI 都走同一条刷新路径,避免多套逻辑分叉。
源码级实现拆解
1. 判定函数:shouldNotifyVaultAfterPull
判定逻辑实现在 VaultGitAutoPull.kt:
/** Electron `pull*Vault`:`broadcast('*-vault:changed')` after success or merge/conflict state. */ fun shouldNotifyVaultAfterPull(pullResult: GitActionResult, afterStatus: GitStatus): Boolean = pullResult.success || afterStatus.merging || afterStatus.conflicts > 0该函数接受两个参数:
pullResult:GitActionResult(定义于 GitEngine.kt),仅含success与message两个字段;afterStatus:pull之后重新执行GitEngine.status(root)得到的GitStatus(定义于 GitEngine.kt),携带merging、conflicts、changes、remote等字段。
注意其语义对齐了 Electron:即使 pull 命令本身失败,只要事后工作区进入了 merge 或冲突态(例如远端带来冲突),也要通知 UI—— 因为冲突态下需要重载 snapshot 以展示冲突文件;反之,若 pull 失败且工作区仍干净,则不通知,避免无谓重载。
2. 通知层:tick 递增
AppContainer.kt 中两个通知函数分别递增各自的 tick 状态:
/** Electron `broadcast('json-vault:changed')` → Vault 面板 `load()` + 干净编辑器 `reloadSelectedFromDisk`。 */ fun notifyJsonVaultTreeChanged() { _jsonVaultAutoPullTick.value += 1 } /** Electron `broadcast('quick-note-vault:changed')` 同上。 */ fun notifyQuickNoteVaultTreeChanged() { _quickNoteVaultAutoPullTick.value += 1 }tick 是单调递增的 Long 状态量,UI 通过collectAsState()观察。除了自动 pull,以下场景也复用同一 tick(详见 DIFF-415 与 DIFF-416):
- JSON/随手记 Git 面板的
onVaultRefresh回调; - Vault 面板的「刷新」按钮与 JSON 更多菜单中的「刷新」入口(见 JsonScreen.kt);
VaultRevisionMonitor磁盘监视回调(DIFF-416);reloadToolSessionsFromStore()(备份恢复/跨产品导入后)同时调用两个通知函数(AppContainer.kt)。
3. 自动 pull 的完整调用链
自动 pull 由VaultGitPullScheduler定时驱动,其实现位于 VaultGitCheckpointScheduler.kt。该调度器支持enabled、hasUnsavedEditorChanges、intervalMilliseconds、pull等 lambda 注入,并提供resetIntervalClock()在设置变更时从下一周期重新计时。
在 AppContainer.kt 中,JSON 与随手记各注册了一个调度器实例:
private val notePullScheduler = VaultGitPullScheduler( enabled = { _settings.value.vault.autoPullMinutes > 0 }, // ... intervalMilliseconds = { _settings.value.vault.autoPullMinutes.coerceIn(0, 1440) * 60_000L }, pull = { runScheduledVaultPull(noteVault().root(), forJson = false) } ) private val jsonPullScheduler = VaultGitPullScheduler( enabled = { _settings.value.vault.autoPullMinutes > 0 }, // ... intervalMilliseconds = { _settings.value.vault.autoPullMinutes.coerceIn(0, 1440) * 60_000L }, pull = { runScheduledVaultPull(jsonVault.root(), forJson = true) } )关键参数:
autoPullMinutes:自动 pull 间隔(分钟),0表示关闭;上限被coerceIn(0, 1440)限制为 24 小时;- 编辑器脏(存在未保存改动)时由调度器的
hasUnsavedEditorChanges单独拦截,不执行 pull; - 当
autoPullMinutes设置变化时,两个调度器调用resetIntervalClock()重设计时起点(AppContainer.kt)。
实际的拉取与通知逻辑集中在 runScheduledVaultPull:
private fun runScheduledVaultPull(root: Path, forJson: Boolean): GitActionResult { val status = GitEngine.status(root) if (!VaultGitAutoPull.mayPullCleanWorkingTree(status)) { return GitActionResult(true, "skipped") } val result = GitEngine.pull(root, token = _settings.value.vault.gitToken) val after = GitEngine.status(root) if (VaultGitAutoPull.shouldNotifyVaultAfterPull(result, after)) { if (forJson) notifyJsonVaultTreeChanged() else notifyQuickNoteVaultTreeChanged() } return result }这里完整呈现了 DIFF-408 与前置 DIFF-407 的衔接:pull 之前先用mayPullCleanWorkingTree校验工作区前置条件(仓库可用、已配置 remote、无 merge、无冲突、changes为空),满足才真正执行GitEngine.pull;pull 之后再依据shouldNotifyVaultAfterPull决定是否发通知。从源码结构看,这两步「事前校验 + 事后通知判定」构成一个可独立单测的纯函数对。
4. 消费层:LaunchedEffect重抓 snapshot
JSON 屏的消费逻辑位于 JsonScreen.kt:
val jsonVaultAutoPullTick by container.jsonVaultAutoPullTick.collectAsState() LaunchedEffect(jsonVaultAutoPullTick, settings.vault.hideGitignoredFiles) { if (jsonVaultAutoPullTick == 0L) return@LaunchedEffect snapshot = withContext(Dispatchers.IO) { container.jsonVault.snapshot(settings.vault.hideGitignoredFiles) } persistFilter() }随手记屏的对应逻辑几乎相同(QuickNoteScreen.kt)。设计要点:
- tick 初始为
0L,作为「从未发生过刷新」的哨兵值,LaunchedEffect首帧不会触发重载,避免启动时重复抓取; - snapshot 抓取在
Dispatchers.IO上执行,避免阻塞 UI 线程; persistFilter()(定义见 JsonScreen.kt)在干净条件下重载当前文件(jsonVaultReloadOpenFileIfClean),并递增filterRev与gitCountRev,驱动 Git 角标(rememberVaultGitChangeCount)重算;hideGitignoredFiles同时作为LaunchedEffect的 key,切换该设置时也会重抓 snapshot,复用同一刷新链路。
与 Electron 实现的逐点对齐
DIFF-408 的所有判定规则都能在 Electron 主进程代码 next/electron/main/index.ts 中找到对应实现:
async function pullJsonVault(): Promise<void> { if (jsonVaultEditorDirty) return const service = createVaultGitService() const status = await service.status() if (!status.repository || !status.remote || status.merging || status.conflicts > 0 || status.changes.length > 0) return const result = await service.action({ action: 'pull' }) const updatedStatus = await service.status() if (result.success || updatedStatus.merging || updatedStatus.conflicts > 0) broadcast('json-vault:changed', '') }pullQuickNoteVault结构相同(index.ts),仅广播事件名不同。对应关系如下表:
| Electron 行为 | Compose 对应实现 |
|---|---|
| 编辑器脏直接返回 | VaultGitPullScheduler.hasUnsavedEditorChanges拦截 |
status.repository/remote/merging/conflicts/changes前置校验 | VaultGitAutoPull.mayPullCleanWorkingTree(DIFF-407) |
pull 后重查 status 并判result.success \|\| merging \|\| conflicts > 0 | VaultGitAutoPull.shouldNotifyVaultAfterPull |
broadcast('json-vault:changed')/broadcast('quick-note-vault:changed') | notifyJsonVaultTreeChanged()/notifyQuickNoteVaultTreeChanged()递增 tick |
定时器configureJsonVaultAutoPull/configureQuickNoteVaultAutoPull(autoPullMinutes * 60_000) | VaultGitPullScheduler+intervalMilliseconds |
定时配置方面,Electron 在 index.ts 中通过setInterval(..., settings.vault.autoPullMinutes * 60_000)实现,autoPullMinutes <= 0时不建定时器;Compose 的enabledlambda 与之语义一致。
测试与验收
DIFF-408 的验收覆盖 A03 / F01/F04 的 Vault Git 场景,主要落在两个测试类:
VaultGitAutoPullTest(VaultGitAutoPullTest.kt)验证纯函数判定逻辑:
allowsPullWhenRepositoryClean:干净仓库允许 pull;skipsWhenLocalChangesOrConflictOrMerge:本地有变更、存在冲突、正在 merge、未配置 remote 时均拒绝 pull;notifiesVaultAfterPullOnSuccessOrMergeState:pull 成功后通知、pull 失败但进入 merge 态时通知、pull 失败且工作区干净时不通知 —— 恰好覆盖shouldNotifyVaultAfterPull的真值表。
VaultTreeRefreshNotifyTest(VaultTreeRefreshNotifyTest.kt)验证 tick 通知行为:
notifyIncrementsVaultTreeRefreshTicks:初始 tick 为0L,调用notifyJsonVaultTreeChanged()/notifyQuickNoteVaultTreeChanged()后各自递增为1L;reloadToolSessionsFromStoreNotifiesVaultTrees:备份恢复等场景会同时递增两个 tick。
运行方式沿用 Compose 工程的标准离线测试命令:
./gradlew :composeApp:desktopTest --offline该工程还配套了 Vault Git 检查点调度器测试VaultGitPullSchedulerTest,用于覆盖定时器的使能与重计时行为。
小结与扩展阅读
DIFF-408 本质上是把「自动 pull 结束事件」翻译成一个可观察的 tick 状态,再复用手动刷新已有的 snapshot 重抓链路,从而以最小改动闭合了「pull 成功 → 树与编辑器同步」这一产品路径。它与其兄弟 DIFF 共同构成了 Compose Vault Git 刷新机制的完整拼图:
- DIFF-407:Vault 自动 pull 对齐 Electron 工作区前置条件:
mayPullCleanWorkingTree事前校验; - DIFF-415:手动 Git / 刷新 Vault 重载文档库 snapshot:手动路径并入同一 tick;
- DIFF-416:Vault 磁盘监视触发文档库树 snapshot 刷新:磁盘监视并入同一 tick。
对后续维护者而言,若要新增一种「Vault 可能已变化」的触发源,只需在对应时机调用notifyJsonVaultTreeChanged()/notifyQuickNoteVaultTreeChanged(),UI 侧的LaunchedEffect会自动完成 snapshot 重抓与过滤持久化,无需改动编辑器逻辑 —— 这正是该 tick 机制的核心价值。
- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
相关推荐
MooTool(Compose 版)Vault Git 丢弃确认与工作区刷新机制深度解析
MooTool(Compose 版)Vault Git 丢弃确认与工作区刷新机制深度解析 本篇技术指南围绕 MooTool 下一代 Compose 桌面端 DI
桌面应用开发工具MooTool Next Compose Vault Git 中止合并确认机制解析(DIFF-108)
MooTool Next Compose Vault Git 中止合并确认机制解析(DIFF 108) 本文以 MooTool 仓库中 next compose
桌面应用开发工具MooTool Next-Compose 备份恢复后的 Vault 树刷新机制:从 sessionGeneration 到 `*-vault:changed` 信号对齐
MooTool Next Compose 备份恢复后的 Vault 树刷新机制:从 sessionGeneration 到 vault:changed 信号对齐
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考