|
|
|
@@ -15,6 +15,13 @@ package main
|
|
|
|
|
// the desk says in its standing when that happened (`answers-lost-at`), and the router, which alone knows
|
|
|
|
|
// what is still open and unanswered, says that again under its handle — replacing the notification in place,
|
|
|
|
|
// so its buttons work again. Nothing resolved, silenced or cancelled comes back.
|
|
|
|
|
// - **A store that does not answer does not lose where a message is shown, while the desk runs.** A message
|
|
|
|
|
// is shown first and its notification ids kept after; when the state store does not answer then (the bus
|
|
|
|
|
// reloading its accounts, 2026-10-26 09:18: the very outage that raised the condition), the work is taken
|
|
|
|
|
// all the same and the ids are held in memory, recalled before the store, and kept at the start of the next
|
|
|
|
|
// work and at the minute's standing. Otherwise the clearing of that message finds no notification to
|
|
|
|
|
// replace and the urgent one stays on screen for good. A desk that restarts while it still holds some loses
|
|
|
|
|
// where those are shown; the log says so when they are held.
|
|
|
|
|
// - **Standing**: it can send when machines are named in its settings; which of them answered last is in its
|
|
|
|
|
// status. A message no machine took is said to the router as failed, and tried again by it.
|
|
|
|
|
|
|
|
|
@@ -93,6 +100,9 @@ type Desk struct {
|
|
|
|
|
pause map[string]pauseSeen
|
|
|
|
|
// lost is when each machine's notifier last started again: the answers its notifications waited for are lost.
|
|
|
|
|
lost map[string]time.Time
|
|
|
|
|
// unkept is what was shown and not yet kept in the store, because it did not answer: recalled from here
|
|
|
|
|
// before the store, and kept at the next piece of work.
|
|
|
|
|
unkept map[string]Shown
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Desk) init() {
|
|
|
|
@@ -107,6 +117,9 @@ func (d *Desk) init() {
|
|
|
|
|
if d.lost == nil {
|
|
|
|
|
d.lost = map[string]time.Time{}
|
|
|
|
|
}
|
|
|
|
|
if d.unkept == nil {
|
|
|
|
|
d.unkept = map[string]Shown{}
|
|
|
|
|
}
|
|
|
|
|
if d.Now == nil {
|
|
|
|
|
d.Now = time.Now
|
|
|
|
|
}
|
|
|
|
@@ -138,6 +151,7 @@ func (d *Desk) Take(subject string, body json.RawMessage) error {
|
|
|
|
|
d.Logf("[desk-channel] work on %s could not be read; taken and dropped", subject)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
d.KeepUnkept()
|
|
|
|
|
machines := d.Machines()
|
|
|
|
|
if m.Secret {
|
|
|
|
|
// Shown only where the notifier said lately it is not paused (the review of 2026-10-09): one held back
|
|
|
|
@@ -167,12 +181,16 @@ func (d *Desk) Take(subject string, body json.RawMessage) error {
|
|
|
|
|
switch {
|
|
|
|
|
case edit:
|
|
|
|
|
var err error
|
|
|
|
|
if before, err = d.Store.Get(m.Handle); err != nil {
|
|
|
|
|
if before, err = d.recall(m.Handle); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if before == nil {
|
|
|
|
|
d.Logf("[desk-channel] an edit of %s, which nothing here records as shown: shown as a new message; "+
|
|
|
|
|
"a notification it was to replace, if any, stays", m.Handle)
|
|
|
|
|
}
|
|
|
|
|
case !m.Secret:
|
|
|
|
|
var err error
|
|
|
|
|
if before, err = d.Store.Get(m.Handle); err != nil {
|
|
|
|
|
if before, err = d.recall(m.Handle); err != nil {
|
|
|
|
|
d.Logf("[desk-channel] what is shown under this handle could not be read (%v): shown as a new message", err)
|
|
|
|
|
before = nil
|
|
|
|
|
}
|
|
|
|
@@ -218,12 +236,91 @@ func (d *Desk) Take(subject string, body json.RawMessage) error {
|
|
|
|
|
// Shown once and kept nowhere of this module's (a link's code): no edit or re-showing reaches it.
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if err := d.Store.Put(m.Handle, s); err != nil {
|
|
|
|
|
d.Logf("[desk-channel] shown, and what was shown could not be kept: an edit of it will not reach it: %v", err)
|
|
|
|
|
}
|
|
|
|
|
d.keep(m.Handle, s)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// keep keeps what a handle shows in the store. When the store does not answer it is held in memory, where an
|
|
|
|
|
// edit, a reminder and a click still find it, and kept later (KeepUnkept). The work is taken either way: offered
|
|
|
|
|
// again it would race the message's own later edits, and a redelivery is bounded where an outage is not.
|
|
|
|
|
func (d *Desk) keep(handle string, s Shown) {
|
|
|
|
|
err := d.Store.Put(handle, s)
|
|
|
|
|
d.mu.Lock()
|
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
|
if err == nil {
|
|
|
|
|
delete(d.unkept, handle)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
d.unkept[handle] = s
|
|
|
|
|
d.Logf("[desk-channel] shown, and where it is shown could not be kept yet (%v): held in memory, so its edit "+
|
|
|
|
|
"and clearing still reach it, and kept when the store answers; %d held, lost if the desk restarts first",
|
|
|
|
|
err, len(d.unkept))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// KeepUnkept keeps in the store what it did not answer for before: at the start of each piece of work and at
|
|
|
|
|
// the minute's standing. It stops at the first write the store does not answer, so one call waits on it once.
|
|
|
|
|
func (d *Desk) KeepUnkept() {
|
|
|
|
|
d.init()
|
|
|
|
|
d.mu.Lock()
|
|
|
|
|
held := make(map[string]Shown, len(d.unkept))
|
|
|
|
|
for h, s := range d.unkept {
|
|
|
|
|
held[h] = s
|
|
|
|
|
}
|
|
|
|
|
d.mu.Unlock()
|
|
|
|
|
for h, s := range held {
|
|
|
|
|
if err := d.Store.Put(h, s); err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
d.mu.Lock()
|
|
|
|
|
if cur, ok := d.unkept[h]; ok && sameShown(cur, s) {
|
|
|
|
|
delete(d.unkept, h)
|
|
|
|
|
}
|
|
|
|
|
d.mu.Unlock()
|
|
|
|
|
d.Logf("[desk-channel] where %s is shown is kept now", h)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Desk) unkeptOf(handle string) (Shown, bool) {
|
|
|
|
|
d.mu.Lock()
|
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
|
s, ok := d.unkept[handle]
|
|
|
|
|
return s, ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// recall is what a handle shows: from memory when the store did not keep it yet, else from the store.
|
|
|
|
|
func (d *Desk) recall(handle string) (*Shown, error) {
|
|
|
|
|
if s, ok := d.unkeptOf(handle); ok {
|
|
|
|
|
return &s, nil
|
|
|
|
|
}
|
|
|
|
|
return d.Store.Get(handle)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handles is every handle shown: the store's and those held in memory.
|
|
|
|
|
func (d *Desk) handles() ([]string, error) {
|
|
|
|
|
hs, err := d.Store.Handles()
|
|
|
|
|
d.mu.Lock()
|
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
|
seen := map[string]bool{}
|
|
|
|
|
for _, h := range hs {
|
|
|
|
|
seen[h] = true
|
|
|
|
|
}
|
|
|
|
|
for h := range d.unkept {
|
|
|
|
|
if !seen[h] {
|
|
|
|
|
hs = append(hs, h)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err != nil && len(d.unkept) == 0 {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return hs, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sameShown(a, b Shown) bool {
|
|
|
|
|
x, err1 := json.Marshal(a)
|
|
|
|
|
y, err2 := json.Marshal(b)
|
|
|
|
|
return err1 == nil && err2 == nil && string(x) == string(y)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// show puts one message on one machine's notifier, in place of `replace` when it names one.
|
|
|
|
|
func (d *Desk) show(machine string, m asks.Message, replace int) (int, error) {
|
|
|
|
|
urgency := "normal"
|
|
|
|
@@ -308,12 +405,12 @@ func (d *Desk) Chosen(event, machine, ticket string) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Desk) find(ticket string) (string, *Shown) {
|
|
|
|
|
handles, err := d.Store.Handles()
|
|
|
|
|
handles, err := d.handles()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
for _, h := range handles {
|
|
|
|
|
s, err := d.Store.Get(h)
|
|
|
|
|
s, err := d.recall(h)
|
|
|
|
|
if err != nil || s == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
@@ -356,9 +453,9 @@ func (d *Desk) Started(event, machine string) int {
|
|
|
|
|
d.lost[machine] = d.Now()
|
|
|
|
|
d.mu.Unlock()
|
|
|
|
|
n := 0
|
|
|
|
|
if handles, err := d.Store.Handles(); err == nil {
|
|
|
|
|
if handles, err := d.handles(); err == nil {
|
|
|
|
|
for _, h := range handles {
|
|
|
|
|
if s, err := d.Store.Get(h); err == nil && s != nil && len(s.Message.Buttons) > 0 {
|
|
|
|
|
if s, err := d.recall(h); err == nil && s != nil && len(s.Message.Buttons) > 0 {
|
|
|
|
|
if _, here := s.On[machine]; here {
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
@@ -472,6 +569,7 @@ func (d *Desk) Status() map[string]any {
|
|
|
|
|
"it proves nothing about who clicked: any process of the operator's account can choose an action, so it answers acknowledgements only",
|
|
|
|
|
"an answer is taken only on a machine its message was shown on; a message with buttons stays until dismissed",
|
|
|
|
|
"what it remembers is never shown again: when a notifier started again, the answers its notifications waited for are lost; the desk says when in its standing (answers-lost-at), and the router says again what is still open and unanswered, replacing those notifications in place — nothing resolved, silenced or cancelled comes back (issue 384)",
|
|
|
|
|
"a store that does not answer does not lose where a message is shown while the desk runs: it is held in memory, its edit and clearing still replace it in place, and it is kept when the store answers again; a desk restarted before then loses where those are shown, and its log said how many it held",
|
|
|
|
|
"it can send while a named machine's notifier said lately that it is not paused; a link's code is shown only on such a machine, kept nowhere, and sent secret so the notifier keeps it out of its history",
|
|
|
|
|
}}
|
|
|
|
|
if s.Why != "" {
|
|
|
|
|