package web
import (
"net/url"
"sync"
"time"
"golang.org/x/sync/singleflight"
)
// The home page's content is the expensive part of the page: it reaches
// into every tracker the reader can see, which is a few hundred
// milliseconds warm and seconds when those indexes are cold. The page
// must arrive at once and never blank, so the content is remembered per
// reader and view, drawn from memory on the next visit, and brought up
// to date in place; and a page with nothing remembered gives the content
// dashBudget to arrive with it before streaming it in.
// dashBudget is how long a page with nothing remembered waits for its
// content before arriving without it.
const dashBudget = 150 * time.Millisecond
// dashKeep is how long a remembered dashboard may be drawn. It is
// replaced at once by the fresh one; the bound is on what a reader can
// glimpse of a view that has since changed under them (a repository
// they no longer reach, say).
const dashKeep = 10 * time.Minute
// dashCacheMax bounds the remembered dashboards; past it the memory is
// cleared rather than grown.
const dashCacheMax = 4096
type dashEntry struct {
html string
at time.Time
}
var (
dashFlight singleflight.Group
dashMu sync.Mutex
dashCache = map[string]dashEntry{}
)
// dashKey names one reader's view: the account and the query, without
// the fragment marker that asked for it.
func dashKey(user string, q url.Values) string {
v := url.Values{}
for k, vs := range q {
if k != "frag" {
v[k] = vs
}
}
return user + "\x00" + v.Encode()
}
func dashRemember(key, html string) {
dashMu.Lock()
defer dashMu.Unlock()
if len(dashCache) >= dashCacheMax {
clear(dashCache)
}
dashCache[key] = dashEntry{html: html, at: time.Now()}
}
func dashRecall(key string) (string, bool) {
dashMu.Lock()
defer dashMu.Unlock()
e, ok := dashCache[key]
if !ok || time.Since(e.at) > dashKeep {
return "", false
}
return e.html, true
}