diff options
author | Slack Coder <slackcoder@server.ky> | 2025-05-16 14:37:12 -0500 |
---|---|---|
committer | Slack Coder <slackcoder@server.ky> | 2025-05-16 16:07:36 -0500 |
commit | bde75151c4852029766ccafd091493f5686e4ab9 (patch) | |
tree | 850710e4526dacd76911eec50f1b98a0496ebf40 | |
parent | d75e17a48934e4896963fb0fee78dbd53f4e780b (diff) | |
download | mirror-bde75151c4852029766ccafd091493f5686e4ab9.tar.xz |
Enable immediate once only mirroring
Interpret Min/Max intervals of zero to mean the mirror should by pulled
immediately and only once.
Allow the user to be specific by adding per-mirror configuration for
MinInterval and MaxInterval.
-rw-r--r-- | internal/service/config.go | 7 | ||||
-rw-r--r-- | internal/service/mirror.go | 21 | ||||
-rw-r--r-- | internal/service/service.go | 98 | ||||
-rw-r--r-- | internal/service/time.go | 8 |
4 files changed, 95 insertions, 39 deletions
diff --git a/internal/service/config.go b/internal/service/config.go index 2a8da6e..d4faeb0 100644 --- a/internal/service/config.go +++ b/internal/service/config.go @@ -20,7 +20,12 @@ const ( // Global parameters type GlobalConfig struct { - MaxInterval *Duration `toml:"max-interval"` + // MaxInterval is the maximum time to wait before syncing with a + // mirror. The wait time is a randomly generated time between the Min + // and Max intervals. The mirror is immediately downloaded once if both + // the Min and Max intervals are set to zero. + MaxInterval *Duration `toml:"max-interval"` + // MinInterval is the minimum time to wait before syncing with a mirror. MinInterval *Duration `toml:"min-interval"` StagingMethod string `toml:"staging-method,omitempty"` StagingPath string `toml:"staging-path,omitempty"` diff --git a/internal/service/mirror.go b/internal/service/mirror.go index 43dd059..b399e9f 100644 --- a/internal/service/mirror.go +++ b/internal/service/mirror.go @@ -8,13 +8,20 @@ import ( ) type Mirror struct { - Method string `toml:"method,omitempty"` - From *internal.URL `toml:"from,omitempty"` - To *internal.URL `toml:"to,omitempty"` - Description string `toml:"description,omitempty"` - StagingMethod string `toml:"staging-method,omitempty"` - StagingPath string `toml:"staging-path,omitempty"` - Verify string `toml:"verify,omitempty"` + Method string `toml:"method,omitempty"` + From *internal.URL `toml:"from,omitempty"` + To *internal.URL `toml:"to,omitempty"` + Description string `toml:"description,omitempty"` + // See global configuration. + MaxInterval *Duration `toml:"max-interval"` + // See global configuration. + MinInterval *Duration `toml:"min-interval"` + // See global configuration. + StagingMethod string `toml:"staging-method,omitempty"` + // See global configuration. + StagingPath string `toml:"staging-path,omitempty"` + // See global configuration. + Verify string `toml:"verify,omitempty"` } func (m *Mirror) Equal(arg *Mirror) bool { diff --git a/internal/service/service.go b/internal/service/service.go index 577a4b4..12f79dc 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -81,10 +81,9 @@ func (s *Service) AddMirror(arg *Mirror) error { } } - record := &mirrorRecord{ - Mirror: arg, - NextRun: time.Now().Add(RandomDuration(*s.cfg.MinInterval, *s.cfg.MaxInterval).Duration), - } + record := &mirrorRecord{Mirror: arg} + scheduleNextRun(s.cfg, record) + s.mirrors = append(s.mirrors, record) return nil @@ -96,7 +95,7 @@ func (s *Service) scheduled(yield func(*mirrorRecord) bool) { now := time.Now() for _, m := range s.mirrors { - if m.NextRun.After(now) { + if m.NextRun.IsZero() || m.NextRun.After(now) { continue } @@ -230,6 +229,37 @@ func (s *Service) RemoveMirror(arg *Mirror) error { return nil } +func scheduleNextRun(cfg *Config, m *mirrorRecord) { + if !m.NextRun.IsZero() { + return + } + + // Default to it not existing, leaving error handling for others. + toPathExists := false + _, err := os.Stat(m.To.Path) + if err == nil { + toPathExists = true + } + + minInterval := *cfg.MinInterval + if m.MinInterval != nil { + minInterval = *m.MinInterval + } + maxInterval := *cfg.MaxInterval + if m.MaxInterval != nil { + maxInterval = *m.MaxInterval + } + + if minInterval.Duration > 0 || maxInterval.Duration > 0 || !toPathExists { + lastRun := m.LastRun + if lastRun.IsZero() { + lastRun = time.Now() + } + + m.NextRun = lastRun.Add(RandomDuration(minInterval, maxInterval).Duration) + } +} + // Reload reloads the service with the given configuration. func (s *Service) Reload(arg *Config) { s.mirrorsLock.Lock() @@ -250,43 +280,47 @@ func (s *Service) Reload(arg *Config) { mirrors := make([]*mirrorRecord, 0) for _, m := range cfg.Mirrors { + record := &mirrorRecord{Mirror: m} + oldM, ok := mirrorsByDest[m.To.String()] - if !ok { - mirrors = append( - mirrors, - &mirrorRecord{ - Mirror: m, - LastRun: time.Time{}, - NextRun: time.Now().Add(RandomDuration(*s.cfg.MinInterval, *s.cfg.MaxInterval).Duration), - }, - ) + if ok { + delete(mirrorsByDest, m.To.String()) - continue - } + record.LastRun = oldM.LastRun + record.NextRun = oldM.NextRun - delete(mirrorsByDest, m.To.String()) + // The run times may be out of sync with the new + // configuration. + lastRun := oldM.LastRun + if lastRun.IsZero() { + lastRun = time.Now() + } - lastRun := oldM.LastRun - if lastRun.IsZero() { - lastRun = time.Now() - } + minInterval := *cfg.MinInterval + if m.MinInterval != nil { + minInterval = *m.MinInterval + } + maxInterval := *cfg.MaxInterval + if m.MaxInterval != nil { + maxInterval = *m.MaxInterval + } - untilNextRun := Duration{oldM.NextRun.Sub(lastRun)} - untilNextRun = cfg.MinInterval.Max(&untilNextRun) - untilNextRun = cfg.MaxInterval.Min(&untilNextRun) + waitTime := oldM.NextRun.Sub(lastRun) + if waitTime < minInterval.Duration || waitTime > maxInterval.Duration { + record.NextRun = time.Time{} + } - // Records match up. - record := mirrorRecord{ - Mirror: m, - LastRun: oldM.LastRun, - NextRun: time.Now().Add(untilNextRun.Duration), } + + scheduleNextRun(s.cfg, record) + mirrors = append( mirrors, - &record, + record, ) } + // Swap out the old with the new. s.mirrors = mirrors } @@ -315,7 +349,9 @@ mainLoop: } m.LastRun = time.Now() - m.NextRun = time.Now().Add(RandomDuration(*s.cfg.MinInterval, *s.cfg.MaxInterval).Duration) + m.NextRun = time.Time{} + + scheduleNextRun(s.cfg, m) return true }) diff --git a/internal/service/time.go b/internal/service/time.go index b275b82..117b868 100644 --- a/internal/service/time.go +++ b/internal/service/time.go @@ -53,7 +53,15 @@ func (s *Duration) UnmarshalText(text []byte) error { return nil } +// Randomly generate a duration between from and until. +// +// Returns from if its greater than until. func RandomDuration(from Duration, until Duration) Duration { + if from.Duration >= until.Duration { + return from + } + period := until.Duration - from.Duration return Duration{from.Duration + time.Duration(rand.Intn(int(period)))} + } |