174 lines
4.3 KiB
Go
174 lines
4.3 KiB
Go
package platform
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
|
||
"github.com/user/devpack/internal/logging"
|
||
)
|
||
|
||
// LinuxPlatform Linux 平台实现
|
||
type LinuxPlatform struct {
|
||
homeDir string
|
||
}
|
||
|
||
// NewLinuxPlatform 创建 Linux 平台实例
|
||
func NewLinuxPlatform() *LinuxPlatform {
|
||
home, _ := os.UserHomeDir()
|
||
return &LinuxPlatform{homeDir: home}
|
||
}
|
||
|
||
func (p *LinuxPlatform) OS() string { return "linux" }
|
||
func (p *LinuxPlatform) Arch() string { return "amd64" } // TODO: 正确检测架构
|
||
func (p *LinuxPlatform) HomeDir() string { return p.homeDir }
|
||
|
||
func (p *LinuxPlatform) ConfigDir() string {
|
||
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
|
||
return dir
|
||
}
|
||
return p.homeDir + "/.config"
|
||
}
|
||
|
||
func (p *LinuxPlatform) DataDir() string {
|
||
if dir := os.Getenv("XDG_DATA_HOME"); dir != "" {
|
||
return dir
|
||
}
|
||
return p.homeDir + "/.local/share"
|
||
}
|
||
|
||
func (p *LinuxPlatform) GetEnvVar(key string) string {
|
||
return os.Getenv(key)
|
||
}
|
||
|
||
func (p *LinuxPlatform) SetEnvVar(ctx context.Context, key, value string) error {
|
||
// TODO: 写入 Shell 配置文件以持久化
|
||
return os.Setenv(key, value)
|
||
}
|
||
|
||
func (p *LinuxPlatform) AddToPath(ctx context.Context, dir string) error {
|
||
// TODO: 添加到 Shell 配置文件
|
||
return nil
|
||
}
|
||
|
||
func (p *LinuxPlatform) IsAdmin() bool {
|
||
return os.Geteuid() == 0
|
||
}
|
||
|
||
func (p *LinuxPlatform) PackageManagers() []string {
|
||
var pms []string
|
||
if _, err := exec.LookPath("apt"); err == nil {
|
||
pms = append(pms, "apt")
|
||
}
|
||
if _, err := exec.LookPath("dnf"); err == nil {
|
||
pms = append(pms, "dnf")
|
||
}
|
||
if _, err := exec.LookPath("yum"); err == nil {
|
||
pms = append(pms, "yum")
|
||
}
|
||
if _, err := exec.LookPath("pacman"); err == nil {
|
||
pms = append(pms, "pacman")
|
||
}
|
||
if _, err := exec.LookPath("snap"); err == nil {
|
||
pms = append(pms, "snap")
|
||
}
|
||
return pms
|
||
}
|
||
|
||
func (p *LinuxPlatform) DefaultShell() string {
|
||
shell := os.Getenv("SHELL")
|
||
if shell != "" {
|
||
return shell
|
||
}
|
||
return "/bin/bash"
|
||
}
|
||
|
||
// InstallFont 安装字体到用户级字体目录并刷新字体缓存
|
||
// 支持 .ttf (TrueType) 和 .otf (OpenType) 格式
|
||
func (p *LinuxPlatform) InstallFont(ctx context.Context, fontPath string) error {
|
||
log := logging.G()
|
||
|
||
// 验证源字体文件是否存在且可访问
|
||
info, err := os.Stat(fontPath)
|
||
if err != nil {
|
||
log.Errorf("无法访问字体文件: %v", err)
|
||
return fmt.Errorf("无法访问字体文件: %w", err)
|
||
}
|
||
if info.IsDir() {
|
||
return fmt.Errorf("字体路径是目录而非文件: %s", fontPath)
|
||
}
|
||
|
||
// 检查字体格式是否受支持
|
||
fontName := filepath.Base(fontPath)
|
||
ext := filepath.Ext(fontName)
|
||
if ext != ".ttf" && ext != ".otf" {
|
||
log.Errorf("不支持的字体格式: %s", ext)
|
||
return fmt.Errorf("不支持的字体格式: %s,仅支持 .ttf 和 .otf", ext)
|
||
}
|
||
|
||
// 用户级字体目录: ~/.local/share/fonts/
|
||
destDir := filepath.Join(p.homeDir, ".local", "share", "fonts")
|
||
log.Debugf("正在安装字体: %s 到 %s", fontPath, destDir)
|
||
|
||
// 确保目标目录存在
|
||
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||
return fmt.Errorf("创建字体目录失败: %w", err)
|
||
}
|
||
|
||
// 检查字体是否已安装
|
||
destPath := filepath.Join(destDir, fontName)
|
||
if _, err := os.Stat(destPath); err == nil {
|
||
log.Infof("字体已存在,跳过安装: %s", destPath)
|
||
return nil
|
||
}
|
||
|
||
// 复制字体文件到目标路径
|
||
if err := linuxCopyFile(fontPath, destPath); err != nil {
|
||
return fmt.Errorf("复制字体文件失败: %w", err)
|
||
}
|
||
|
||
// 刷新字体缓存
|
||
if _, err := exec.LookPath("fc-cache"); err == nil {
|
||
cmd := exec.CommandContext(ctx, "fc-cache", "-f", destDir)
|
||
if out, err := cmd.CombinedOutput(); err != nil {
|
||
log.Warnf("刷新字体缓存失败: %s, %v", string(out), err)
|
||
// 字体文件已复制成功,缓存刷新失败不是致命错误
|
||
}
|
||
} else {
|
||
log.Warnf("未找到 fc-cache 命令,字体缓存未刷新,可能需要重新登录后生效")
|
||
}
|
||
|
||
log.Infof("字体安装成功: %s", fontName)
|
||
return nil
|
||
}
|
||
|
||
// linuxCopyFile 复制文件,失败时自动清理目标文件
|
||
func linuxCopyFile(src, dst string) error {
|
||
input, err := os.Open(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer input.Close()
|
||
|
||
output, err := os.Create(dst)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
_, err = io.Copy(output, input)
|
||
closeErr := output.Close()
|
||
|
||
if err != nil {
|
||
os.Remove(dst)
|
||
return err
|
||
}
|
||
if closeErr != nil {
|
||
os.Remove(dst)
|
||
return closeErr
|
||
}
|
||
return nil
|
||
}
|