DIFF-358 解析:JSON 转换结果/弹层写入编辑器前的 Vault 脏数据 Flush 机制)
桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载本文围绕 MooTool NextCompose 桌面端JSON 工具的转换结果弹层与 XML/Bean 转换弹层展开聚焦“结果写入编辑器前必须先保存当前未落盘的 Vault 片段”这一数据安全策略。读完本文你将理解 flush 的触发时机、失败处理语义、与历史恢复/导入路径的异同以及如何通过JsonVaultSession与VaultConflictEngine在源码层面验证这套机制。背景与问题setText直接覆盖编辑器带来的 Vault 数据丢失风险MooTool 的 JSON 工具桌面端基于 Compose for Desktop 实现内置了 Vault片段库能力编辑器当前内容可以与 Vault 中某个.json片段绑定currentFilesavedText未保存的编辑即“脏缓冲区”。当用户把某个转换结果如 JSON → XML通过结果弹层的「使用输出」按钮写回主编辑器时如果实现直接用setText覆盖那么当前已打开、但尚未保存的 Vault 片段就可能丢失编辑内容同时造成“磁盘上的片段内容”与“编辑器即将显示的内容”不一致后续的保存与冲突判定都会变得混乱。DIFF-357 已经为历史恢复路径补上了 flushDIFF-358 是这条安全链路的延续补齐了**检查器结果弹层jsonToXml/jsonToBean等「使用输出」**与XML/Bean 转换弹层这两条此前仍然直接setText的路径。设计文档原文给出的问题定位如下检查器结果弹层「使用输出」jsonToXml/jsonToBean等与XML/Bean 转换弹层成功写入主编辑器时仍直接setText未保存的 Vault 片段可能丢失。行为约定三条写入路径的差异化 flush 策略DIFF-358 明确了三条写入主编辑器的路径各自的行为其核心原则是“凡是会用外部输入覆盖编辑器缓冲区的路径在setText前必须先 flush仅对当前缓冲区本身做变换的路径不需要 flush”路径触发条件是否 flush失败处理ResultDialog「使用输出」点击结果弹层的「使用输出」按钮将dialogBody写回编辑器先 flush失败则提示并保持弹层打开InputDialog「运行」成功路径XML/Bean 转换弹层点击「运行」转换成功后把输出写回编辑器先 flush失败则提示不执行转换写回工具栏transform格式化/压缩等对当前editor.text做原地变换不额外 flush输入即editor.text无需落盘保护这里的flush指 flushJsonVaultEditorIfDirty当且仅当存在currentFile且编辑器文本与savedText不一致即“脏”时才执行写盘否则为 no-op。结果弹层ResultDialog使用输出前先 flush失败保持弹层在 JsonScreen.kt 中ResultDialog展示检查器转换产物session.dialogBody底部提供「复制」「使用输出」「关闭」三个按钮。「使用输出」的完整处理逻辑为MooButton(container.t(json.dialog.useOutput), prominent true, onClick { if (!jsonVaultFlushDirtyOrNotice(container, session, monitor, onConflict)) { onChanged() returnMooButton } onEdt { session.editor.setText(session.dialogBody, recordUndo true) } session.clearJsonPathQueryResult() session.dialogTitle session.dialogBody onChanged() })关键点按钮点击后第一件事是调用jsonVaultFlushDirtyOrNoticeflush 成功才继续setTextflush 失败如磁盘与已保存文本出现三方冲突时立即returnMooButton弹层的dialogTitle/dialogBody保持不变——用户不会因为点击「使用输出」而丢失转换结果也保证了未保存的 Vault 片段不被覆盖只有 flush 成功才会用recordUndo true的方式写回编辑器保留撤销历史、清空路径查询缓存并关闭弹层。转换弹层InputDialog运行成功路径同样先 flushXML → JSON、Java Bean → JSON 转换使用独立的InputDialogJsonScreen.kt用户输入位于session.conversionInput模式记录在session.conversionModexml/bean。「运行」按钮的处理MooButton(container.t(json.dialog.run), prominent true, onClick { if (!jsonVaultFlushDirtyOrNotice(container, session, monitor, onConflict)) { onChanged() returnMooButton } runCatching { if (session.conversionMode xml) JsonEngine.xmlToJson(session.conversionInput, translator) else JsonEngine.javaBeanToJson(session.conversionInput, translator) }.onSuccess { output - val input session.conversionInput onEdt { session.editor.setText(output, recordUndo true) } session.clearJsonPathQueryResult() ... container.history.save(ToolId.Json.id, title, title, input, output, JsonHistoryMetadata.encodeEditor()) closeConversion() }.onFailure { val message it.message ?: container.t(json.notice.failed) notifyJsonFailure(container, session, message, it) } onChanged() })这段代码体现了两个层次的数据保护写盘保护jsonVaultFlushDirtyOrNotice位于runCatching之前也就是说“把当前脏片段保存到磁盘”优先于“执行转换”flush 失败时整个运行动作中止转换弹层保持打开输入内容不丢失转换异常隔离xmlToJson/javaBeanToJson本身也会抛异常例如空输入、无字段通过runCatchingonFailure走notifyJsonFailure提示不会污染编辑器。转换成功后的写回同样使用recordUndo true并会把“输入 → 输出”写入历史记录供后续恢复恢复路径的 flush 保护见 DIFF-357。为什么工具栏transform不需要 flush与上面两条路径形成对照的是工具栏与检查器面板的transform系列动作JsonScreen.ktprivate fun transform( container: AppContainer, session: JsonSession, translator: JsonTranslator, notice: String, historySummary: String notice, runner: (String) - JsonWiringPresentation.TransformOutcome, ) { val input session.editor.text when (val outcome runner(input)) { is JsonWiringPresentation.TransformOutcome.Success - { val output outcome.output session.clearJsonPathQueryResult() onEdt { session.editor.setText(output, recordUndo true) } session.notice notice container.toastSuccess(notice) container.history.save(...) } ... } }格式化、压缩、键值对交换、转义/反转义等变换的输入就是当前editor.text这类操作是在当前缓冲区内容上原地推导新文本不引入任何外部数据源。即便不 flush编辑器内已有的未保存内容也全部包含在新结果中不存在“覆盖导致丢失”的问题。因此 DIFF-358 明确工具栏transform仍只改当前缓冲不额外 flush。从源码结构看这也与 DIFF-368 引入的辅助函数体系一致——flush 的适用对象是“切换/导入/覆盖当前打开片段”的外部写入路径而不是“基于当前缓冲计算”的原地变换路径。底层实现flushJsonVaultEditorIfDirty与统一的 flush 辅助函数flush 的核心实现在 JsonVaultSession.ktinternal fun flushJsonVaultEditorIfDirty( container: AppContainer, session: JsonSession, monitor: VaultRevisionMonitor?, onConflict: (VaultConflictState) - Unit, ): ResultUnit { if (session.currentFile.isBlank() || session.editor.text session.savedText) return Result.success(Unit) return saveJsonVault(container, session, monitor, showToast false, onConflict onConflict) }其语义可以拆解为快速跳过no-opcurrentFile为空编辑器未绑定任何 Vault 片段或editor.text savedText无未保存修改时直接成功返回。注意这里不会展示保存 toastshowToast false因为 flush 是链路内部行为不是用户主动“保存”脏时落盘否则调用saveJsonVault走完整写盘流程。JsonSessionSessionManager.kt通过currentFile、savedText与editor.text三者的组合来判定“脏”状态fun isVaultEditorDirty(): Boolean currentFile.isNotBlank() editor.text ! savedTextDIFF-358 中两个弹层调用的并不是flushJsonVaultEditorIfDirty本身而是它的包装函数jsonVaultFlushDirtyOrNotice由 DIFF-368 引入internal fun jsonVaultFlushDirtyOrNotice( container: AppContainer, session: JsonSession, monitor: VaultRevisionMonitor?, onConflict: (VaultConflictState) - Unit, ): Boolean flushJsonVaultEditorIfDirty(container, session, monitor, onConflict) .onFailure { session.notice it.message ?: container.t(json.notice.failed) } .isSuccess该辅助函数把“flush 失败 → 写session.notice错误提示→ 返回false”的重复模式收敛成一行调用结果弹层与转换弹层的if (!jsonVaultFlushDirtyOrNotice(...)) return...模式正是依赖它。同样地jsonVaultRequireFlushDirty则用于runCatching内部失败时抛IllegalStateException且notice已写入供拖放导入等路径复用。冲突判定flush 失败何时发生flush 并非总是成功。saveJsonVault在写盘前会通过VaultConflictEngine判定三方一致性VaultConflictEngine.kt 的规则为fun canOverwrite(savedText: String, diskText: String?, editorText: String): Boolean { if (diskText null) return false return diskText savedText || diskText editorText }磁盘内容与“已保存基线”一致或与编辑器当前文本一致 → 允许覆盖写盘磁盘为空文件被外部删除或三方内容互不相同编辑器、已保存基线、磁盘三方冲突→flush 失败触发onConflict(VaultConflictState(...))由jsonVaultFlushDirtyOrNotice写入session.notice并返回false。这正是 DIFF-358 中“失败则提示并保持弹层”的底层原因冲突状态下绝不能用转换结果覆盖可能承载用户未保存编辑的缓冲区。测试验证干净跳过与冲突提示DIFF-358 的验证命令为./gradlew :composeApp:desktopTest --offline。与 flush 辅助函数对应的测试用例在 JsonVaultFlushNoticeTest.kt 中覆盖了两个核心分支干净跳过flushOrNoticeSucceedsWhenVaultClean—— 会话绑定a.json且editor.text与savedText一致时jsonVaultFlushDirtyOrNotice返回true且不写任何notice冲突写提示flushOrNoticeFailsWithNoticeOnConflict—— 先让磁盘内容变为与已保存基线不同的{disk:true}编辑器保持{dirty:true}此时 flush 返回false且session.notice.isNotBlank()requireFlushThrowsWhenConflict则验证jsonVaultRequireFlushDirty在冲突时抛出IllegalStateException且notice已写入。这三组断言与 DIFF-358 的“失败则提示并保持弹层”行为一一对应可作为阅读源码与复现验证的切入点。与其他 flush 路径的关系一张完整的 Vault 数据安全地图DIFF-358 是 MooTool Next JSON Vault 系列 flush 改造中的一环。把相邻的 DIFF 文档拼合起来可以看到完整的“覆盖编辑器前先落盘”安全网络DIFF-341编辑器/Vault 树拖入.json入库并打开前 flushDIFF-352一次拖入多个.json只入库不切换时flush 当前脏片段DIFF-355拖入非 JSON 文本覆盖编辑器前 flushDIFF-356工具栏/「更多」导入文件前 flushDIFF-357历史恢复onRestore前 flushDIFF-358本文结果弹层「使用输出」与 XML/Bean 转换弹层「运行」写回前 flushDIFF-359Vault 树打开另一片段前统一改用flushJsonVaultEditorIfDirty不展示保存 toast。可以看出MooTool Next 对“任何会用外部内容替换当前编辑器缓冲区的动作”都执行了同一套保护逻辑差异仅在于失败时是“保持弹层/对话框打开并提示”还是“中止操作并回滚选中路径”核心语义完全对齐。这份设计文档与源码、测试互为印证读者可以沿 JsonVaultSession.kt 与 JsonScreen.kt 两条主线继续深入阅读。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐MooTool Compose 端 DIFF-356 实战JSON 工具栏与溢出菜单导入前的 Vault 脏片段 Flush 机制MooTool Compose 端 DIFF 356 实战JSON 工具栏与溢出菜单导入前的 Vault 脏片段 Flush 机制 导读 MooTool开发桌面应用开发工具MooToolCompose 桌面版JSON Vault 目录选中前强制 flush 脏片段prepareJsonVaultContext 语义、实现与测试MooToolCompose 桌面版JSON Vault 目录选中前强制 flush 脏片段prepareJsonVaultContext 语义、实现与测桌面应用开发工具MooToolCompose 桌面版DIFF-368 源码解析JSON Vault flush 失败 notice 统一辅助函数MooToolCompose 桌面版DIFF 368 源码解析JSON Vault flush 失败 notice 统一辅助函数 本文以 MooTool桌面应用开发工具上一篇5分钟快速上手React Native DropdownAlert 基础使用教程下一篇PyTorch Transformers数据变换技巧图像处理实战应用完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考