0.1.38 gateway

This commit is contained in:
ponzischeme89
2026-08-14 09:40:03 +12:00
parent abc392d30b
commit 5e2ed3d12e
2847 changed files with 1072928 additions and 3783 deletions
+25 -48
View File
@@ -16,6 +16,7 @@ import (
"syscall"
"time"
"github.com/ponzischeme89/memby/server/internal/adminevents"
"github.com/ponzischeme89/memby/server/internal/api"
"github.com/ponzischeme89/memby/server/internal/bazarr"
"github.com/ponzischeme89/memby/server/internal/buildinfo"
@@ -23,11 +24,13 @@ import (
"github.com/ponzischeme89/memby/server/internal/config"
"github.com/ponzischeme89/memby/server/internal/emby"
"github.com/ponzischeme89/memby/server/internal/foryou"
"github.com/ponzischeme89/memby/server/internal/integrations"
"github.com/ponzischeme89/memby/server/internal/library"
"github.com/ponzischeme89/memby/server/internal/logging"
"github.com/ponzischeme89/memby/server/internal/mdblist"
"github.com/ponzischeme89/memby/server/internal/radarr"
"github.com/ponzischeme89/memby/server/internal/recommend"
"github.com/ponzischeme89/memby/server/internal/scheduler"
"github.com/ponzischeme89/memby/server/internal/sonarr"
"github.com/ponzischeme89/memby/server/internal/store"
"github.com/ponzischeme89/memby/server/internal/tracearr"
@@ -172,6 +175,16 @@ func run(log *slog.Logger, events *logging.Buffer) error {
})
}
// The administrative event bus, the integration dispatcher that subscribes to it and
// the scheduler that publishes into it are built before the server, because the server
// takes all three: a handler that could not publish would have to check for nil at
// every call site, which is exactly how an event comes to be silently dropped.
adminBus := adminevents.New(st, log)
dispatcher := integrations.New(st, log, adminBus)
adminBus.AddSink(dispatcher)
dispatcher.Start(ctx)
sched := scheduler.New(st, log, adminBus)
server := api.New(cfg, api.Deps{
Emby: embyClient,
Store: st,
@@ -185,8 +198,17 @@ func run(log *slog.Logger, events *logging.Buffer) error {
Syncer: syncer,
Log: log,
Events: events,
AdminEvents: adminBus,
Scheduler: sched,
Integrations: dispatcher,
})
// Registration is separate from construction so the task list reads as a declaration
// of what the gateway does in the background rather than as more wiring in here.
server.RegisterHousekeeping(sched)
sched.Start(ctx)
// Installed after the server exists, because both halves of a finished import are
// its business: derived data has to be invalidated, and the TVs told the catalogue
// moved. Kept off the syncer itself so library stays ignorant of the API.
@@ -251,8 +273,6 @@ func run(log *slog.Logger, events *logging.Buffer) error {
}
}()
}
go pruneAnalytics(ctx, st, cfg.AnalyticsRetention, log)
httpServer := &http.Server{
Addr: cfg.ListenAddr,
Handler: server.Routes(),
@@ -261,8 +281,6 @@ func run(log *slog.Logger, events *logging.Buffer) error {
IdleTimeout: 60 * time.Second,
}
go sweepIdleSessions(ctx, st, cfg.SessionIdleExpiry, log)
errCh := make(chan error, 1)
go func() {
log.Info("gateway ready",
@@ -271,6 +289,9 @@ func run(log *slog.Logger, events *logging.Buffer) error {
"protocol", api.ProtocolVersion,
"sync_every", cfg.SyncInterval,
)
// Announced here rather than beside the log line above, so the feed cannot record
// a start that then failed to bind to its port.
server.AnnounceServerStart(buildinfo.Version())
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- err
}
@@ -328,48 +349,4 @@ func openStore(ctx context.Context, databaseURL string, log *slog.Logger) (*stor
return nil, lastErr
}
// pruneAnalytics keeps raw engagement and journey events inside their retention window.
func pruneAnalytics(ctx context.Context, st *store.Store, retention time.Duration, log *slog.Logger) {
if retention <= 0 {
return
}
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
removed, err := st.PruneRowEvents(ctx, retention)
if err != nil {
log.Warn("analytics prune failed", "error", err)
continue
}
if removed > 0 {
log.Info("pruned row events", "count", removed)
}
}
}
}
// sweepIdleSessions retires gateway tokens that have not been used in a long time, so a
// TV that was factory-reset does not leave a live Emby token in the database forever.
func sweepIdleSessions(ctx context.Context, st *store.Store, idle time.Duration, log *slog.Logger) {
ticker := time.NewTicker(6 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
removed, err := st.DeleteIdleSessions(ctx, idle)
if err != nil {
log.Warn("session sweep failed", "error", err)
continue
}
if removed > 0 {
log.Info("retired idle sessions", "count", removed)
}
}
}
}