1
0
Files
Skill/manager/internal/installer/transaction.go

135 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package installer
import (
"fmt"
"os"
"path/filepath"
"skillmgr/pkg/fileutil"
)
// Transaction 事务性安装
type Transaction struct {
stagingDir string
targetDir string
fileMap map[string]string // source → dest
}
// NewTransaction 创建事务
// 在系统临时目录创建 staging 目录
func NewTransaction(targetDir string, fileMap map[string]string) (*Transaction, error) {
// 在系统临时目录创建 staging 目录
stagingDir, err := os.MkdirTemp("", "skillmgr-*")
if err != nil {
return nil, fmt.Errorf("创建 staging 目录失败: %w", err)
}
return &Transaction{
stagingDir: stagingDir,
targetDir: targetDir,
fileMap: fileMap,
}, nil
}
// Stage 阶段:复制文件到 staging 目录
func (t *Transaction) Stage() error {
stagedCount := 0
for src, dest := range t.fileMap {
// 计算相对于 targetDir 的路径
relPath, err := filepath.Rel(t.targetDir, dest)
if err != nil {
return fmt.Errorf("计算相对路径失败: %w", err)
}
stagingDest := filepath.Join(t.stagingDir, relPath)
// 确保目标目录存在
if err := os.MkdirAll(filepath.Dir(stagingDest), 0755); err != nil {
return fmt.Errorf("创建 staging 子目录失败: %w", err)
}
// 复制文件
if err := fileutil.CopyFile(src, stagingDest); err != nil {
return fmt.Errorf("复制文件到 staging 失败: %w", err)
}
stagedCount++
}
// 验证 staging 完整性:检查文件数量是否与预期一致
if err := t.verifyStagingIntegrity(stagedCount); err != nil {
return fmt.Errorf("staging 验证失败: %w", err)
}
return nil
}
// verifyStagingIntegrity 验证 staging 目录中的文件数量
func (t *Transaction) verifyStagingIntegrity(expectedCount int) error {
actualCount := 0
err := filepath.Walk(t.stagingDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
actualCount++
}
return nil
})
if err != nil {
return fmt.Errorf("遍历 staging 目录失败: %w", err)
}
if actualCount != expectedCount {
return fmt.Errorf("文件数量不匹配: 预期 %d 个文件,实际 %d 个", expectedCount, actualCount)
}
return nil
}
// Commit 提交:将 staging 目录移动到目标位置
func (t *Transaction) Commit() error {
// 确保目标目录的父目录存在
if err := os.MkdirAll(filepath.Dir(t.targetDir), 0755); err != nil {
return fmt.Errorf("创建目标父目录失败: %w", err)
}
// 如果目标目录已存在,先删除(已经过用户确认)
if _, err := os.Stat(t.targetDir); err == nil {
if err := os.RemoveAll(t.targetDir); err != nil {
return fmt.Errorf("删除已存在的目标目录失败: %w", err)
}
}
// 尝试原子性移动 staging 目录到目标位置
if err := os.Rename(t.stagingDir, t.targetDir); err != nil {
// 如果跨文件系统Rename 会失败,改用复制
// 使用 defer 确保 staging 目录被清理
defer os.RemoveAll(t.stagingDir)
if err := fileutil.CopyDir(t.stagingDir, t.targetDir); err != nil {
return fmt.Errorf("复制 staging 到目标失败: %w", err)
}
}
return nil
}
// Rollback 回滚:清理 staging 目录
func (t *Transaction) Rollback() {
if t.stagingDir != "" {
os.RemoveAll(t.stagingDir)
}
}
// StagingDir 获取 staging 目录路径
func (t *Transaction) StagingDir() string {
return t.stagingDir
}
// TargetDir 获取目标目录路径
func (t *Transaction) TargetDir() string {
return t.targetDir
}