about summary refs log tree commit diff stats
path: root/web/searchix.go
diff options
context:
space:
mode:
Diffstat (limited to 'web/searchix.go')
-rw-r--r--web/searchix.go156
1 files changed, 156 insertions, 0 deletions
diff --git a/web/searchix.go b/web/searchix.go
new file mode 100644
index 0000000..b410d8f
--- /dev/null
+++ b/web/searchix.go
@@ -0,0 +1,156 @@
+package web
+
+import (
+	"context"
+	"math"
+	"sync"
+	"time"
+
+	"go.alanpearce.eu/searchix/internal/config"
+	"go.alanpearce.eu/searchix/internal/importer"
+	"go.alanpearce.eu/searchix/internal/index"
+	"go.alanpearce.eu/searchix/internal/server"
+	"go.alanpearce.eu/x/log"
+
+	"github.com/getsentry/sentry-go"
+	"gitlab.com/tozd/go/errors"
+)
+
+func nextUTCOccurrenceOfTime(dayTime config.LocalTime) time.Time {
+	now := time.Now()
+	nextRun := time.Date(
+		now.Year(),
+		now.Month(),
+		now.Day(),
+		dayTime.Hour,
+		dayTime.Minute,
+		dayTime.Second,
+		0,
+		time.UTC,
+	)
+	if nextRun.Before(now) {
+		return nextRun.AddDate(0, 0, 1)
+	}
+
+	return nextRun
+}
+
+type Server struct {
+	sv         *server.Server
+	wg         *sync.WaitGroup
+	cfg        *config.Config
+	log        *log.Logger
+	sentryHub  *sentry.Hub
+	readIndex  *index.ReadIndex
+	writeIndex *index.WriteIndex
+}
+
+func New(cfg *config.Config, log *log.Logger) (*Server, errors.E) {
+	err := sentry.Init(sentry.ClientOptions{
+		EnableTracing:    true,
+		TracesSampleRate: 1.0,
+		Dsn:              cfg.Web.SentryDSN,
+		Environment:      cfg.Web.Environment,
+	})
+	if err != nil {
+		log.Warn("could not initialise sentry", "error", err)
+	}
+
+	return &Server{
+		cfg:       cfg,
+		log:       log,
+		sentryHub: sentry.CurrentHub(),
+	}, nil
+}
+
+func (s *Server) startUpdateTimer(
+	ctx context.Context,
+	cfg *config.Config,
+	localHub *sentry.Hub,
+) {
+	const monitorSlug = "import"
+	localHub.WithScope(func(scope *sentry.Scope) {
+		var err errors.E
+		scope.SetContext("monitor", sentry.Context{"slug": monitorSlug})
+		monitorConfig := &sentry.MonitorConfig{
+			Schedule:      sentry.IntervalSchedule(1, sentry.MonitorScheduleUnitDay),
+			MaxRuntime:    int64(math.Ceil(cfg.Importer.Timeout.Minutes())),
+			CheckInMargin: 5,
+			Timezone:      time.Local.String(),
+		}
+
+		s.wg.Add(1)
+		nextRun := nextUTCOccurrenceOfTime(s.cfg.Importer.UpdateAt)
+		importer.SetNextRun(nextRun)
+		for {
+			s.log.Debug("scheduling next run", "next-run", nextRun)
+			select {
+			case <-ctx.Done():
+				s.log.Debug("stopping scheduler")
+				s.wg.Done()
+
+				return
+			case <-time.After(time.Until(nextRun)):
+			}
+			s.wg.Add(1)
+			s.log.Info("updating index")
+
+			eventID := localHub.CaptureCheckIn(&sentry.CheckIn{
+				MonitorSlug: monitorSlug,
+				Status:      sentry.CheckInStatusInProgress,
+			}, monitorConfig)
+			importer.MarkIndexingStarted()
+
+			imp := importer.New(s.cfg, s.log.Named("importer"), s.writeIndex)
+			err = imp.Start(ctx, false, nil)
+			s.wg.Done()
+			if err != nil {
+				s.log.Warn("error updating index", "error", err)
+
+				localHub.CaptureException(err)
+				localHub.CaptureCheckIn(&sentry.CheckIn{
+					ID:          *eventID,
+					MonitorSlug: monitorSlug,
+					Status:      sentry.CheckInStatusError,
+				}, monitorConfig)
+			} else {
+				s.log.Info("update complete")
+
+				localHub.CaptureCheckIn(&sentry.CheckIn{
+					ID:          *eventID,
+					MonitorSlug: monitorSlug,
+					Status:      sentry.CheckInStatusOK,
+				}, monitorConfig)
+			}
+			nextRun = nextRun.AddDate(0, 0, 1)
+			importer.MarkIndexingFinished(nextRun)
+		}
+	})
+}
+
+func (s *Server) Start(ctx context.Context, liveReload bool) errors.E {
+	var err errors.E
+	s.sv, err = server.New(s.cfg, s.readIndex, s.log.Named("server"), liveReload)
+	if err != nil {
+		return errors.Wrap(err, "error setting up server")
+	}
+
+	s.wg = &sync.WaitGroup{}
+	go s.startUpdateTimer(ctx, s.cfg, sentry.CurrentHub().Clone())
+
+	s.wg.Add(1)
+	err = s.sv.Start()
+	if err != nil {
+		s.wg.Done()
+
+		return errors.Wrap(err, "error starting server")
+	}
+
+	return nil
+}
+
+func (s *Server) Stop() {
+	<-s.sv.Stop()
+	defer s.wg.Done()
+	s.sentryHub.Flush(2 * time.Second)
+}