package ui import ( "context" "time" "git.stinnesbeck.com/nils/arr-tui/internal/sabnzbd" "github.com/rivo/tview" ) // watchInterval is how often SABnzbd's history is checked for finished // downloads in the background, whatever view is showing. const watchInterval = 15 * time.Second // downloadEvent is a download that just finished or failed. type downloadEvent struct { name string failed bool message string // failure reason, if any } // diffHistory compares SABnzbd's history with what was seen before and // returns the downloads that reached a final state since. seen maps job id to // the last known status and is updated. With baseline set (the first poll) the // current state is only recorded, so downloads that finished before arr-tui // started do not produce notifications. func diffHistory(seen map[string]string, slots []sabnzbd.HistorySlot, baseline bool) []downloadEvent { var events []downloadEvent for _, s := range slots { prev, known := seen[s.ID] seen[s.ID] = s.Status final := s.Status == "Completed" || s.Failed() if baseline || !final || (known && prev == s.Status) { continue } events = append(events, downloadEvent{name: s.Name, failed: s.Failed(), message: s.FailMessage}) } return events } // watchDownloads polls SABnzbd's history until ctx ends and reports finished // and failed downloads as popups (and a bell, if enabled). Errors are not // reported here: the connection indicator and the log already do that. func (a *App) watchDownloads(ctx context.Context, sab *sabnzbd.Client, bell bool) { seen := map[string]string{} baseline := true t := time.NewTicker(watchInterval) defer t.Stop() for { hctx, cancel := context.WithTimeout(ctx, 10*time.Second) slots, err := sab.History(hctx, 30) cancel() if err == nil { events := diffHistory(seen, slots, baseline) baseline = false if len(events) > 0 { a.post(func() { a.announce(events, bell) }) } } select { case <-ctx.Done(): return case <-t.C: } } } // announce shows a popup for each event; UI goroutine only. func (a *App) announce(events []downloadEvent, bell bool) { for _, e := range events { if e.failed { text := "Download failed: " + tview.Escape(e.name) if e.message != "" { text += " (" + tview.Escape(truncate(e.message, 60)) + ")" } a.SetMessage("[red]" + text) } else { a.SetMessage("[green]Download finished: " + tview.Escape(e.name)) } } if bell { if a.screen != nil { a.screen.Beep() } } }