Select Page

MarkdowOO 開發日誌 - 檔案被覆蓋慘案

前言:慘案經過

那天我在 MarkdowOO 裡用 WordPress 模式寫了一篇文章。花時間把內容整理好,存檔時 app 從正文第一行標題自動設定為 WordPress 文章標題,一切看起來都很順利。

存完之後,我順手在另一個 tab 開了一個本地的 .md 檔案查資料。看完之後切回 WordPress 的 tab,繼續補充內容。

就在這時候,畫面頂端突然出現了一條橫幅:

「檔案已在外部變更,是否重新載入?」

我心想奇怪,明明只有我自己在用這篇文章,怎麼會有外部變更?但 MarkdowOO 這樣提示,我就反射性地點了「重新載入」。

然後——編輯器的內容整個換掉了,換掉的內容竟是我開本地端的檔案內容,心想!這Bug來的未免太.....令我想噴....一句話!

新文章才剛寫好就全都沒了!全都沒了!好吧!自已的坑自已救!

pasted image


架構背景

fileWatchersMap<wcId, FSWatcher>,整個 app window 只有一個 watcher,監視最後一個被 journal:readFile 讀取的本地檔案。

設計假設(單 tab 情境)

graph LR A["📄 本地檔 A.md"] -->|readFile| W["👁 Watcher"] W -->|externalChange| T["📝 active tab"]

實際情況(多 tab 情境)

graph LR WP["🌐 WP Tab(active)"] W["👁 Watcher"] B["📄 本地檔 B.md"] W -->|"watches"| B W -.->|"❌ externalChange\n(錯誤觸發)"| WP

Bug 觸發流程

sequenceDiagram actor User participant WP as WP Tab(filePath=null) participant Local as Local File Tab participant MP as Main Process participant FS as File System Note over WP: 正在編輯 WP 文章 User->>Local: 開啟 B.md(新 tab) Local->>MP: journal:readFile("B.md") MP->>FS: startWatching(webContents, "B.md") Note over MP: ⚠️ 唯一 watcher 改為監視 B.md User->>WP: 切回 WP tab 繼續寫作 Note over MP: Watcher 仍在監視 B.md,沒有停止 FS->>MP: B.md 檔案變更事件 MP->>WP: ipc: file:externalChange("B.md") Note over WP: ❌ 不檢查 active tab 是誰 User->>WP: 看到 banner,點「重新載入」 WP->>MP: journal:readFile("B.md") MP-->>WP: B.md 的內容 WP->>WP: updateActiveTabById(wpTabId,{ markdown: B.md 的內容 }) Note over WP: 💀 WP 精心寫好的內容被覆蓋!

根本原因

問題點 說明
① Watcher 不跟著 tab 切換 切換 active tab 時 main process 的 watcher 不更新,繼續監視舊 tab 的檔案
onFileExternalChange 不驗 tab 收到 IPC 後直接 setExternalChangedFile(path),沒檢查 active tab 的 filePath 是否吻合
reloadFromDisk 不驗 tab 直接讀 changedPath 內容寫入 active tab,沒有任何防護 — 這是造成資料損失的直接原因

修法

策略:兩層防護

不修改 main process 的 watcher 架構(複雜度高),在 renderer 兩個關鍵點加防護:

flowchart TD E["📩 file:externalChange 事件"] --> C1{"active tab.filePath\n=== changedPath?"} C1 -->|否 — WP/SSH tab| R1["resumeFileWatch()\n靜默恢復,不彈 banner"] C1 -->|是 — 本地檔 tab| S["setExternalChangedFile()\n顯示 banner"] S --> U["使用者點「重新載入」"] U --> C2{"active tab.filePath\n=== externalChangedFile?\n(race condition 防護)"} C2 -->|否| D["dismiss\n不更動任何 tab"] C2 -->|是| RL["readFile → 更新 tab ✅"]

修法後的行為對照

情境 修法前 修法後
active tab = 本地檔,該檔案變更 ✅ 正確顯示 banner ✅ 正確顯示 banner
active tab = WP tab,本地檔在後台變更 ❌ Banner 出現,reload 後 WP 內容被覆蓋 ✅ 靜默 resume,WP tab 完全不受影響
active tab = WP tab,點「重新載入」(race condition) ❌ WP 內容被覆蓋 ✅ Layer 2 攔截,dismiss banner 不動 tab
active tab = SSH tab,本地檔在後台變更 ❌ SSH 內容可能被覆蓋 ✅ 同樣保護(filePath 也是 null)

已知限制: 若使用者切換到本地檔 tab 後又切走,在 WP tab 期間發生的本地檔變更通知會被靜默忽略。切回本地檔 tab 時不會再補通知。這是可接受的 trade-off — 不損失資料比多一次通知重要。

結論

現階段這篇文章就是用 修正版的 MarkdowOO 寫的文章,看來功能是改善了!

pasted image