diff --git a/backend/app_browser_core_scan_test.go b/backend/app_browser_core_scan_test.go new file mode 100644 index 00000000..0d2813a9 --- /dev/null +++ b/backend/app_browser_core_scan_test.go @@ -0,0 +1,84 @@ +package backend + +import ( + "ant-chrome/backend/internal/browser" + "ant-chrome/backend/internal/config" + "os" + "path/filepath" + "testing" +) + +type browserCoreDAOStub struct { + list []browser.Core +} + +func (s *browserCoreDAOStub) List() ([]browser.Core, error) { + return append([]browser.Core{}, s.list...), nil +} + +func (s *browserCoreDAOStub) Upsert(core browser.Core) error { + for i := range s.list { + if s.list[i].CoreId == core.CoreId { + s.list[i] = core + return nil + } + } + s.list = append(s.list, core) + return nil +} + +func (s *browserCoreDAOStub) Delete(coreId string) error { + next := s.list[:0] + for _, core := range s.list { + if core.CoreId != coreId { + next = append(next, core) + } + } + s.list = next + return nil +} + +func (s *browserCoreDAOStub) SetDefault(coreId string) error { + for i := range s.list { + s.list[i].IsDefault = s.list[i].CoreId == coreId + } + return nil +} + +func TestBrowserCoreScanRegistersDetectedCoreInDAO(t *testing.T) { + root := t.TempDir() + coreDir := filepath.Join(root, "chrome", "chromium-148") + exePath := filepath.Join(coreDir, filepath.FromSlash(browser.CoreExecutableCandidates()[0])) + if err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil { + t.Fatalf("创建测试内核目录失败: %v", err) + } + if err := os.WriteFile(exePath, []byte("stub"), 0o755); err != nil { + t.Fatalf("写入测试内核可执行文件失败: %v", err) + } + + cfg := config.DefaultConfig() + cfg.Browser.Cores = nil + + app := NewApp(root) + app.config = cfg + app.browserMgr = browser.NewManager(cfg, root) + dao := &browserCoreDAOStub{} + app.browserMgr.CoreDAO = dao + + cores := app.BrowserCoreScan() + if len(dao.list) != 1 { + t.Fatalf("扫描后应写入 DAO 1 个内核,got=%d", len(dao.list)) + } + if dao.list[0].CoreId != "core-chromium-148" { + t.Fatalf("内核 ID 不符合预期: got=%q", dao.list[0].CoreId) + } + if dao.list[0].CorePath != filepath.Join("chrome", "chromium-148") { + t.Fatalf("内核路径不符合预期: got=%q", dao.list[0].CorePath) + } + if !dao.list[0].IsDefault { + t.Fatal("首个扫描到的内核应设为默认") + } + if len(cores) != 1 || cores[0].CoreId != dao.list[0].CoreId { + t.Fatalf("扫描返回列表未包含新内核: %+v", cores) + } +} diff --git a/backend/app_utils.go b/backend/app_utils.go index 405ff3da..4724c9f9 100644 --- a/backend/app_utils.go +++ b/backend/app_utils.go @@ -9,6 +9,7 @@ import ( "net" "os" "path/filepath" + "strings" "time" "github.com/google/uuid" @@ -58,7 +59,7 @@ func (a *App) ensureDefaultCores() { log := logger.New("Browser") // 扫描 chrome/ 目录,无论配置是否已有内核都执行一次,确保新增子目录被发现 - detected := a.scanChromeDir("chrome") + detected := a.scanChromeDir(a.browserCoreRoot()) if len(a.config.Browser.Cores) == 0 { // 配置为空:直接用扫描结果,或兜底写一个占位 @@ -100,9 +101,8 @@ func (a *App) ensureDefaultCores() { func (a *App) autoDetectCores() { log := logger.New("Browser") - // ensureDefaultCores 已完成扫描注册,这里只做路径有效性日志。 + cores := a.scanAndRegisterCores() // SQLite 模式下以内核表为准,避免与 config.yaml 历史条目不一致。 - cores := a.config.Browser.Cores if a.browserMgr != nil { cores = a.browserMgr.ListCores() } @@ -116,6 +116,61 @@ func (a *App) autoDetectCores() { } } +func (a *App) browserCoreRoot() string { + if a != nil && a.config != nil { + if root := strings.TrimSpace(a.config.Browser.CoreRoot); root != "" { + return root + } + } + return "chrome" +} + +func (a *App) scanAndRegisterCores() []browser.Core { + log := logger.New("Browser") + detected := a.scanChromeDir(a.browserCoreRoot()) + if len(detected) == 0 || a.browserMgr == nil { + return detected + } + + existing := a.browserMgr.ListCores() + knownPaths := make(map[string]struct{}, len(existing)) + hasDefault := false + for _, core := range existing { + knownPaths[normalizeCorePathForCompare(core.CorePath)] = struct{}{} + if core.IsDefault { + hasDefault = true + } + } + + for _, core := range detected { + if _, ok := knownPaths[normalizeCorePathForCompare(core.CorePath)]; ok { + continue + } + core.IsDefault = !hasDefault + if err := a.browserMgr.SaveCore(browser.CoreInput{ + CoreId: core.CoreId, + CoreName: core.CoreName, + CorePath: core.CorePath, + IsDefault: core.IsDefault, + }); err != nil { + log.Warn("自动注册内核失败", logger.F("core_id", core.CoreId), logger.F("path", core.CorePath), logger.F("error", err.Error())) + continue + } + log.Info("发现新内核,已注册", logger.F("core_id", core.CoreId), logger.F("path", core.CorePath)) + knownPaths[normalizeCorePathForCompare(core.CorePath)] = struct{}{} + hasDefault = hasDefault || core.IsDefault + } + return a.browserMgr.ListCores() +} + +func normalizeCorePathForCompare(path string) string { + path = strings.TrimSpace(path) + if path == "" { + return "" + } + return filepath.ToSlash(filepath.Clean(path)) +} + // scanChromeDir 扫描指定目录,将包含浏览器可执行文件的子文件夹识别为内核。 // 如果目录本身包含可执行文件(旧版单内核结构),则直接返回该目录作为内核。 func (a *App) scanChromeDir(chromeRoot string) []browser.Core {