While the Library is visible it reads the Radarr and Sonarr queues (on opening, every 10 s, and shortly after a grab) and matches them to the titles by movieId / seriesId. Those titles get a blue dot instead of the green or grey one, and their details say how far the download is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
2.4 KiB
Go
106 lines
2.4 KiB
Go
package ui
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.stinnesbeck.com/nils/arr-tui/internal/arr"
|
|
)
|
|
|
|
// libQueueInterval is how often the Library asks for the download queues
|
|
// while it is 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
|
|
}
|
|
|
|
// 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 {
|
|
return
|
|
}
|
|
stop := make(chan struct{})
|
|
v.queueStop = stop
|
|
go func() {
|
|
t := time.NewTicker(libQueueInterval)
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
case <-t.C:
|
|
v.app.post(v.loadQueue)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// deactivate stops the queue watch when the Library is left.
|
|
func (v *libraryView) deactivate() {
|
|
if v.queueStop != nil {
|
|
close(v.queueStop)
|
|
v.queueStop = nil
|
|
}
|
|
}
|