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 }