mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
channel: master version: 1.0.0 source-ref: master published-at-utc: 2026-03-13T15:19:28Z
345 lines
9.3 KiB
Go
345 lines
9.3 KiB
Go
package browser
|
|
|
|
import (
|
|
"ant-chrome/backend/internal/logger"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// GetCore 根据 coreId 获取内核配置
|
|
func (m *Manager) GetCore(coreId string) (Core, bool) {
|
|
coreId = strings.TrimSpace(coreId)
|
|
if coreId == "" {
|
|
return Core{}, false
|
|
}
|
|
for _, core := range m.Config.Browser.Cores {
|
|
if strings.EqualFold(core.CoreId, coreId) {
|
|
return core, true
|
|
}
|
|
}
|
|
return Core{}, false
|
|
}
|
|
|
|
// GetDefaultCore 获取默认内核
|
|
func (m *Manager) GetDefaultCore() (Core, bool) {
|
|
for _, core := range m.Config.Browser.Cores {
|
|
if core.IsDefault {
|
|
return core, true
|
|
}
|
|
}
|
|
if len(m.Config.Browser.Cores) > 0 {
|
|
return m.Config.Browser.Cores[0], true
|
|
}
|
|
return Core{}, false
|
|
}
|
|
|
|
// ResolveCoreExecutable 解析内核可执行文件路径
|
|
func (m *Manager) ResolveCoreExecutable(core Core) (string, error) {
|
|
corePath := strings.TrimSpace(core.CorePath)
|
|
if corePath == "" {
|
|
return "", fmt.Errorf("浏览器内核路径为空,请在“内核管理”中补充内核目录")
|
|
}
|
|
|
|
baseDir := m.ResolveRelativePath(corePath)
|
|
|
|
exePath := filepath.Join(baseDir, "chrome.exe")
|
|
if _, err := os.Stat(exePath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("浏览器内核目录无效:未找到 chrome.exe(%s)。请检查内核目录是否完整或重新下载内核", exePath)
|
|
}
|
|
return "", fmt.Errorf("浏览器内核目录不可访问:%s。原因:%v", exePath, err)
|
|
}
|
|
|
|
return exePath, nil
|
|
}
|
|
|
|
// ValidateCorePath 验证内核路径是否有效
|
|
func (m *Manager) ValidateCorePath(corePath string) CoreValidateResult {
|
|
corePath = strings.TrimSpace(corePath)
|
|
if corePath == "" {
|
|
return CoreValidateResult{Valid: false, Message: "路径不能为空"}
|
|
}
|
|
|
|
baseDir := m.ResolveRelativePath(corePath)
|
|
|
|
if _, err := os.Stat(baseDir); os.IsNotExist(err) {
|
|
return CoreValidateResult{Valid: false, Message: fmt.Sprintf("目录不存在: %s", baseDir)}
|
|
}
|
|
|
|
exePath := filepath.Join(baseDir, "chrome.exe")
|
|
if _, err := os.Stat(exePath); os.IsNotExist(err) {
|
|
return CoreValidateResult{Valid: false, Message: fmt.Sprintf("chrome.exe 不存在: %s", exePath)}
|
|
}
|
|
|
|
return CoreValidateResult{Valid: true, Message: fmt.Sprintf("路径有效: %s", exePath)}
|
|
}
|
|
|
|
// ListCores 获取所有内核配置
|
|
func (m *Manager) ListCores() []Core {
|
|
if m.CoreDAO != nil {
|
|
cores, err := m.CoreDAO.List()
|
|
if err == nil {
|
|
// 同步到内存 config,供其他逻辑使用
|
|
m.Config.Browser.Cores = cores
|
|
return cores
|
|
}
|
|
}
|
|
return m.Config.Browser.Cores
|
|
}
|
|
|
|
// SaveCore 保存内核配置(新增或更新)
|
|
func (m *Manager) SaveCore(input CoreInput) error {
|
|
log := logger.New("Browser")
|
|
coreId := strings.TrimSpace(input.CoreId)
|
|
coreName := strings.TrimSpace(input.CoreName)
|
|
corePath := strings.TrimSpace(input.CorePath)
|
|
|
|
if coreName == "" {
|
|
return fmt.Errorf("内核名称不能为空")
|
|
}
|
|
if corePath == "" {
|
|
return fmt.Errorf("内核路径不能为空")
|
|
}
|
|
|
|
if m.CoreDAO != nil {
|
|
if coreId == "" {
|
|
coreId = uuid.NewString()
|
|
}
|
|
if input.IsDefault {
|
|
if err := m.CoreDAO.SetDefault(""); err != nil {
|
|
// SetDefault 空串只清除,忽略错误
|
|
_ = err
|
|
}
|
|
}
|
|
core := Core{CoreId: coreId, CoreName: coreName, CorePath: corePath, IsDefault: input.IsDefault}
|
|
if err := m.CoreDAO.Upsert(core); err != nil {
|
|
return err
|
|
}
|
|
// 同步内存
|
|
m.syncCoresFromDAO()
|
|
log.Info("内核配置保存", logger.F("core_id", coreId), logger.F("core_name", coreName))
|
|
return nil
|
|
}
|
|
|
|
// 降级:写 config.yaml
|
|
existingIndex := -1
|
|
for i, core := range m.Config.Browser.Cores {
|
|
if coreId != "" && strings.EqualFold(core.CoreId, coreId) {
|
|
existingIndex = i
|
|
break
|
|
}
|
|
}
|
|
if existingIndex >= 0 {
|
|
m.Config.Browser.Cores[existingIndex].CoreName = coreName
|
|
m.Config.Browser.Cores[existingIndex].CorePath = corePath
|
|
if input.IsDefault {
|
|
m.clearDefaultCore()
|
|
m.Config.Browser.Cores[existingIndex].IsDefault = true
|
|
}
|
|
} else {
|
|
if coreId == "" {
|
|
coreId = uuid.NewString()
|
|
}
|
|
newCore := Core{CoreId: coreId, CoreName: coreName, CorePath: corePath,
|
|
IsDefault: input.IsDefault || len(m.Config.Browser.Cores) == 0}
|
|
if newCore.IsDefault {
|
|
m.clearDefaultCore()
|
|
}
|
|
m.Config.Browser.Cores = append(m.Config.Browser.Cores, newCore)
|
|
}
|
|
log.Info("内核配置保存(文件)", logger.F("core_id", coreId))
|
|
return m.Config.Save(m.ResolveRelativePath("config.yaml"))
|
|
}
|
|
|
|
// DeleteCore 删除内核配置
|
|
func (m *Manager) DeleteCore(coreId string) error {
|
|
log := logger.New("Browser")
|
|
coreId = strings.TrimSpace(coreId)
|
|
if coreId == "" {
|
|
return fmt.Errorf("内核ID不能为空")
|
|
}
|
|
|
|
if m.CoreDAO != nil {
|
|
if err := m.CoreDAO.Delete(coreId); err != nil {
|
|
return err
|
|
}
|
|
m.syncCoresFromDAO()
|
|
log.Info("内核配置删除", logger.F("core_id", coreId))
|
|
return nil
|
|
}
|
|
|
|
// 降级
|
|
index := -1
|
|
for i, core := range m.Config.Browser.Cores {
|
|
if strings.EqualFold(core.CoreId, coreId) {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
if index < 0 {
|
|
return fmt.Errorf("内核不存在: %s", coreId)
|
|
}
|
|
wasDefault := m.Config.Browser.Cores[index].IsDefault
|
|
m.Config.Browser.Cores = append(m.Config.Browser.Cores[:index], m.Config.Browser.Cores[index+1:]...)
|
|
if wasDefault && len(m.Config.Browser.Cores) > 0 {
|
|
m.Config.Browser.Cores[0].IsDefault = true
|
|
}
|
|
log.Info("内核配置删除(文件)", logger.F("core_id", coreId))
|
|
return m.Config.Save(m.ResolveRelativePath("config.yaml"))
|
|
}
|
|
|
|
// SetDefaultCore 设置默认内核
|
|
func (m *Manager) SetDefaultCore(coreId string) error {
|
|
log := logger.New("Browser")
|
|
coreId = strings.TrimSpace(coreId)
|
|
if coreId == "" {
|
|
return fmt.Errorf("内核ID不能为空")
|
|
}
|
|
|
|
if m.CoreDAO != nil {
|
|
if err := m.CoreDAO.SetDefault(coreId); err != nil {
|
|
return err
|
|
}
|
|
m.syncCoresFromDAO()
|
|
log.Info("设置默认内核", logger.F("core_id", coreId))
|
|
return nil
|
|
}
|
|
|
|
// 降级
|
|
found := false
|
|
for i := range m.Config.Browser.Cores {
|
|
if strings.EqualFold(m.Config.Browser.Cores[i].CoreId, coreId) {
|
|
m.Config.Browser.Cores[i].IsDefault = true
|
|
found = true
|
|
} else {
|
|
m.Config.Browser.Cores[i].IsDefault = false
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("内核不存在: %s", coreId)
|
|
}
|
|
log.Info("设置默认内核(文件)", logger.F("core_id", coreId))
|
|
return m.Config.Save(m.ResolveRelativePath("config.yaml"))
|
|
}
|
|
|
|
// syncCoresFromDAO 从 DAO 同步内核列表到内存 config
|
|
func (m *Manager) syncCoresFromDAO() {
|
|
if m.CoreDAO == nil {
|
|
return
|
|
}
|
|
if cores, err := m.CoreDAO.List(); err == nil {
|
|
m.Config.Browser.Cores = cores
|
|
}
|
|
}
|
|
|
|
// clearDefaultCore 清除所有默认标记
|
|
func (m *Manager) clearDefaultCore() {
|
|
for i := range m.Config.Browser.Cores {
|
|
m.Config.Browser.Cores[i].IsDefault = false
|
|
}
|
|
}
|
|
|
|
// ResolveChromeBinary 解析 Chrome 二进制路径(简化版)
|
|
func (m *Manager) ResolveChromeBinary(profile *Profile) (string, error) {
|
|
log := logger.New("Browser")
|
|
coreId := strings.TrimSpace(profile.CoreId)
|
|
|
|
var core Core
|
|
var found bool
|
|
|
|
if coreId != "" {
|
|
core, found = m.GetCore(coreId)
|
|
}
|
|
if !found {
|
|
core, found = m.GetDefaultCore()
|
|
}
|
|
if !found {
|
|
return "", fmt.Errorf("未配置可用浏览器内核。请先在“内核管理”中添加内核并设置默认内核")
|
|
}
|
|
|
|
exePath, err := m.ResolveCoreExecutable(core)
|
|
if err != nil {
|
|
log.Error("内核路径解析失败", logger.F("core_id", core.CoreId), logger.F("error", err.Error()))
|
|
return "", err
|
|
}
|
|
|
|
log.Debug("使用内核", logger.F("core_id", core.CoreId), logger.F("path", exePath))
|
|
return exePath, nil
|
|
}
|
|
|
|
// GetChromeVersion 从 manifest.json 读取 Chrome 版本号
|
|
func (m *Manager) GetChromeVersion(corePath string) string {
|
|
corePath = strings.TrimSpace(corePath)
|
|
if corePath == "" {
|
|
return ""
|
|
}
|
|
|
|
baseDir := m.ResolveRelativePath(corePath)
|
|
|
|
// 尝试读取 manifest.json 或 *.manifest 文件
|
|
manifestPath := filepath.Join(baseDir, "manifest.json")
|
|
data, err := os.ReadFile(manifestPath)
|
|
if err != nil {
|
|
// 尝试查找 *.manifest 文件
|
|
matches, _ := filepath.Glob(filepath.Join(baseDir, "*.manifest"))
|
|
if len(matches) > 0 {
|
|
// 从文件名提取版本号,如 "142.0.7444.175.manifest"
|
|
baseName := filepath.Base(matches[0])
|
|
version := strings.TrimSuffix(baseName, ".manifest")
|
|
if version != "" {
|
|
return version
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// 解析 JSON
|
|
var manifest struct {
|
|
Version string `json:"version"`
|
|
}
|
|
if err := json.Unmarshal(data, &manifest); err != nil {
|
|
return ""
|
|
}
|
|
|
|
return manifest.Version
|
|
}
|
|
|
|
// CountInstancesByCore 统计使用指定内核的实例数量
|
|
func (m *Manager) CountInstancesByCore(coreId string) int {
|
|
coreId = strings.TrimSpace(coreId)
|
|
count := 0
|
|
for _, profile := range m.Config.Browser.Profiles {
|
|
profileCoreId := strings.TrimSpace(profile.CoreId)
|
|
// 如果实例的 CoreId 为空,则使用默认内核
|
|
if profileCoreId == "" {
|
|
defaultCore, found := m.GetDefaultCore()
|
|
if found && strings.EqualFold(defaultCore.CoreId, coreId) {
|
|
count++
|
|
}
|
|
} else if strings.EqualFold(profileCoreId, coreId) {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// GetCoresExtendedInfo 获取所有内核的扩展信息
|
|
func (m *Manager) GetCoresExtendedInfo() []CoreExtendedInfo {
|
|
cores := m.ListCores()
|
|
result := make([]CoreExtendedInfo, 0, len(cores))
|
|
for _, core := range cores {
|
|
info := CoreExtendedInfo{
|
|
CoreId: core.CoreId,
|
|
ChromeVersion: m.GetChromeVersion(core.CorePath),
|
|
InstanceCount: m.CountInstancesByCore(core.CoreId),
|
|
}
|
|
result = append(result, info)
|
|
}
|
|
return result
|
|
}
|