DIFF-358 解析:JSON 轉(zhuǎn)換結(jié)果/彈層寫入編輯器前的 Vault 臟數(shù)據(jù) Flush 機(jī)制)
桌面應(yīng)用開發(fā)工具【免費(fèi)下載鏈接】MooToolHandy tool set for developers項(xiàng)目地址https://gitcode.com/gh_mirrors/mo/MooTool點(diǎn)擊查看免費(fèi)下載本文圍繞 MooTool NextCompose 桌面端JSON 工具的轉(zhuǎn)換結(jié)果彈層與 XML/Bean 轉(zhuǎn)換彈層展開聚焦“結(jié)果寫入編輯器前必須先保存當(dāng)前未落盤的 Vault 片段”這一數(shù)據(jù)安全策略。讀完本文你將理解 flush 的觸發(fā)時(shí)機(jī)、失敗處理語義、與歷史恢復(fù)/導(dǎo)入路徑的異同以及如何通過JsonVaultSession與VaultConflictEngine在源碼層面驗(yàn)證這套機(jī)制。背景與問題setText直接覆蓋編輯器帶來的 Vault 數(shù)據(jù)丟失風(fēng)險(xiǎn)MooTool 的 JSON 工具桌面端基于 Compose for Desktop 實(shí)現(xiàn)內(nèi)置了 Vault片段庫能力編輯器當(dāng)前內(nèi)容可以與 Vault 中某個(gè).json片段綁定currentFilesavedText未保存的編輯即“臟緩沖區(qū)”。當(dāng)用戶把某個(gè)轉(zhuǎn)換結(jié)果如 JSON → XML通過結(jié)果彈層的「使用輸出」按鈕寫回主編輯器時(shí)如果實(shí)現(xiàn)直接用setText覆蓋那么當(dāng)前已打開、但尚未保存的 Vault 片段就可能丟失編輯內(nèi)容同時(shí)造成“磁盤上的片段內(nèi)容”與“編輯器即將顯示的內(nèi)容”不一致后續(xù)的保存與沖突判定都會(huì)變得混亂。DIFF-357 已經(jīng)為歷史恢復(fù)路徑補(bǔ)上了 flushDIFF-358 是這條安全鏈路的延續(xù)補(bǔ)齊了**檢查器結(jié)果彈層jsonToXml/jsonToBean等「使用輸出」**與XML/Bean 轉(zhuǎn)換彈層這兩條此前仍然直接setText的路徑。設(shè)計(jì)文檔原文給出的問題定位如下檢查器結(jié)果彈層「使用輸出」jsonToXml/jsonToBean等與XML/Bean 轉(zhuǎn)換彈層成功寫入主編輯器時(shí)仍直接setText未保存的 Vault 片段可能丟失。行為約定三條寫入路徑的差異化 flush 策略DIFF-358 明確了三條寫入主編輯器的路徑各自的行為其核心原則是“凡是會(huì)用外部輸入覆蓋編輯器緩沖區(qū)的路徑在setText前必須先 flush僅對(duì)當(dāng)前緩沖區(qū)本身做變換的路徑不需要 flush”路徑觸發(fā)條件是否 flush失敗處理ResultDialog「使用輸出」點(diǎn)擊結(jié)果彈層的「使用輸出」按鈕將dialogBody寫回編輯器先 flush失敗則提示并保持彈層打開InputDialog「運(yùn)行」成功路徑XML/Bean 轉(zhuǎn)換彈層點(diǎn)擊「運(yùn)行」轉(zhuǎn)換成功后把輸出寫回編輯器先 flush失敗則提示不執(zhí)行轉(zhuǎn)換寫回工具欄transform格式化/壓縮等對(duì)當(dāng)前editor.text做原地變換不額外 flush輸入即editor.text無需落盤保護(hù)這里的flush指 flushJsonVaultEditorIfDirty當(dāng)且僅當(dāng)存在currentFile且編輯器文本與savedText不一致即“臟”時(shí)才執(zhí)行寫盤否則為 no-op。結(jié)果彈層ResultDialog使用輸出前先 flush失敗保持彈層在 JsonScreen.kt 中ResultDialog展示檢查器轉(zhuǎn)換產(chǎn)物session.dialogBody底部提供「復(fù)制」「使用輸出」「關(guān)閉」三個(gè)按鈕?!甘褂幂敵觥沟耐暾幚磉壿嫗镸ooButton(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() })關(guān)鍵點(diǎn)按鈕點(diǎn)擊后第一件事是調(diào)用jsonVaultFlushDirtyOrNoticeflush 成功才繼續(xù)setTextflush 失敗如磁盤與已保存文本出現(xiàn)三方?jīng)_突時(shí)立即returnMooButton彈層的dialogTitle/dialogBody保持不變——用戶不會(huì)因?yàn)辄c(diǎn)擊「使用輸出」而丟失轉(zhuǎn)換結(jié)果也保證了未保存的 Vault 片段不被覆蓋只有 flush 成功才會(huì)用recordUndo true的方式寫回編輯器保留撤銷歷史、清空路徑查詢緩存并關(guān)閉彈層。轉(zhuǎn)換彈層InputDialog運(yùn)行成功路徑同樣先 flushXML → JSON、Java Bean → JSON 轉(zhuǎn)換使用獨(dú)立的InputDialogJsonScreen.kt用戶輸入位于session.conversionInput模式記錄在session.conversionModexml/bean?!高\(yùn)行」按鈕的處理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() })這段代碼體現(xiàn)了兩個(gè)層次的數(shù)據(jù)保護(hù)寫盤保護(hù)jsonVaultFlushDirtyOrNotice位于runCatching之前也就是說“把當(dāng)前臟片段保存到磁盤”優(yōu)先于“執(zhí)行轉(zhuǎn)換”flush 失敗時(shí)整個(gè)運(yùn)行動(dòng)作中止轉(zhuǎn)換彈層保持打開輸入內(nèi)容不丟失轉(zhuǎn)換異常隔離xmlToJson/javaBeanToJson本身也會(huì)拋異常例如空輸入、無字段通過runCatchingonFailure走notifyJsonFailure提示不會(huì)污染編輯器。轉(zhuǎn)換成功后的寫回同樣使用recordUndo true并會(huì)把“輸入 → 輸出”寫入歷史記錄供后續(xù)恢復(fù)恢復(fù)路徑的 flush 保護(hù)見 DIFF-357。為什么工具欄transform不需要 flush與上面兩條路徑形成對(duì)照的是工具欄與檢查器面板的transform系列動(dòng)作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(...) } ... } }格式化、壓縮、鍵值對(duì)交換、轉(zhuǎn)義/反轉(zhuǎn)義等變換的輸入就是當(dāng)前editor.text這類操作是在當(dāng)前緩沖區(qū)內(nèi)容上原地推導(dǎo)新文本不引入任何外部數(shù)據(jù)源。即便不 flush編輯器內(nèi)已有的未保存內(nèi)容也全部包含在新結(jié)果中不存在“覆蓋導(dǎo)致丟失”的問題。因此 DIFF-358 明確工具欄transform仍只改當(dāng)前緩沖不額外 flush。從源碼結(jié)構(gòu)看這也與 DIFF-368 引入的輔助函數(shù)體系一致——flush 的適用對(duì)象是“切換/導(dǎo)入/覆蓋當(dāng)前打開片段”的外部寫入路徑而不是“基于當(dāng)前緩沖計(jì)算”的原地變換路徑。底層實(shí)現(xiàn)flushJsonVaultEditorIfDirty與統(tǒng)一的 flush 輔助函數(shù)flush 的核心實(shí)現(xiàn)在 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無未保存修改時(shí)直接成功返回。注意這里不會(huì)展示保存 toastshowToast false因?yàn)?flush 是鏈路內(nèi)部行為不是用戶主動(dòng)“保存”臟時(shí)落盤否則調(diào)用saveJsonVault走完整寫盤流程。JsonSessionSessionManager.kt通過currentFile、savedText與editor.text三者的組合來判定“臟”狀態(tài)fun isVaultEditorDirty(): Boolean currentFile.isNotBlank() editor.text ! savedTextDIFF-358 中兩個(gè)彈層調(diào)用的并不是flushJsonVaultEditorIfDirty本身而是它的包裝函數(shù)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該輔助函數(shù)把“flush 失敗 → 寫session.notice錯(cuò)誤提示→ 返回false”的重復(fù)模式收斂成一行調(diào)用結(jié)果彈層與轉(zhuǎn)換彈層的if (!jsonVaultFlushDirtyOrNotice(...)) return...模式正是依賴它。同樣地jsonVaultRequireFlushDirty則用于runCatching內(nèi)部失敗時(shí)拋IllegalStateException且notice已寫入供拖放導(dǎo)入等路徑復(fù)用。沖突判定flush 失敗何時(shí)發(fā)生flush 并非總是成功。saveJsonVault在寫盤前會(huì)通過VaultConflictEngine判定三方一致性VaultConflictEngine.kt 的規(guī)則為fun canOverwrite(savedText: String, diskText: String?, editorText: String): Boolean { if (diskText null) return false return diskText savedText || diskText editorText }磁盤內(nèi)容與“已保存基線”一致或與編輯器當(dāng)前文本一致 → 允許覆蓋寫盤磁盤為空文件被外部刪除或三方內(nèi)容互不相同編輯器、已保存基線、磁盤三方?jīng)_突→flush 失敗觸發(fā)onConflict(VaultConflictState(...))由jsonVaultFlushDirtyOrNotice寫入session.notice并返回false。這正是 DIFF-358 中“失敗則提示并保持彈層”的底層原因沖突狀態(tài)下絕不能用轉(zhuǎn)換結(jié)果覆蓋可能承載用戶未保存編輯的緩沖區(qū)。測試驗(yàn)證干凈跳過與沖突提示DIFF-358 的驗(yàn)證命令為./gradlew :composeApp:desktopTest --offline。與 flush 輔助函數(shù)對(duì)應(yīng)的測試用例在 JsonVaultFlushNoticeTest.kt 中覆蓋了兩個(gè)核心分支干凈跳過flushOrNoticeSucceedsWhenVaultClean—— 會(huì)話綁定a.json且editor.text與savedText一致時(shí)jsonVaultFlushDirtyOrNotice返回true且不寫任何notice沖突寫提示flushOrNoticeFailsWithNoticeOnConflict—— 先讓磁盤內(nèi)容變?yōu)榕c已保存基線不同的{disk:true}編輯器保持{dirty:true}此時(shí) flush 返回false且session.notice.isNotBlank()requireFlushThrowsWhenConflict則驗(yàn)證jsonVaultRequireFlushDirty在沖突時(shí)拋出IllegalStateException且notice已寫入。這三組斷言與 DIFF-358 的“失敗則提示并保持彈層”行為一一對(duì)應(yīng)可作為閱讀源碼與復(fù)現(xiàn)驗(yàn)證的切入點(diǎn)。與其他 flush 路徑的關(guān)系一張完整的 Vault 數(shù)據(jù)安全地圖DIFF-358 是 MooTool Next JSON Vault 系列 flush 改造中的一環(huán)。把相鄰的 DIFF 文檔拼合起來可以看到完整的“覆蓋編輯器前先落盤”安全網(wǎng)絡(luò)DIFF-341編輯器/Vault 樹拖入.json入庫并打開前 flushDIFF-352一次拖入多個(gè).json只入庫不切換時(shí)flush 當(dāng)前臟片段DIFF-355拖入非 JSON 文本覆蓋編輯器前 flushDIFF-356工具欄/「更多」導(dǎo)入文件前 flushDIFF-357歷史恢復(fù)onRestore前 flushDIFF-358本文結(jié)果彈層「使用輸出」與 XML/Bean 轉(zhuǎn)換彈層「運(yùn)行」寫回前 flushDIFF-359Vault 樹打開另一片段前統(tǒng)一改用flushJsonVaultEditorIfDirty不展示保存 toast。可以看出MooTool Next 對(duì)“任何會(huì)用外部內(nèi)容替換當(dāng)前編輯器緩沖區(qū)的動(dòng)作”都執(zhí)行了同一套保護(hù)邏輯差異僅在于失敗時(shí)是“保持彈層/對(duì)話框打開并提示”還是“中止操作并回滾選中路徑”核心語義完全對(duì)齊。這份設(shè)計(jì)文檔與源碼、測試互為印證讀者可以沿 JsonVaultSession.kt 與 JsonScreen.kt 兩條主線繼續(xù)深入閱讀。贊分享桌面應(yīng)用開發(fā)工具【免費(fèi)下載鏈接】MooToolHandy tool set for developers項(xiàng)目地址https://gitcode.com/gh_mirrors/mo/MooTool點(diǎn)擊查看免費(fèi)下載相關(guān)推薦MooTool Compose 端 DIFF-356 實(shí)戰(zhàn)JSON 工具欄與溢出菜單導(dǎo)入前的 Vault 臟片段 Flush 機(jī)制MooTool Compose 端 DIFF 356 實(shí)戰(zhàn)JSON 工具欄與溢出菜單導(dǎo)入前的 Vault 臟片段 Flush 機(jī)制 導(dǎo)讀 MooTool開發(fā)桌面應(yīng)用開發(fā)工具M(jìn)ooToolCompose 桌面版JSON Vault 目錄選中前強(qiáng)制 flush 臟片段prepareJsonVaultContext 語義、實(shí)現(xiàn)與測試MooToolCompose 桌面版JSON Vault 目錄選中前強(qiáng)制 flush 臟片段prepareJsonVaultContext 語義、實(shí)現(xiàn)與測桌面應(yīng)用開發(fā)工具M(jìn)ooToolCompose 桌面版DIFF-368 源碼解析JSON Vault flush 失敗 notice 統(tǒng)一輔助函數(shù)MooToolCompose 桌面版DIFF 368 源碼解析JSON Vault flush 失敗 notice 統(tǒng)一輔助函數(shù) 本文以 MooTool桌面應(yīng)用開發(fā)工具上一篇5分鐘快速上手React Native DropdownAlert 基礎(chǔ)使用教程下一篇PyTorch Transformers數(shù)據(jù)變換技巧圖像處理實(shí)戰(zhàn)應(yīng)用完整指南創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考