package ui import ( "context" "fmt" "time" "git.stinnesbeck.com/nils/arr-tui/internal/arr" ) // libQueueInterval is how often the Library (and a series' seasons and // episodes) ask for the download queues while they are visible. const libQueueInterval = 10 * time.Second // dlInfo says that a library title is in the Radarr/Sonarr download queue. type dlInfo struct { count int // queue entries (episodes of a series) progress float64 // percent, of the first entry timeLeft string // of the first entry } // dlLine is the details pane line for a title in the queue. func (d dlInfo) dlLine() string { if d.count > 1 { return fmt.Sprintf("[blue]Downloading[-] %d episodes\n", d.count) } line := fmt.Sprintf("[blue]Downloading[-] %.0f%%", d.progress) if d.timeLeft != "" { line += " ยท " + d.timeLeft + " left" } return line + "\n" } // queueByTitle groups queue entries by the library title they belong to. func queueByTitle(items []arr.QueueItem, series bool) map[int]dlInfo { out := map[int]dlInfo{} for _, q := range items { id := q.MovieID if series { id = q.SeriesID } if id == 0 { continue } d, seen := out[id] d.count++ if !seen { d.progress, d.timeLeft = q.Progress(), q.TimeLeft } out[id] = d } return out } // queueByEpisode picks the queue entries of one series, by episode. func queueByEpisode(items []arr.QueueItem, seriesID int) map[int]dlInfo { out := map[int]dlInfo{} for _, q := range items { if q.SeriesID != seriesID || q.EpisodeID == 0 { continue } if _, seen := out[q.EpisodeID]; !seen { out[q.EpisodeID] = dlInfo{count: 1, progress: q.Progress(), timeLeft: q.TimeLeft} } } return out } // every runs f on the UI goroutine every d until stop is called. func (a *App) every(d time.Duration, f func()) (stop func()) { done := make(chan struct{}) go func() { t := time.NewTicker(d) defer t.Stop() for { select { case <-done: return case <-t.C: a.post(f) } } }() return func() { close(done) } } // loadQueue fetches both download queues and marks the titles in them. // Failures (a service down or not configured) leave the marks as they were. func (v *libraryView) loadQueue() { go func() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() movies, merr := v.radarr.Queue(ctx) series, serr := v.sonarr.Queue(ctx) v.app.post(func() { if merr == nil { v.downloading[kindMovies] = queueByTitle(movies, false) } if serr == nil { v.downloading[kindSeries] = queueByTitle(series, true) } if merr == nil || serr == nil { v.render() } }) }() } // startQueueWatch refreshes the queue marks while the Library is visible. func (v *libraryView) startQueueWatch() { v.loadQueue() if v.queueStop == nil { v.queueStop = v.app.every(libQueueInterval, v.loadQueue) } } // deactivate stops the queue watch when the Library is left. func (v *libraryView) deactivate() { if v.queueStop != nil { v.queueStop() v.queueStop = nil } }