aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSlack Coder <slackcoder@server.ky>2025-05-16 14:37:12 -0500
committerSlack Coder <slackcoder@server.ky>2025-05-16 16:07:36 -0500
commitbde75151c4852029766ccafd091493f5686e4ab9 (patch)
tree850710e4526dacd76911eec50f1b98a0496ebf40
parentd75e17a48934e4896963fb0fee78dbd53f4e780b (diff)
downloadmirror-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.go7
-rw-r--r--internal/service/mirror.go21
-rw-r--r--internal/service/service.go98
-rw-r--r--internal/service/time.go8
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)))}
+
}