apps/easel/scheduler.go 2.4 K raw
1
package main
2
3
import (
4
	"context"
5
	"time"
6
)
7
8
func (a *App) todayInTZ() string {
9
	return time.Now().In(a.TZ).Format("2006-01-02")
10
}
11
12
func (a *App) pastNDates(n int) []string {
13
	today := time.Now().In(a.TZ).Truncate(24 * time.Hour)
14
	out := make([]string, 0, n)
15
	for i := 1; i <= n; i++ {
16
		out = append(out, today.AddDate(0, 0, -i).Format("2006-01-02"))
17
	}
18
	return out
19
}
20
21
func parseDate(s string) (time.Time, bool) {
22
	t, err := time.Parse("2006-01-02", s)
23
	return t, err == nil
24
}
25
26
func (a *App) ensureDay(ctx context.Context, date string) error {
27
	if d, err := getDaily(a.DB, date); err != nil {
28
		return err
29
	} else if d != nil {
30
		return nil
31
	}
32
	raw, err := pickUnique(ctx, a.HTTP, a.DB, a.Classifications, a.ExcludeTerms, a.MaxDedupRetries)
33
	if err != nil {
34
		return err
35
	}
36
	now := time.Now().UTC().Format(time.RFC3339)
37
	daily := rawToDaily(raw, date, now)
38
	if daily == nil {
39
		return errMissingImage
40
	}
41
	if _, err := insertDaily(a.DB, daily); err != nil {
42
		return err
43
	}
44
	a.Log.Info("stored artwork", "artwork_id", daily.ArtworkID, "date", date, "image_id", daily.ImageID)
45
	return nil
46
}
47
48
var errMissingImage = &simpleError{"missing image_id on selected artwork"}
49
50
type simpleError struct{ msg string }
51
52
func (e *simpleError) Error() string { return e.msg }
53
54
func (a *App) runScheduler(ctx context.Context) {
55
	if err := a.ensureDay(ctx, a.todayInTZ()); err != nil {
56
		a.Log.Warn("startup ensure_day failed", "err", err)
57
	}
58
	if a.BackfillDays > 0 {
59
		dates := a.pastNDates(a.BackfillDays)
60
		missing, err := missingDates(a.DB, dates)
61
		if err != nil {
62
			a.Log.Error("backfill missing_dates failed", "err", err)
63
		} else {
64
			a.Log.Info("backfill", "missing", len(missing), "window_days", a.BackfillDays)
65
			for _, d := range missing {
66
				if err := a.ensureDay(ctx, d); err != nil {
67
					a.Log.Warn("backfill day failed", "date", d, "err", err)
68
				}
69
			}
70
		}
71
	}
72
	for {
73
		dur := a.durationUntilNextMidnight()
74
		a.Log.Info("scheduler sleeping", "seconds", dur.Seconds(), "tz", a.TZName)
75
		select {
76
		case <-ctx.Done():
77
			return
78
		case <-time.After(dur):
79
		}
80
		if err := a.ensureDay(ctx, a.todayInTZ()); err != nil {
81
			a.Log.Warn("scheduled ensure_day failed", "err", err)
82
		}
83
	}
84
}
85
86
func (a *App) durationUntilNextMidnight() time.Duration {
87
	now := time.Now().In(a.TZ)
88
	nextDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 1, 0, a.TZ).AddDate(0, 0, 1)
89
	delta := nextDay.Sub(now)
90
	if delta < time.Second {
91
		return time.Hour
92
	}
93
	return delta
94
}