Config: pick the default quality profile and root folder from the server

The two defaults of Radarr and Sonarr are dropdowns filled from the
server with the address and key in the fields: when the view opens, on
Revert, and shortly after either is changed. A configured value the
server does not list stays selectable, marked "(not on the server)".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
This commit is contained in:
2026-10-21 12:29:09 +02:00
co-authored by Claude Opus 5.5
parent c3eca0b68c
commit 191cfdbabb
7 changed files with 320 additions and 24 deletions
+56 -18
View File
@@ -120,17 +120,23 @@ type settingsView struct {
groups []*tview.Form // keyboard order: the fields, then the buttons
status *tview.TextView
inputs map[string]*tview.InputField // by setting key, e.g. "radarr.url"
refresh *tview.InputField
covers *tview.Checkbox
style *tview.DropDown
theme *tview.DropDown
notify *tview.Checkbox
bell *tview.Checkbox
showKeys *tview.Checkbox
overridden map[string]string
dirty bool
filling bool // set while the fields are filled, so that does not count as an edit
inputs map[string]*tview.InputField // by setting key, e.g. "radarr.url"
choices map[string]*serverChoice // "radarr.profile", "radarr.folder", ...
// Loading the choices from the servers, per service.
choiceTimers map[string]*time.Timer
choiceGen map[string]int
choiceErr map[string]string // last error reported, so it is not repeated
refresh *tview.InputField
covers *tview.Checkbox
style *tview.DropDown
theme *tview.DropDown
notify *tview.Checkbox
bell *tview.Checkbox
showKeys *tview.Checkbox
overridden map[string]string
dirty bool
filling bool // set while the fields are filled, so that does not count as an edit
}
func settingsHints() string {
@@ -138,7 +144,13 @@ func settingsHints() string {
}
func newSettingsView(app *App) *settingsView {
v := &settingsView{app: app, inputs: map[string]*tview.InputField{}, overridden: config.Overrides(os.Getenv)}
v := &settingsView{app: app, inputs: map[string]*tview.InputField{}, overridden: config.Overrides(os.Getenv),
choices: map[string]*serverChoice{}, choiceTimers: map[string]*time.Timer{}, choiceGen: map[string]int{}, choiceErr: map[string]string{}}
quiet := func() func() {
was := v.filling
v.filling = true
return func() { v.filling = was }
}
// One form with every field below the other: ↑↓ moves through all of them
// and the form scrolls to keep the focused field in view.
@@ -156,17 +168,28 @@ func newSettingsView(app *App) *settingsView {
in.SetLabel(label + " (env)")
in.SetDisabled(true)
}
in.SetChangedFunc(func(string) { v.edited() })
in.SetChangedFunc(func(string) {
v.edited()
// New address or key: ask that server for its profiles and folders.
if service, field, _ := strings.Cut(key, "."); service != "sabnzbd" && !v.filling && (field == "url" || field == "apikey") {
v.scheduleChoices(service)
}
})
f.AddFormItem(in)
v.inputs[key] = in
}
choice := func(label, key string) {
c := newServerChoice(label, quiet, v.edited)
f.AddFormItem(c)
v.choices[key] = c
}
service := func(title, name string, withDefaults bool) {
f.AddFormItem(newFormHeading(title))
input("Address", name+".url")
input("API key", name+".apikey")
if withDefaults {
input("Default quality profile", name+".profile")
input("Default root folder", name+".folder")
choice("Default quality profile", name+".profile")
choice("Default root folder", name+".folder")
}
}
service("Radarr", "radarr", true)
@@ -290,13 +313,19 @@ func (v *settingsView) fill(s settingsValues) {
v.filling = true
defer func() { v.filling = false }()
set := map[string]string{
"radarr.url": s.radarrURL, "radarr.apikey": s.radarrKey, "radarr.profile": s.radarrProfile, "radarr.folder": s.radarrFolder,
"sonarr.url": s.sonarrURL, "sonarr.apikey": s.sonarrKey, "sonarr.profile": s.sonarrProfile, "sonarr.folder": s.sonarrFolder,
"radarr.url": s.radarrURL, "radarr.apikey": s.radarrKey,
"sonarr.url": s.sonarrURL, "sonarr.apikey": s.sonarrKey,
"sabnzbd.url": s.sabURL, "sabnzbd.apikey": s.sabKey,
}
for key, value := range set {
v.inputs[key].SetText(value)
}
for key, value := range map[string]string{
"radarr.profile": s.radarrProfile, "radarr.folder": s.radarrFolder,
"sonarr.profile": s.sonarrProfile, "sonarr.folder": s.sonarrFolder,
} {
v.choices[key].set(value)
}
v.refresh.SetText(s.refresh)
v.covers.SetChecked(s.covers)
for i, name := range coverStyles {
@@ -316,7 +345,12 @@ func (v *settingsView) fill(s settingsValues) {
// read collects the values from the fields.
func (v *settingsView) read() settingsValues {
text := func(key string) string { return v.inputs[key].GetText() }
text := func(key string) string {
if c, ok := v.choices[key]; ok {
return c.value
}
return v.inputs[key].GetText()
}
i, _ := v.style.GetCurrentOption()
style := "auto"
if i >= 0 && i < len(coverStyles) {
@@ -363,6 +397,8 @@ func (v *settingsView) activate() {
if !v.dirty {
v.fill(settingsFrom(v.app.cfg))
}
v.loadChoices("radarr")
v.loadChoices("sonarr")
v.focusGroup(0, false)
}
@@ -374,6 +410,8 @@ func (v *settingsView) report(line string) {
func (v *settingsView) revert() {
v.fill(settingsFrom(v.app.cfg))
v.loadChoices("radarr")
v.loadChoices("sonarr")
v.describe()
v.report("[green]Reverted to the saved settings.[-]")
}