feat(lookup): a cache TTL of 0 switches the cache off

Wanted for the case where the answers are moving: an operator correcting their
own QRZ record, or chasing a DXpedition whose page changes during the operation,
otherwise waits out thirty days before OpsLog will ask again. Clearing the cache
by hand works once; this is the setting for a whole session.

Nothing is read from it and nothing is written to it — rows stored while it is
off would only sit there going stale, waiting for the day it comes back on.
Switching off is NOT clearing: what it already holds stays, and the Clear cache
button remains the way to throw that away.

Two distinctions the code now has to keep, both load-bearing:

An EXPLICIT stored zero is off; an ABSENT key is the thirty-day default. Every
operator who has never opened this setting has nothing stored, and reading that
blank as a zero would silently switch the cache off for all of them.

The CONSTRUCTOR's zero is still the default, not off. At startup the settings
have not been read yet, and beginning with no cache would hammer the provider
for the first seconds of every launch. Only SetTTL, called once the operator's
settings are known, can switch it off.

A negative lifetime is meaningless and is ignored rather than rounded into
either meaning.

The input had to change too: it derived its value from the stored number on
every keystroke, so the box could not be emptied — and 0 was unreachable
outright, since parseInt('0') || 30 is 30.
This commit is contained in:
2026-08-17 09:21:12 +02:00
parent 72ec3cbb97
commit 3f95763ca6
6 changed files with 148 additions and 16 deletions
+18 -7
View File
@@ -2670,8 +2670,14 @@ func (a *App) reloadLookupProviders() {
fmt.Println("OpsLog: settings load error:", err)
return
}
if days, _ := strconv.Atoi(m[keyCacheTTL]); days > 0 {
a.cache.SetTTL(time.Duration(days) * 24 * time.Hour)
// An EXPLICIT zero switches the cache off; an ABSENT key leaves the thirty-day
// default the cache was built with. The difference matters: every operator who
// has never opened this setting has no value stored, and reading that blank as
// a zero would silently turn the cache off for all of them.
if raw := strings.TrimSpace(m[keyCacheTTL]); raw != "" {
if days, err := strconv.Atoi(raw); err == nil && days >= 0 {
a.cache.SetTTL(time.Duration(days) * 24 * time.Hour)
}
}
build := func(name string) lookup.Provider {
@@ -7367,9 +7373,13 @@ func (a *App) GetLookupSettings() (LookupSettings, error) {
if err != nil {
return LookupSettings{}, err
}
ttl, _ := strconv.Atoi(m[keyCacheTTL])
if ttl <= 0 {
ttl = 30
// Same rule as reloadLookupProviders: blank means "never set" and gets the
// default, while a stored zero is the operator asking for no cache at all.
ttl := 30
if raw := strings.TrimSpace(m[keyCacheTTL]); raw != "" {
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
ttl = n
}
}
return LookupSettings{
QRZUser: m[keyQRZUser],
@@ -7389,8 +7399,9 @@ func (a *App) SaveLookupSettings(s LookupSettings) error {
if a.settings == nil {
return fmt.Errorf("db not initialized")
}
if s.CacheTTLDays <= 0 {
s.CacheTTLDays = 30
// Zero is kept — it means no cache. Only a negative number is nonsense.
if s.CacheTTLDays < 0 {
s.CacheTTLDays = 0
}
// Reject a primary == failsafe routing combo — would just hit the same
// provider twice. Frontend should prevent this but defend in depth.