5c: app.Fetcher = a per-user YouTube videoFetcher, so POST /paste mounts and resolves arbitrary-video metadata (Feature 2 goes live). 6: the connect trigger now runs an onboarding burst after discovery — summarize up to TAPIR_ONBOARD_SUMMARIZE_COUNT of the user's newest unsummarized videos via the gated Processor (Feature 1). Hard cap; explicit so it bypasses recency; every fetch still through globalFetchGate. No-op when count=0 or queue-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
1.7 KiB
Go
51 lines
1.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
|
|
"gitea.d-ma.be/mathias/tapir/internal/runner"
|
|
)
|
|
|
|
// discoveryRunner runs one user's discovery pass.
|
|
type discoveryRunner func(ctx context.Context, userID string) (runner.Stats, error)
|
|
|
|
// serialize wraps run so calls never overlap: every discovery pass — scheduled
|
|
// or connect-triggered (#6) — acquires the same lock, preserving the
|
|
// one-fetcher-at-a-time invariant the scheduler relies on (ADR-018, the
|
|
// single-replica assumption). Locking is per-user, so a connect-triggered pass
|
|
// interleaves between the scheduler's users instead of waiting for a whole pass.
|
|
func serialize(mu *sync.Mutex, run discoveryRunner) discoveryRunner {
|
|
return func(ctx context.Context, userID string) (runner.Stats, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return run(ctx, userID)
|
|
}
|
|
}
|
|
|
|
// discoveryTrigger fires an out-of-band discovery pass for one user without
|
|
// blocking the caller (the connect HTTP handler). The pass runs on the server's
|
|
// long-lived ctx — not the request ctx — so it survives the post-connect
|
|
// redirect. run is the serialized runner, so a trigger never overlaps the
|
|
// scheduler. Satisfies web.DiscoveryTrigger.
|
|
type discoveryTrigger struct {
|
|
ctx context.Context
|
|
run discoveryRunner
|
|
// onboard, when set, runs after the discovery pass to summarize a capped number
|
|
// of the user's newest videos (Feature 1). Optional.
|
|
onboard func(ctx context.Context, userID string)
|
|
log *slog.Logger
|
|
}
|
|
|
|
func (t *discoveryTrigger) Enqueue(userID string) {
|
|
go func() {
|
|
if _, err := t.run(t.ctx, userID); err != nil {
|
|
t.log.Warn("discovery: connect-triggered pass had errors", "user", userID, "err", err)
|
|
}
|
|
if t.onboard != nil {
|
|
t.onboard(t.ctx, userID)
|
|
}
|
|
}()
|
|
}
|