🏡 index : quine-global-labs/nugitea.git

package mirror

import (
	"context"
	"log"
	"sync"
	"time"

	"nugitea/internal/repo"
)

// tickInterval is how often the scheduler checks for due mirrors.
const tickInterval = 30 * time.Second

// Scheduler periodically syncs configured mirrors whose interval has elapsed.
type Scheduler struct {
	Repos   *repo.Store
	Mirrors *Store

	locksMu sync.Mutex
	locks   map[string]*sync.Mutex // per-repo+direction, prevents overlapping syncs
}

// NewScheduler returns a Scheduler for the given repo and mirror stores.
func NewScheduler(repos *repo.Store, mirrors *Store) *Scheduler {
	return &Scheduler{Repos: repos, Mirrors: mirrors, locks: map[string]*sync.Mutex{}}
}

// Run blocks, ticking until ctx is cancelled.
func (s *Scheduler) Run(ctx context.Context) {
	ticker := time.NewTicker(tickInterval)
	defer ticker.Stop()
	for {
		s.tick(ctx)
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
		}
	}
}

func (s *Scheduler) tick(ctx context.Context) {
	entries, err := s.Mirrors.All()
	if err != nil {
		log.Printf("mirror scheduler: load mirrors: %v", err)
		return
	}
	now := time.Now()
	for _, e := range entries {
		due := e.LastSync.Add(time.Duration(e.IntervalSeconds) * time.Second)
		if now.Before(due) {
			continue
		}
		go s.sync(ctx, e)
	}
}

func (s *Scheduler) sync(ctx context.Context, e Entry) {
	lock := s.lockFor(e)
	if !lock.TryLock() {
		return // a sync for this mirror is already running
	}
	defer lock.Unlock()

	var err error
	switch e.Direction {
	case Pull:
		err = SyncPull(ctx, s.Repos, e)
	case Push:
		err = SyncPush(ctx, s.Repos, e)
	}
	if err != nil {
		log.Printf("mirror sync %s %s: %v", e.Direction, e.Repo, err)
	}
	if touchErr := s.Mirrors.touch(e, time.Now()); touchErr != nil {
		log.Printf("mirror sync %s %s: record last sync: %v", e.Direction, e.Repo, touchErr)
	}
}

func (s *Scheduler) lockFor(e Entry) *sync.Mutex {
	s.locksMu.Lock()
	defer s.locksMu.Unlock()
	key := e.key()
	if s.locks[key] == nil {
		s.locks[key] = &sync.Mutex{}
	}
	return s.locks[key]
}