0.2.78
This commit is contained in:
@@ -99,6 +99,20 @@ func (s *Service) Running() bool {
|
||||
return s.importRunning || len(s.building) > 0
|
||||
}
|
||||
|
||||
// Ping asks Tracearr for the smallest thing it will answer with.
|
||||
//
|
||||
// It exists so the integrations console can report whether Tracearr is reachable without
|
||||
// reaching past this service for the client: the service owns the connection, and a
|
||||
// console holding its own copy of the client would be a second place the address and key
|
||||
// could be wrong. One user, one page — the answer is discarded and only the error matters.
|
||||
func (s *Service) Ping(ctx context.Context) error {
|
||||
if s == nil || s.tracearr == nil {
|
||||
return errors.New("tracearr is not configured")
|
||||
}
|
||||
_, err := s.tracearr.Users(ctx, 1, 1)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) Stats(ctx context.Context) (store.ForYouStats, error) {
|
||||
return s.store.ForYouStats(ctx)
|
||||
}
|
||||
@@ -403,17 +417,35 @@ func (s *Service) RefreshAsync(sess store.Session, force bool) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Service) RebuildAll(ctx context.Context, force bool) error {
|
||||
// RebuildResult is what one pass over the household did.
|
||||
//
|
||||
// Counted because a rebuild that quietly failed for every viewer and one that succeeded
|
||||
// for every viewer are the same "no error" from outside: a per-user failure is logged and
|
||||
// swallowed on purpose — one viewer's broken profile must not stop the rest being built —
|
||||
// so the count is the only thing that can say it happened.
|
||||
type RebuildResult struct {
|
||||
Users int
|
||||
Built int
|
||||
Failed int
|
||||
Skipped int
|
||||
}
|
||||
|
||||
func (s *Service) RebuildAll(ctx context.Context, force bool) (RebuildResult, error) {
|
||||
result := RebuildResult{}
|
||||
users, err := s.recommendationUsers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return result, err
|
||||
}
|
||||
result.Users = len(users)
|
||||
for _, user := range users {
|
||||
if err := s.Rebuild(ctx, user, force); err != nil {
|
||||
result.Failed++
|
||||
s.log.Warn("For You user rebuild failed", "user", user.EmbyUserID, "error", err)
|
||||
continue
|
||||
}
|
||||
result.Built++
|
||||
}
|
||||
return nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RebuildOutdated refreshes only profiles produced by an older algorithm. This makes
|
||||
@@ -767,66 +799,13 @@ func importDue(
|
||||
return false, false
|
||||
}
|
||||
|
||||
func (s *Service) Schedule(
|
||||
ctx context.Context,
|
||||
importEvery, fullEvery time.Duration,
|
||||
rebuildHour int,
|
||||
paused ...func() bool,
|
||||
) {
|
||||
// One ticker asks "is anything owed?"; the persisted stamps decide what and whether.
|
||||
// Two independent tickers measured process uptime, which is what let a restart reset
|
||||
// the cadence and a bounced container import far more often than configured.
|
||||
checkEvery := importEvery
|
||||
if checkEvery <= 0 || (fullEvery > 0 && fullEvery < checkEvery) {
|
||||
checkEvery = fullEvery
|
||||
}
|
||||
var importC <-chan time.Time
|
||||
if checkEvery > 0 {
|
||||
importTicker := time.NewTicker(checkEvery)
|
||||
importC = importTicker.C
|
||||
defer importTicker.Stop()
|
||||
s.log.Info("Tracearr auto-import scheduled",
|
||||
"incremental", importEvery.String(), "full", fullEvery.String())
|
||||
} else {
|
||||
s.log.Info("Tracearr auto-import disabled")
|
||||
}
|
||||
nextRebuild := nextDailyRebuild(time.Now(), s.location, rebuildHour)
|
||||
rebuildTimer := time.NewTimer(time.Until(nextRebuild))
|
||||
defer rebuildTimer.Stop()
|
||||
s.log.Info("For You daily rebuild scheduled", "next", nextRebuild)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-importC:
|
||||
if len(paused) > 0 && paused[0] != nil && paused[0]() {
|
||||
continue
|
||||
}
|
||||
if _, _, err := s.ImportIfDue(ctx, importEvery, fullEvery); err != nil {
|
||||
s.log.Warn("scheduled Tracearr import failed", "error", err)
|
||||
}
|
||||
case <-rebuildTimer.C:
|
||||
if len(paused) == 0 || paused[0] == nil || !paused[0]() {
|
||||
if err := s.RebuildAll(ctx, true); err != nil {
|
||||
s.log.Warn("scheduled daily For You rebuild failed", "error", err)
|
||||
}
|
||||
}
|
||||
rebuildTimer.Reset(time.Until(nextDailyRebuild(time.Now(), s.location, rebuildHour)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nextDailyRebuild(now time.Time, location *time.Location, hour int) time.Time {
|
||||
if location == nil {
|
||||
location = time.UTC
|
||||
}
|
||||
local := now.In(location)
|
||||
next := time.Date(local.Year(), local.Month(), local.Day(), hour, 0, 0, 0, location)
|
||||
if !next.After(local) {
|
||||
next = next.AddDate(0, 0, 1)
|
||||
}
|
||||
return next
|
||||
}
|
||||
// Schedule used to own both the Tracearr import ticker and the daily rebuild timer, and
|
||||
// nextDailyRebuild was when the household's off-peak hour next came round. Both are gone:
|
||||
// the import and the rebuild are scheduler tasks now (see api.RegisterIntegrationTasks),
|
||||
// because a ticker in here could report nothing to an operator, could not be started by
|
||||
// hand, and — since the tasks carry an integration id — could not be counted towards
|
||||
// Tracearr's run history. The "is the rebuild due" rule moved with the work, to
|
||||
// api.forYouRebuildDue, and is still pure and still tested.
|
||||
|
||||
func (s *Service) beginBuild(userID string) bool {
|
||||
s.mu.Lock()
|
||||
|
||||
@@ -179,17 +179,6 @@ func TestImportDueRespectsDisabledIntervals(t *testing.T) {
|
||||
full, due)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextDailyRebuildUsesConfiguredLocalHour(t *testing.T) {
|
||||
location := time.FixedZone("NZST", 12*60*60)
|
||||
now := time.Date(2026, 7, 31, 5, 30, 0, 0, location)
|
||||
next := nextDailyRebuild(now, location, 4)
|
||||
want := time.Date(2026, 8, 1, 4, 0, 0, 0, location)
|
||||
if !next.Equal(want) {
|
||||
t.Fatalf("next rebuild = %v, want %v", next, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoredResultCapsCandidatePoolAndPersistsAlgorithmVersion(t *testing.T) {
|
||||
result := recommend.PreparedResult{
|
||||
Candidates: make([]recommend.PreparedCandidate, maxPreparedCandidates+50),
|
||||
|
||||
Reference in New Issue
Block a user