package updater import ( "context" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "scout/pkg/scoutpkg" ) var ErrPlatformInstallRequired = errors.New("当前平台需要宿主应用接入系统安装流程") // LaunchRequest 描述平台适配器启动已确认版本所需的信息。 type LaunchRequest struct { Release ReleasePointer Args []string Env []string } // PlatformAdapter 隔离更新器与不同操作系统的启动和安装机制。 // Prepare、Activate 和健康确认仍由 Manager 统一负责,宿主应用只需在需要时注入适配器。 type PlatformAdapter interface { Platform() string Validate(ReleasePointer) error Install(context.Context, ReleasePointer) error Launch(context.Context, LaunchRequest) (*exec.Cmd, error) } type nativeAdapter struct { platform string } type androidAdapter struct{} // NewPlatformAdapter 返回指定平台的默认适配器。 func NewPlatformAdapter(platform string) (PlatformAdapter, error) { normalized, err := scoutpkg.NormalizePlatform(platform) if err != nil { return nil, err } if normalized == "android" { return androidAdapter{}, nil } return nativeAdapter{platform: normalized}, nil } // NewNativeAdapter 创建桌面平台默认适配器,便于宿主应用显式注入。 func NewNativeAdapter(platform string) (PlatformAdapter, error) { normalized, err := scoutpkg.NormalizePlatform(platform) if err != nil { return nil, err } if normalized == "android" { return nil, errors.New("Android 必须使用宿主应用提供的安装适配器") } return nativeAdapter{platform: normalized}, nil } func (adapter nativeAdapter) Platform() string { return adapter.platform } func (adapter nativeAdapter) Validate(release ReleasePointer) error { if release.Platform != "" && release.Platform != adapter.platform && release.Platform != "any" { return fmt.Errorf("更新版本平台不匹配: %s", release.Platform) } executable, err := releaseExecutable(release) if err != nil { return err } info, err := os.Stat(executable) if err != nil { return fmt.Errorf("平台适配器检查程序入口失败: %w", err) } if info.IsDir() { return errors.New("程序入口不能是目录") } if adapter.platform == "macos" && strings.HasSuffix(strings.ToLower(executable), ".app") { return errors.New("macOS 程序入口必须指向应用包内的可执行文件") } return nil } func (adapter nativeAdapter) Install(_ context.Context, release ReleasePointer) error { return adapter.Validate(release) } func (adapter nativeAdapter) Launch(ctx context.Context, request LaunchRequest) (*exec.Cmd, error) { if err := adapter.Validate(request.Release); err != nil { return nil, err } executable, err := releaseExecutable(request.Release) if err != nil { return nil, err } command := exec.CommandContext(ctx, executable, request.Args...) command.Dir = request.Release.Path command.Env = request.Env if err := command.Start(); err != nil { return nil, fmt.Errorf("启动当前版本失败: %w", err) } return command, nil } func (androidAdapter) Platform() string { return "android" } func (androidAdapter) Validate(release ReleasePointer) error { if strings.TrimSpace(release.Path) == "" { return errors.New("Android 更新版本路径不能为空") } return nil } func (adapter androidAdapter) Install(_ context.Context, release ReleasePointer) error { if err := adapter.Validate(release); err != nil { return err } return ErrPlatformInstallRequired } func (androidAdapter) Launch(_ context.Context, _ LaunchRequest) (*exec.Cmd, error) { return nil, errors.New("Android 不能由 Go 更新器直接启动,请由宿主应用通过系统组件启动") } func releaseExecutable(release ReleasePointer) (string, error) { if strings.TrimSpace(release.Path) == "" { return "", errors.New("版本路径不能为空") } if strings.TrimSpace(release.Entrypoint) == "" { return "", errors.New("程序入口不能为空") } root, err := filepath.Abs(release.Path) if err != nil { return "", fmt.Errorf("解析版本路径失败: %w", err) } return safeEntrypoint(root, release.Entrypoint) }