feat(server/m7-352): cron loop + main.go wiring for system playlist build
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
|||||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrconfig"
|
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrconfig"
|
||||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrrequests"
|
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarrrequests"
|
||||||
"git.fabledsword.com/bvandeusen/minstrel/internal/logging"
|
"git.fabledsword.com/bvandeusen/minstrel/internal/logging"
|
||||||
|
"git.fabledsword.com/bvandeusen/minstrel/internal/playlists"
|
||||||
"git.fabledsword.com/bvandeusen/minstrel/internal/scrobble"
|
"git.fabledsword.com/bvandeusen/minstrel/internal/scrobble"
|
||||||
"git.fabledsword.com/bvandeusen/minstrel/internal/scrobble/listenbrainz"
|
"git.fabledsword.com/bvandeusen/minstrel/internal/scrobble/listenbrainz"
|
||||||
"git.fabledsword.com/bvandeusen/minstrel/internal/server"
|
"git.fabledsword.com/bvandeusen/minstrel/internal/server"
|
||||||
@@ -110,6 +111,7 @@ func run() error {
|
|||||||
srv := server.New(logger, pool, scanner, subsonic.Config{
|
srv := server.New(logger, pool, scanner, subsonic.Config{
|
||||||
AllowPlaintextPassword: cfg.Subsonic.AllowPlaintextPassword,
|
AllowPlaintextPassword: cfg.Subsonic.AllowPlaintextPassword,
|
||||||
}, cfg.Events, cfg.Recommendation, cfg.Storage.DataDir, cfg.Branding)
|
}, cfg.Events, cfg.Recommendation, cfg.Storage.DataDir, cfg.Branding)
|
||||||
|
playlists.StartSystemPlaylistCron(ctx, pool, logger.With("component", "system_playlist_cron"))
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: cfg.Server.Address,
|
Addr: cfg.Server.Address,
|
||||||
Handler: srv.Router(),
|
Handler: srv.Router(),
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package playlists
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
|
||||||
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartSystemPlaylistCron launches the in-process daily build loop in a
|
||||||
|
// goroutine. Caller passes a context that's cancelled on graceful shutdown.
|
||||||
|
//
|
||||||
|
// Lifecycle:
|
||||||
|
// 1. Clear any stale in_flight=true rows from a previously crashed process
|
||||||
|
// (safe at startup: by definition, nothing is running yet).
|
||||||
|
// 2. Run once at startup so users catch up after a server restart without
|
||||||
|
// waiting up to 24h.
|
||||||
|
// 3. Tick every 24h; runOnce on each tick.
|
||||||
|
// 4. Exit on context cancellation.
|
||||||
|
//
|
||||||
|
// Per-user errors are logged but don't halt the loop. Per-cycle errors (e.g.,
|
||||||
|
// "list active users" failed) are logged but the next tick still fires.
|
||||||
|
func StartSystemPlaylistCron(ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger) {
|
||||||
|
go func() {
|
||||||
|
q := dbq.New(pool)
|
||||||
|
if err := q.ClearStaleSystemPlaylistInFlight(ctx); err != nil {
|
||||||
|
logger.Warn("system playlist cron: startup recovery failed", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runOnce(ctx, pool, logger)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
runOnce(ctx, pool, logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runOnce(ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger) {
|
||||||
|
q := dbq.New(pool)
|
||||||
|
users, err := q.ListActiveUsersForSystemPlaylists(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("system playlist cron: list active users failed", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, userID := range users {
|
||||||
|
if err := BuildSystemPlaylists(ctx, pool, logger, userID, time.Now()); err != nil {
|
||||||
|
logger.Warn("system playlist build failed in cron",
|
||||||
|
"user_id", uuidStringPL(userID), "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user