Files
minstrel/internal/library/notify.go
T
bvandeusenandClaude Opus 5.5 1e9408c835
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m43s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m16s
release / Build signed APK (releases and dev) (push) Successful in 5m27s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 15s
release / Verify release artifacts (tag releases only) (push) Skipped
feat(notifications): library health reaches admins, coalesced (#5341)
- A failed scan run sends scan_failed. Each failure adds to the count and
  the notice shows the latest error. A scan cut short by shutdown says
  nothing.
- Marking tracks missing sends tracks_missing with a running count.
- A duplicate sweep that proposes a group it had not proposed before
  sends duplicates_found, counting everything awaiting review. A sweep
  that only re-finds known groups stays quiet, so a read notice isn't
  repeated every sweep (CountDuplicateGroupsDetectedSince).
- A playback-error report sends playback_errors, counting the unresolved
  errors (CountUnresolvedPlaybackErrors).

The library package gets its notifier as a package-level SetNotifier
beside SetEventBus, for the same reason the bus is package-level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:20:33 -04:00

45 lines
1.4 KiB
Go

package library
import (
"context"
"sync"
"github.com/jackc/pgx/v5/pgtype"
"git.fabledsword.com/bvandeusen/minstrel/internal/notifications"
)
// Package-level notifier for library-health notices to admins (M489): a
// scan that failed, tracks gone missing, new duplicates. Set once at startup
// beside SetEventBus, for the same reason the bus is package-level. Nil, as in
// tests that never set it, means nobody is told.
var (
notifierMu sync.RWMutex
notifier *notifications.Notifier
)
// SetNotifier wires the notifications inbox into the library package.
func SetNotifier(n *notifications.Notifier) {
notifierMu.Lock()
defer notifierMu.Unlock()
notifier = n
}
// notifyAdmins tells every admin about a library-health event. These kinds
// coalesce, so a burst is one unread notice with a running count.
func notifyAdmins(ctx context.Context, kind notifications.Kind, p notifications.Payload) {
notifierMu.RLock()
n := notifier
notifierMu.RUnlock()
n.NotifyLogged(ctx, kind, notifications.ToAdmins(pgtype.UUID{}), p.Map())
}
// notifyScanFinished tells admins a scan run ended in error. A scan cut short
// by shutdown is not a failure anyone needs telling about.
func notifyScanFinished(ctx context.Context, errMsg string) {
if errMsg == "" || ctx.Err() != nil {
return
}
notifyAdmins(ctx, notifications.KindScanFailed, notifications.Payload{Count: 1, Detail: errMsg})
}