☰
MooTool Compose 端自动 Git Pull 后的 Vault 树刷新:DIFF-408 的 tick 通知机制深度解析
2026/10/15 2:44:48 网站建设 项目流程
  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

导读

本篇文章聚焦 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 > 0VaultGitAutoPull.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

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载
上一篇:AI-For-Beginners 人体分割实验室:用神经网络模型从视频画面中抠出人体轮廓
下一篇:ModOrganizer2游戏报错怎么办?快速解决MO2运行失败的实用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询