From 0deef3dbd97cd70e674905f09b61f11e92eda883 Mon Sep 17 00:00:00 2001 From: richblack Date: Tue, 28 Jul 2026 12:03:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(t86=20=F0=9F=94=B4):=20=E6=8F=9B=E7=9F=A5?= =?UTF-8?q?=E8=AD=98=E5=BA=AB=E5=AF=A6=E4=BE=8B=E6=99=82=E6=B8=85=E7=A9=BA?= =?UTF-8?q?=E7=9C=8B=E5=AE=88=E8=B3=87=E6=96=99=E5=A4=BE=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=A0=B5=E5=80=8B=E8=B3=87=E5=A4=96=E6=B4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit leo 07-28 實測坐實:youlin 時代的資料夾被原封同步進 geek6688 新實例 (「假如我把個人的同步到官方去就很危險,個資外泄」)。 修:instanceChanged(old,new) 比 host;連線精靈成功後若換了實例→清空 WatchFolders/WatchFolder,成功對話框改為說明「因為換了知識庫,為避免把舊資料夾 誤傳到新知識庫,請重新用『+新增知識資料夾…』選擇」;同實例改密碼行為照舊。 測試(main_test.go):host 異同判定含大小寫/尾斜線/scheme 差異。tray go test 綠。 (實作=子 CC;審查+commit=總管。⚠️ manifest 沿用=t86b 另案未含) --- cmd/arcrun-tray/main.go | 41 ++++++++++++++-- cmd/arcrun-tray/main_test.go | 91 ++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 4 deletions(-) diff --git a/cmd/arcrun-tray/main.go b/cmd/arcrun-tray/main.go index 405da3a..16837c3 100644 --- a/cmd/arcrun-tray/main.go +++ b/cmd/arcrun-tray/main.go @@ -292,8 +292,35 @@ func fetchConfigByLogin(portalURL, email, password string) (*daemonConfigResp, e return &out, nil } -// applyRemoteConfig 把換到的設定寫進本地 config(保留既有看守資料夾)。 -func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) { +// instanceChanged 比對兩個 cypher_url 的 host;host 不同代表換了知識庫實例。 +// 只比 host 不比 scheme/path——同實例改密碼時 URL 完全不變,不觸發資料夾清空。 +// 兩端有任一為空(首次設定或異常值)時回 false,不誤觸清空。 +// +// t86(leo 2026-07-28 實測:youlin 時代的資料夾被同步進 geek6688 新實例—個資外洩)。 +func instanceChanged(oldURL, newURL string) bool { + parseHost := func(s string) string { + u, err := neturl.Parse(strings.TrimSpace(s)) + if err != nil || u.Host == "" { + return strings.TrimSpace(s) + } + return u.Host + } + oldHost := parseHost(oldURL) + newHost := parseHost(newURL) + return oldHost != "" && newHost != "" && oldHost != newHost +} + +// applyRemoteConfig 把換到的設定寫進本地 config。 +// 回傳 true 代表換了知識庫實例(host 不同):此時已清空 WatchFolders/WatchFolder, +// 呼叫端應提示用戶重新選擇資料夾,避免舊資料夾誤傳到新知識庫(t86)。 +func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) bool { + // t86:換了實例(host 改變)就清空資料夾清單——在寫入新 URL 之前比對, + // 確保比的是「舊 host vs 新 host」而非「新 vs 新」。 + switched := instanceChanged(cfg.CypherURL, r.Config.CypherURL) + if switched { + cfg.WatchFolders = nil + cfg.WatchFolder = "" + } cfg.CypherURL = r.Config.CypherURL cfg.Namespace = r.Config.Namespace if r.Config.Library != "" { @@ -309,6 +336,7 @@ func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) { if cfg.Manifest == "" { cfg.Manifest = filepath.Join(appDir(), "manifest.json") } + return switched } // registerLibraries 把「這台機器看守的資料夾各自對應的庫」報上雲端自動登記(t52)。 @@ -472,7 +500,7 @@ func main() { showConnectWizardWith(urlEntry.Text, emailEntry.Text) return } - applyRemoteConfig(cfg, r) + switched := applyRemoteConfig(cfg, r) if err := saveConfig(cfg); err != nil { dialog.ShowError(err, win) return @@ -483,7 +511,12 @@ func main() { } restartWatch() rebuildTray() - dialog.ShowInformation("連上了", "已連上「"+connectionStatusLabel(cfg.InstanceName, cfg.Email)+"」。\n接下來用選單「+ 新增知識資料夾…」挑要同步的資料夾就好。", win) + // t86:換了知識庫實例時,說明已清空資料夾清單的原因,請用戶重新選資料夾。 + if switched { + dialog.ShowInformation("連上了", "因為換了知識庫,為了避免把舊資料夾誤傳到新知識庫,請重新用「+ 新增知識資料夾…」選擇要同步的資料夾。", win) + } else { + dialog.ShowInformation("連上了", "已連上「"+connectionStatusLabel(cfg.InstanceName, cfg.Email)+"」。\n接下來用選單「+ 新增知識資料夾…」挑要同步的資料夾就好。", win) + } }() }, win) d.Resize(fyne.NewSize(460, 320)) diff --git a/cmd/arcrun-tray/main_test.go b/cmd/arcrun-tray/main_test.go index c8ff877..45572df 100644 --- a/cmd/arcrun-tray/main_test.go +++ b/cmd/arcrun-tray/main_test.go @@ -92,3 +92,94 @@ func TestDirectConfigJSONOmitsEmptyInstanceName(t *testing.T) { t.Errorf("暱稱空時不該序列化出 instance_name 欄:%s", data) } } + +// ── t86:instanceChanged ───────────────────────────────────────────────────── + +func TestInstanceChanged(t *testing.T) { + cases := []struct { + name string + oldURL string + newURL string + want bool + }{ + { + "同 host 同實例改密碼不觸發清空", + "https://arcrun-cypher-executor.youlin.workers.dev", + "https://arcrun-cypher-executor.youlin.workers.dev", + false, + }, + { + "不同 host 換實例觸發清空", + "https://arcrun-cypher-executor.youlin.workers.dev", + "https://arcrun-cypher-executor.geek6688.workers.dev", + true, + }, + { + "舊 URL 空(首次設定)不觸發清空", + "", + "https://arcrun-cypher-executor.geek6688.workers.dev", + false, + }, + { + "新 URL 空(異常值)不觸發清空", + "https://arcrun-cypher-executor.youlin.workers.dev", + "", + false, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := instanceChanged(c.oldURL, c.newURL) + if got != c.want { + t.Errorf("instanceChanged(%q, %q) = %v, want %v", c.oldURL, c.newURL, got, c.want) + } + }) + } +} + +// t86:換實例後 applyRemoteConfig 清空資料夾清單 +func TestApplyRemoteConfigClearsFoldersOnInstanceSwitch(t *testing.T) { + cfg := &directConfig{ + CypherURL: "https://arcrun-cypher-executor.youlin.workers.dev", + Namespace: "youlin", + WatchFolder: "/Users/youlin/KnowledgeBase", + WatchFolders: []string{"/Users/youlin/KnowledgeBase", "/Users/youlin/Finance"}, + } + r := &daemonConfigResp{} + r.Config.CypherURL = "https://arcrun-cypher-executor.geek6688.workers.dev" + r.Config.Namespace = "geek6688" + + switched := applyRemoteConfig(cfg, r) + + if !switched { + t.Error("換了不同 host 應回傳 switched=true") + } + if cfg.WatchFolder != "" { + t.Errorf("換實例後 WatchFolder 應清空,got %q", cfg.WatchFolder) + } + if len(cfg.WatchFolders) != 0 { + t.Errorf("換實例後 WatchFolders 應清空,got %v", cfg.WatchFolders) + } +} + +// t86:同實例改密碼不清空資料夾清單 +func TestApplyRemoteConfigKeepsFoldersOnSameInstance(t *testing.T) { + cfg := &directConfig{ + CypherURL: "https://arcrun-cypher-executor.youlin.workers.dev", + Namespace: "youlin", + WatchFolder: "/Users/youlin/KnowledgeBase", + WatchFolders: []string{"/Users/youlin/KnowledgeBase"}, + } + r := &daemonConfigResp{} + r.Config.CypherURL = "https://arcrun-cypher-executor.youlin.workers.dev" + r.Config.Namespace = "youlin" + + switched := applyRemoteConfig(cfg, r) + + if switched { + t.Error("同 host 不應回傳 switched=true") + } + if cfg.WatchFolder == "" || len(cfg.WatchFolders) == 0 { + t.Error("同實例不應清空資料夾清單") + } +}