release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m33s
release / android (push) Failing after 1m42s
release / integration (push) Failing after 4m42s
release / Build signed APK (releases and dev) (push) Successful in 4m55s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
- An update to a grouped notice keeps its arrival time and email state, so the phone and the email no longer go off again each time it grows. - Notices carry the names of what changed (newest ten), shown as three and "and N more": tracks still missing after the scan's move/replacement fold, duplicate groups new to a sweep, songs merged automatically, and the track and listener a playback error was for. - Duplicates found counts only the groups new to that sweep; playback errors count reports, not the unresolved total. - A scan failure says what stopped and links to Admin > Library. - Android: a pile of more than three alerts lists the newest five titles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
2.7 KiB
Go
82 lines
2.7 KiB
Go
package library
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
|
||
"github.com/jackc/pgx/v5/pgtype"
|
||
|
||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||
"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())
|
||
}
|
||
|
||
// notifyItems is how many names a notice carries; the notifications package
|
||
// keeps the newest ten across a coalesced burst.
|
||
const notifyItems = 10
|
||
|
||
// TrackLabel names a track as a notice does: "Artist – Title", or the title
|
||
// alone when the artist is unknown.
|
||
func TrackLabel(artist, title string) string {
|
||
if artist == "" {
|
||
return title
|
||
}
|
||
return artist + " – " + title
|
||
}
|
||
|
||
// notifyStillMissing tells admins which of the tracks a scan marked missing
|
||
// are still gone after it adopted moves and folded replacements (M542). A
|
||
// marked track the scan healed is not news: reporting it is what made a
|
||
// quality upgrade read as lost music.
|
||
func (s *Scanner) notifyStillMissing(ctx context.Context, marked []pgtype.UUID) {
|
||
if len(marked) == 0 || ctx.Err() != nil {
|
||
return
|
||
}
|
||
rows, err := dbq.New(s.pool).ListStillMissingTrackLabels(ctx, marked)
|
||
if err != nil {
|
||
s.logger.Warn("library scan: listing missing tracks for the notice failed", "err", err)
|
||
return
|
||
}
|
||
if len(rows) == 0 {
|
||
return
|
||
}
|
||
items := make([]string, 0, min(len(rows), notifyItems))
|
||
for _, r := range rows[:min(len(rows), notifyItems)] {
|
||
items = append(items, TrackLabel(r.ArtistName, r.Title))
|
||
}
|
||
notifyAdmins(ctx, notifications.KindTracksMissing, notifications.Payload{Count: int64(len(rows)), Items: items})
|
||
}
|
||
|
||
// 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})
|
||
}
|