feat(health): offer a sourcesUpdated pull alongside the presets push

Adds a second QuickFix to the speaker_presets_count warning, reusing
the existing postSourcesUpdated fix (checks_refresh_sources.go). It
nudges the speaker to re-fetch /full, which is confirmed (both from
marge.AccountFullToXML and a genuine captured Bose-cloud response) to
carry presets alongside sources.

Whether firmware actually re-applies /full's preset section back onto
its own local table is unconfirmed — issue253_regression_test.go
already flags that exact link as untested. So this is offered as a
free, non-destructive thing to try first, with the guaranteed
restore_presets_to_speaker push as the fallback. Gives both directions
(pull-style nudge, direct push) rather than only the one.

Refs #614
This commit is contained in:
Tobias Gesellchen
2026-08-15 14:39:49 +02:00
parent 0f452357e2
commit b0b8b9a475
2 changed files with 34 additions and 7 deletions
+23 -5
View File
@@ -50,6 +50,17 @@ func RegisterPresetsCountCheck(r *Registry, ds *datastore.DataStore) {
r.RegisterFix(CheckIDPresetsCount, FixIDRestorePresetsToSpeaker, func(target Target) (string, error) {
return restorePresetsToSpeaker(ds, target)
})
// Same underlying nudge as the refresh_sources check (FixIDPostSourcesUpdated,
// checks_refresh_sources.go): POSTs sourcesUpdated so the speaker re-fetches
// /full. Confirmed that /full carries presets alongside sources
// (marge.AccountFullToXML); NOT confirmed that firmware re-applies the
// presets section locally (see issue253_regression_test.go — that exact
// link is documented as untested). Offered as a cheap, non-destructive
// thing to try before the guaranteed-but-heavier restore-to-speaker push.
r.RegisterFix(CheckIDPresetsCount, FixIDPostSourcesUpdated, func(target Target) (string, error) {
return postSourcesUpdated(ds, target)
})
}
func runPresetsCountCheck(ds *datastore.DataStore) []Finding {
@@ -149,11 +160,18 @@ func comparePresetsForDeviceWithURL(ds *datastore.DataStore, account, deviceID,
// (reboot and/or Sync leaving the speaker's own preset
// slots empty while the service's Presets.xml is untouched).
severity = SeverityWarning
quickFixes = []QuickFix{{
ID: FixIDRestorePresetsToSpeaker,
Label: "Restore presets to speaker",
Confirm: "This pushes AfterTouch's stored presets onto the speaker's own preset slots, one at a time. Doesn't require a reboot.",
}}
quickFixes = []QuickFix{
{
ID: FixIDPostSourcesUpdated,
Label: "Try a sourcesUpdated nudge first",
Confirm: "Asks the speaker to re-fetch /full (the same nudge used to refresh sources). /full does include presets, but whether the speaker applies them back to its own preset table isn't confirmed — this is free and non-destructive, worth trying before the push below.",
},
{
ID: FixIDRestorePresetsToSpeaker,
Label: "Restore presets to speaker",
Confirm: "This pushes AfterTouch's stored presets onto the speaker's own preset slots, one at a time. Doesn't require a reboot.",
},
}
}
return []Finding{{
@@ -188,8 +188,17 @@ func TestPresetsCount_SpeakerEmptyOffersRestoreQuickFix(t *testing.T) {
t.Fatalf("expected one finding, got %+v", got)
}
if len(got[0].QuickFixes) != 1 || got[0].QuickFixes[0].ID != FixIDRestorePresetsToSpeaker {
t.Errorf("expected the restore-presets QuickFix, got %+v", got[0].QuickFixes)
// Offers both the cheap, unconfirmed pull-style nudge (sourcesUpdated,
// which makes the speaker re-fetch /full — /full does carry presets,
// but firmware re-applying them locally is unconfirmed) and the
// guaranteed push (restore_presets_to_speaker) — see #614 discussion.
fixIDs := map[string]bool{}
for _, qf := range got[0].QuickFixes {
fixIDs[qf.ID] = true
}
if len(got[0].QuickFixes) != 2 || !fixIDs[FixIDRestorePresetsToSpeaker] || !fixIDs[FixIDPostSourcesUpdated] {
t.Errorf("expected both the sourcesUpdated nudge and the restore-presets QuickFix, got %+v", got[0].QuickFixes)
}
}