完成一个简易的全局skill、command管理器
This commit is contained in:
134
manager/internal/installer/transaction.go
Normal file
134
manager/internal/installer/transaction.go
Normal file
@@ -0,0 +1,134 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user