diff --git a/cmd/arcrun-app/app.go b/cmd/arcrun-app/app.go index abdc49a..656121d 100644 --- a/cmd/arcrun-app/app.go +++ b/cmd/arcrun-app/app.go @@ -221,10 +221,25 @@ func loadSyncStatus() syncStatus { } // describeStatus 產生狀態文案。 -// 🔴 issue #17:正在跑就要**看得出來在跑**——按「立刻同步」會寫 sync-now 訊號檔, -// collector 開工時才把它吃掉;訊號檔還在=已排隊未完成 ⇒ 顯示「同步中…」。 +// 🔴 t195(leo 08-05 實撞:「燈號是真的還是假的?」——**是假的**): +// +// 舊版只看「sync-now 訊號檔存不存在」就顯示「同步中…」。 +// 但那只代表**排隊了**,不代表有人在處理:leo 的 collector 在 11:25 死掉, +// 他 11:38 按同步 ⇒ 訊號檔沒人消化 ⇒ 畫面一直說「正在整理知識卡」, +// 實際上 13 分鐘沒跑過任何一輪、也不會產卡。**這比沒有燈號更糟——它在說謊。** +// +// ⇒ 燈號改成有憑有據: +// · collector 沒在跑 → 明說「同步引擎沒有在跑」,不要假裝在整理 +// · 有訊號檔且引擎活著 → 才是真的「同步中」 +// · 其餘 → 看守中 func describeStatus(s syncStatus) (syncing bool, big, sub string) { - if _, err := os.Stat(syncNowSignal()); err == nil { + alive := collectorAlive() + _, queued := os.Stat(syncNowSignal()) + + if !alive { + return false, "同步引擎沒有在跑", "資料夾不會被自動整理 ⇒ 請結束 Arcrun 再重新開啟" + } + if queued == nil { return true, "同步中… 正在讀檔並整理成知識卡", "請稍候,完成後會顯示整理了幾份" } if s.ExtractorError != "" && !s.ExtractorOK { diff --git a/cmd/arcrun-app/build-dmg.sh b/cmd/arcrun-app/build-dmg.sh new file mode 100755 index 0000000..4162883 --- /dev/null +++ b/cmd/arcrun-app/build-dmg.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# build-dmg.sh — 打包成 Mac 標準安裝檔(t194,leo:「你應該包成 Mac 安裝檔,我完整做一次」) +# +# DMG 裡放 Arcrun.app + Applications 捷徑 ⇒ 開啟就是「把 App 拖進去」的標準畫面。 +# 為什麼一定要:使用者若直接在下載資料夾裡跑,自更新會蓋錯位置(t184 Oscar 的病); +# 而且 macOS 對「不在 /Applications 的常駐程式」權限行為也不一致。 +set -euo pipefail +cd "$(dirname "$0")" +VERSION="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}" + +echo "① 先確保 .app 是最新的" +VERSION="$VERSION" bash build-mac.sh >/dev/null + +APP="build/bin/Arcrun.app" +[ -d "$APP" ] || { echo "❌ 找不到 $APP" >&2; exit 1; } + +OUT="dist"; DMG="$OUT/Arcrun-${VERSION}.dmg" +STAGE="$(mktemp -d)/Arcrun" +mkdir -p "$OUT" "$STAGE" + +echo "② 放 app + Applications 捷徑" +ditto "$APP" "$STAGE/Arcrun.app" +ln -s /Applications "$STAGE/Applications" + +echo "③ 產 DMG(-fs HFS+:否則含空格/中文的名稱會被截斷)" +rm -f "$DMG" +hdiutil create -volname "Arcrun" -srcfolder "$STAGE" -fs HFS+ -ov -format UDZO "$DMG" >/dev/null +rm -rf "$(dirname "$STAGE")" + +SIZE=$(ls -lh "$DMG" | awk '{print $5}') +echo "✅ 完成:${DMG}(${SIZE})" +echo +echo "🔬 使用者流程:開 DMG → 把 Arcrun 拖到 Applications → 從啟動台開啟" diff --git a/cmd/arcrun-app/supervise.go b/cmd/arcrun-app/supervise.go index b0619ca..ae3d977 100644 --- a/cmd/arcrun-app/supervise.go +++ b/cmd/arcrun-app/supervise.go @@ -9,6 +9,7 @@ package main // 邊界:**複用既有的 supervisor 套件**(重起退避、狀態機、round 解析都在裡面, // 含 t191 的 phase:start/done ⇒ 「同步中…」)。這裡只做「找到執行檔、拉起來」。 import ( + "fmt" "os" "os/exec" "os/signal" @@ -43,10 +44,34 @@ func collectorBinPath() string { // startSupervisor 在 App 啟動時拉起 collector;沒有 config 就先不啟動 // (使用者還沒連知識庫,啟動只會一直失敗刷 log)。 func startSupervisor() { - sup = supervisor.New(collectorBinPath(), configPath()) - if _, err := os.Stat(configPath()); err == nil { - sup.Start() + bin := collectorBinPath() + cfg := configPath() + // 🔴 t195:這裡以前只 Stat(config) 就靜默 return,出問題完全查不到。 + // leo 實撞:App 活著、collector 從沒啟動、畫面卻顯示「正在整理知識卡」。 + // ⇒ 每個 return 點都要留下痕跡(寫進 collector.log 旁邊的 app.log)。 + if _, err := os.Stat(bin); err != nil { + appLog("啟動同步引擎失敗:找不到 %s(%v)", bin, err) + return } + if _, err := os.Stat(cfg); err != nil { + appLog("尚未連上知識庫(沒有 %s),同步引擎先不啟動", cfg) + return + } + sup = supervisor.New(bin, cfg) + sup.Start() + appLog("同步引擎已啟動:%s", bin) +} + +// appLog 把 App 層的關鍵事件寫進 ~/.arcrun-rag/app.log。 +// 沒有這個,「為什麼沒同步」就只能用猜的(leo 這次就卡在這)。 +func appLog(format string, args ...any) { + f, err := os.OpenFile(filepath.Join(appDir(), "app.log"), + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return + } + defer f.Close() + fmt.Fprintf(f, "%s %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, args...)) } // restartWatch 在設定變更後重起看守,讓新設定立刻生效。 @@ -109,3 +134,13 @@ func processRunning(bin string) bool { out, _ := exec.Command("pgrep", "-f", bin).Output() return len(strings.TrimSpace(string(out))) > 0 } + +// collectorAlive 回報「同步引擎是不是真的在跑」。 +// 🔴 t195:燈號不可以只憑「訊號檔存在」就說在同步——leo 實撞過 +// collector 已死、畫面卻一直顯示「正在整理知識卡」13 分鐘。 +func collectorAlive() bool { + if sup == nil { + return false + } + return processRunning(collectorBinPath()) +}