diff --git a/web/scraping.go b/web/scraping.go index fa720a8..7ea3673 100644 --- a/web/scraping.go +++ b/web/scraping.go @@ -1220,7 +1220,13 @@ func TriggerSchedule(watchID WatchID, web *Web, scheduleID *FilterID) { // getCronDebugResult parses the cron schedule string and logs any errors func getCronDebugResult(filter *Filter) { - _, err := cron.ParseStandard(filter.Var1) + scheduleSplit := strings.Split(filter.Var1, "+") + scheduleTrimmed := strings.TrimSpace(scheduleSplit[0]) + _, err := cron.ParseStandard(scheduleTrimmed) + if err != nil { + filter.Log(err) + } + _, err = getJittersFromScheduleString(filter.Var1) if err != nil { filter.Log(err) } diff --git a/web/util.go b/web/util.go index be41731..9931fea 100644 --- a/web/util.go +++ b/web/util.go @@ -2,6 +2,9 @@ package web import ( "errors" + "math/rand" + "strings" + "time" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" @@ -66,3 +69,27 @@ func buildFilterTree(filters []Filter, connections []FilterConnection) { } */ } + +func getJittersFromScheduleString(scheduleString string) ([]time.Duration, error) { + split := strings.Split(scheduleString, "+") + split = split[1:] + durations := []time.Duration{} + if len(split) == 0 { + return durations, nil + } + + rand.Seed(time.Now().UnixMilli()) + + for _, jitter := range split { + trimmed := strings.TrimSpace(jitter) + + duration, err := time.ParseDuration(trimmed) + if err != nil { + return durations, err + } + + duration = time.Duration(float64(duration.Nanoseconds()) * rand.Float64()) + durations = append(durations, duration) + } + return durations, nil +} diff --git a/web/web.go b/web/web.go index eb7c9f5..b555988 100644 --- a/web/web.go +++ b/web/web.go @@ -91,7 +91,9 @@ func (web *Web) addCronJobIfCronFilter(filter *Filter, startup bool) { if filter.Var2 != nil && *filter.Var2 == "no" { return } - entryID, err := web.cron.AddFunc(filter.Var1, func() { TriggerSchedule(filter.WatchID, web, &filter.ID) }) + scheduleSplit := strings.Split(filter.Var1, "+") + scheduleTrimmed := strings.TrimSpace(scheduleSplit[0]) + entryID, err := web.cron.AddFunc(scheduleTrimmed, func() { web.addCronJitter(filter.WatchID, filter) }) if err != nil { if startup { web.startupWarning("Could not start job for Watch: ", filter.WatchID) @@ -104,6 +106,20 @@ func (web *Web) addCronJobIfCronFilter(filter *Filter, startup bool) { web.cronWatch[filter.ID] = entryID } +func (web *Web) addCronJitter(watchID WatchID, filter *Filter) { + durations, err := getJittersFromScheduleString(filter.Var1) + if err != nil { + log.Println("Could not parse Schedule string:", filter.Var1) + return + } + + for _, duration := range durations { + log.Println("Delaying by:", duration) + time.Sleep(duration) + } + TriggerSchedule(filter.WatchID, web, &filter.ID) +} + // validateProxyURL calls url.Parse with the proxy.url, if there is an error, it's added to startupWarnings func (web *Web) validateProxyURL() { if viper.IsSet("proxy.url") {