an ambient meeting suppresses a nudge for its own span (V-513)
The ambient endpoint writes calendar_event_* and never calendar_busy, so a notification-derived meeting was good enough to recite out loud and not good enough to stop a nudge during it. Backwards: being wrong here costs one nudge. The loop gatherer now derives busy from the event facts themselves, so the expiry IS the meeting's span. No new level, no interval to choose, and no way for the suppression to outlive the meeting. calendar.FactSpan reads back what FactValue wrote; anything that does not parse says nothing about now.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kami/maven/internal/calendar"
|
||||
"github.com/kami/maven/internal/store"
|
||||
)
|
||||
|
||||
@@ -158,6 +159,19 @@ func (g *Gatherer) GatherState(ctx context.Context, now time.Time) (State, []sto
|
||||
if f, ok := readFact(ctx, g.store, "calendar_busy"); ok {
|
||||
calBusy = f.Value == "true" || f.Value == `"true"`
|
||||
}
|
||||
// An ambient meeting suppresses a nudge too (Vikunja #513). It writes
|
||||
// calendar_event_* and never calendar_busy, which is the CalDAV poller's
|
||||
// level, so before this a low-confidence meeting was good enough to recite
|
||||
// out loud and not good enough to stop a nudge during it. That is
|
||||
// backwards: being wrong here costs one nudge he did not get.
|
||||
//
|
||||
// The expiry is the event's own span, which is why there is no new level
|
||||
// and no interval to choose. A poller re-asserts a level every cycle and a
|
||||
// notification arrives once; an event that already ended covers nothing,
|
||||
// and one that has not started yet covers nothing either.
|
||||
if !calBusy {
|
||||
calBusy = g.eventCoversNow(ctx, now)
|
||||
}
|
||||
|
||||
// due reminders — gate-bypassing class. read here, the daemon emits them.
|
||||
due, err := g.store.DueReminders(ctx, now)
|
||||
@@ -212,6 +226,29 @@ func parseHHMM(s string) (hour, min int, ok bool) {
|
||||
return h, m, true
|
||||
}
|
||||
|
||||
// eventCoversNow reports whether any stored calendar event covers this instant.
|
||||
// Read from the event facts themselves, so it holds for exactly as long as the
|
||||
// meeting does — see the note at the call site.
|
||||
//
|
||||
// A read failure answers false: a meeting nobody can read about is not a reason
|
||||
// to go quiet.
|
||||
func (g *Gatherer) eventCoversNow(ctx context.Context, now time.Time) bool {
|
||||
fam, err := g.store.LatestFactsByPrefix(ctx, calendar.EventKeyPrefix)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, f := range fam {
|
||||
start, end, ok := calendar.FactSpan(f.Key, f.Value, now.Location())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !now.Before(start) && now.Before(end) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readFact(ctx context.Context, s *store.Store, key string) (store.Fact, bool) {
|
||||
f, err := s.LatestFact(ctx, key)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user