347 lines
8.7 KiB
Go
347 lines
8.7 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/hanxuanyu/gitcodestatic/internal/cache"
|
|
"github.com/hanxuanyu/gitcodestatic/internal/git"
|
|
"github.com/hanxuanyu/gitcodestatic/internal/logger"
|
|
"github.com/hanxuanyu/gitcodestatic/internal/models"
|
|
"github.com/hanxuanyu/gitcodestatic/internal/stats"
|
|
"github.com/hanxuanyu/gitcodestatic/internal/storage"
|
|
)
|
|
|
|
// CloneHandler 克隆任务处理器
|
|
type CloneHandler struct {
|
|
store storage.Store
|
|
gitManager git.Manager
|
|
}
|
|
|
|
func NewCloneHandler(store storage.Store, gitManager git.Manager) *CloneHandler {
|
|
return &CloneHandler{
|
|
store: store,
|
|
gitManager: gitManager,
|
|
}
|
|
}
|
|
|
|
func (h *CloneHandler) Type() string {
|
|
return models.TaskTypeClone
|
|
}
|
|
|
|
func (h *CloneHandler) Timeout() time.Duration {
|
|
return 10 * time.Minute
|
|
}
|
|
|
|
func (h *CloneHandler) Handle(ctx context.Context, task *models.Task) error {
|
|
// 获取仓库信息
|
|
repo, err := h.store.Repos().GetByID(ctx, task.RepoID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get repository: %w", err)
|
|
}
|
|
|
|
// 更新仓库状态为cloning
|
|
repo.Status = models.RepoStatusCloning
|
|
h.store.Repos().Update(ctx, repo)
|
|
|
|
// 获取凭据(如果有)
|
|
var cred *models.Credential
|
|
if repo.CredentialID != nil {
|
|
cred, _ = h.store.Credentials().GetByID(ctx, *repo.CredentialID)
|
|
}
|
|
|
|
// 克隆仓库
|
|
if err := h.gitManager.Clone(ctx, repo.URL, repo.LocalPath, cred); err != nil {
|
|
errMsg := err.Error()
|
|
repo.Status = models.RepoStatusFailed
|
|
repo.ErrorMessage = &errMsg
|
|
h.store.Repos().Update(ctx, repo)
|
|
return err
|
|
}
|
|
|
|
// 获取当前分支和commit hash
|
|
branch, err := h.gitManager.GetCurrentBranch(ctx, repo.LocalPath)
|
|
if err != nil {
|
|
logger.Logger.Warn().Err(err).Msg("failed to get current branch")
|
|
branch = "main"
|
|
}
|
|
|
|
commitHash, err := h.gitManager.GetHeadCommitHash(ctx, repo.LocalPath)
|
|
if err != nil {
|
|
logger.Logger.Warn().Err(err).Msg("failed to get HEAD commit hash")
|
|
}
|
|
|
|
// 更新仓库状态为ready
|
|
now := time.Now()
|
|
repo.Status = models.RepoStatusReady
|
|
repo.CurrentBranch = branch
|
|
repo.LastCommitHash = &commitHash
|
|
repo.LastPullAt = &now
|
|
repo.ErrorMessage = nil
|
|
h.store.Repos().Update(ctx, repo)
|
|
|
|
return nil
|
|
}
|
|
|
|
// PullHandler 拉取任务处理器
|
|
type PullHandler struct {
|
|
store storage.Store
|
|
gitManager git.Manager
|
|
}
|
|
|
|
func NewPullHandler(store storage.Store, gitManager git.Manager) *PullHandler {
|
|
return &PullHandler{
|
|
store: store,
|
|
gitManager: gitManager,
|
|
}
|
|
}
|
|
|
|
func (h *PullHandler) Type() string {
|
|
return models.TaskTypePull
|
|
}
|
|
|
|
func (h *PullHandler) Timeout() time.Duration {
|
|
return 5 * time.Minute
|
|
}
|
|
|
|
func (h *PullHandler) Handle(ctx context.Context, task *models.Task) error {
|
|
repo, err := h.store.Repos().GetByID(ctx, task.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var cred *models.Credential
|
|
if repo.CredentialID != nil {
|
|
cred, _ = h.store.Credentials().GetByID(ctx, *repo.CredentialID)
|
|
}
|
|
|
|
if err := h.gitManager.Pull(ctx, repo.LocalPath, cred); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 更新commit hash
|
|
commitHash, _ := h.gitManager.GetHeadCommitHash(ctx, repo.LocalPath)
|
|
now := time.Now()
|
|
repo.LastCommitHash = &commitHash
|
|
repo.LastPullAt = &now
|
|
h.store.Repos().Update(ctx, repo)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SwitchHandler 切换分支处理器
|
|
type SwitchHandler struct {
|
|
store storage.Store
|
|
gitManager git.Manager
|
|
}
|
|
|
|
func NewSwitchHandler(store storage.Store, gitManager git.Manager) *SwitchHandler {
|
|
return &SwitchHandler{
|
|
store: store,
|
|
gitManager: gitManager,
|
|
}
|
|
}
|
|
|
|
func (h *SwitchHandler) Type() string {
|
|
return models.TaskTypeSwitch
|
|
}
|
|
|
|
func (h *SwitchHandler) Timeout() time.Duration {
|
|
return 1 * time.Minute
|
|
}
|
|
|
|
func (h *SwitchHandler) Handle(ctx context.Context, task *models.Task) error {
|
|
repo, err := h.store.Repos().GetByID(ctx, task.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var params models.TaskParameters
|
|
if err := json.Unmarshal([]byte(task.Parameters), ¶ms); err != nil {
|
|
return fmt.Errorf("failed to parse parameters: %w", err)
|
|
}
|
|
|
|
if err := h.gitManager.Checkout(ctx, repo.LocalPath, params.Branch); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 更新仓库当前分支
|
|
repo.CurrentBranch = params.Branch
|
|
commitHash, _ := h.gitManager.GetHeadCommitHash(ctx, repo.LocalPath)
|
|
repo.LastCommitHash = &commitHash
|
|
h.store.Repos().Update(ctx, repo)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResetHandler 重置仓库处理器
|
|
type ResetHandler struct {
|
|
store storage.Store
|
|
gitManager git.Manager
|
|
fileCache *cache.FileCache
|
|
}
|
|
|
|
func NewResetHandler(store storage.Store, gitManager git.Manager, fileCache *cache.FileCache) *ResetHandler {
|
|
return &ResetHandler{
|
|
store: store,
|
|
gitManager: gitManager,
|
|
fileCache: fileCache,
|
|
}
|
|
}
|
|
|
|
func (h *ResetHandler) Type() string {
|
|
return models.TaskTypeReset
|
|
}
|
|
|
|
func (h *ResetHandler) Timeout() time.Duration {
|
|
return 10 * time.Minute
|
|
}
|
|
|
|
func (h *ResetHandler) Handle(ctx context.Context, task *models.Task) error {
|
|
repo, err := h.store.Repos().GetByID(ctx, task.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 1. 删除统计缓存
|
|
h.fileCache.InvalidateByRepoID(ctx, repo.ID)
|
|
|
|
// 2. 删除本地目录
|
|
if err := os.RemoveAll(repo.LocalPath); err != nil {
|
|
logger.Logger.Warn().Err(err).Str("path", repo.LocalPath).Msg("failed to remove local path")
|
|
}
|
|
|
|
// 3. 更新仓库状态为pending
|
|
repo.Status = models.RepoStatusPending
|
|
repo.CurrentBranch = ""
|
|
repo.LastCommitHash = nil
|
|
repo.LastPullAt = nil
|
|
repo.ErrorMessage = nil
|
|
h.store.Repos().Update(ctx, repo)
|
|
|
|
// 4. 重新克隆
|
|
var cred *models.Credential
|
|
if repo.CredentialID != nil {
|
|
cred, _ = h.store.Credentials().GetByID(ctx, *repo.CredentialID)
|
|
}
|
|
|
|
repo.Status = models.RepoStatusCloning
|
|
h.store.Repos().Update(ctx, repo)
|
|
|
|
if err := h.gitManager.Clone(ctx, repo.URL, repo.LocalPath, cred); err != nil {
|
|
errMsg := err.Error()
|
|
repo.Status = models.RepoStatusFailed
|
|
repo.ErrorMessage = &errMsg
|
|
h.store.Repos().Update(ctx, repo)
|
|
return err
|
|
}
|
|
|
|
// 更新为ready
|
|
branch, _ := h.gitManager.GetCurrentBranch(ctx, repo.LocalPath)
|
|
commitHash, _ := h.gitManager.GetHeadCommitHash(ctx, repo.LocalPath)
|
|
now := time.Now()
|
|
repo.Status = models.RepoStatusReady
|
|
repo.CurrentBranch = branch
|
|
repo.LastCommitHash = &commitHash
|
|
repo.LastPullAt = &now
|
|
repo.ErrorMessage = nil
|
|
h.store.Repos().Update(ctx, repo)
|
|
|
|
return nil
|
|
}
|
|
|
|
// StatsHandler 统计任务处理器
|
|
type StatsHandler struct {
|
|
store storage.Store
|
|
calculator *stats.Calculator
|
|
fileCache *cache.FileCache
|
|
gitManager git.Manager
|
|
}
|
|
|
|
func NewStatsHandler(store storage.Store, calculator *stats.Calculator, fileCache *cache.FileCache, gitManager git.Manager) *StatsHandler {
|
|
return &StatsHandler{
|
|
store: store,
|
|
calculator: calculator,
|
|
fileCache: fileCache,
|
|
gitManager: gitManager,
|
|
}
|
|
}
|
|
|
|
func (h *StatsHandler) Type() string {
|
|
return models.TaskTypeStats
|
|
}
|
|
|
|
func (h *StatsHandler) Timeout() time.Duration {
|
|
return 30 * time.Minute
|
|
}
|
|
|
|
func (h *StatsHandler) Handle(ctx context.Context, task *models.Task) error {
|
|
repo, err := h.store.Repos().GetByID(ctx, task.RepoID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var params models.TaskParameters
|
|
if err := json.Unmarshal([]byte(task.Parameters), ¶ms); err != nil {
|
|
return fmt.Errorf("failed to parse parameters: %w", err)
|
|
}
|
|
|
|
// 获取当前HEAD commit hash
|
|
commitHash, err := h.gitManager.GetHeadCommitHash(ctx, repo.LocalPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get HEAD commit hash: %w", err)
|
|
}
|
|
|
|
// 检查缓存
|
|
cacheKey := cache.GenerateCacheKey(repo.ID, params.Branch, params.Constraint, commitHash)
|
|
cached, _ := h.fileCache.Get(ctx, cacheKey)
|
|
if cached != nil {
|
|
// 缓存命中,直接返回
|
|
logger.Logger.Info().Str("cache_key", cacheKey).Msg("cache hit during stats calculation")
|
|
|
|
result := models.TaskResult{
|
|
CacheKey: cacheKey,
|
|
Message: "cache hit",
|
|
}
|
|
resultJSON, _ := json.Marshal(result)
|
|
resultStr := string(resultJSON)
|
|
task.Result = &resultStr
|
|
h.store.Tasks().Update(ctx, task)
|
|
|
|
return nil
|
|
}
|
|
|
|
// 执行统计
|
|
statistics, err := h.calculator.Calculate(ctx, repo.LocalPath, params.Branch, params.Constraint)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to calculate statistics: %w", err)
|
|
}
|
|
|
|
// 保存到缓存
|
|
if err := h.fileCache.Set(ctx, repo.ID, params.Branch, params.Constraint, commitHash, statistics); err != nil {
|
|
logger.Logger.Warn().Err(err).Msg("failed to save statistics to cache")
|
|
}
|
|
|
|
// 更新任务结果
|
|
result := models.TaskResult{
|
|
CacheKey: cacheKey,
|
|
Message: "statistics calculated successfully",
|
|
}
|
|
resultJSON, _ := json.Marshal(result)
|
|
resultStr := string(resultJSON)
|
|
task.Result = &resultStr
|
|
h.store.Tasks().Update(ctx, task)
|
|
|
|
logger.Logger.Info().
|
|
Int64("repo_id", repo.ID).
|
|
Str("branch", params.Branch).
|
|
Int("total_commits", statistics.Summary.TotalCommits).
|
|
Int("contributors", statistics.Summary.TotalContributors).
|
|
Msg("statistics calculated")
|
|
|
|
return nil
|
|
}
|