1package libkb
2
3import (
4	"sync"
5	"time"
6
7	"github.com/keybase/client/go/protocol/keybase1"
8	context "golang.org/x/net/context"
9	"golang.org/x/time/rate"
10)
11
12type TeamMemberCountCache struct {
13	g        *GlobalContext
14	notifyCh chan struct{}
15
16	lock  sync.RWMutex
17	cache map[keybase1.TeamID]int
18}
19
20func newTeamMemberCountCache(g *GlobalContext) *TeamMemberCountCache {
21	cache := &TeamMemberCountCache{
22		cache:    make(map[keybase1.TeamID]int),
23		g:        g,
24		notifyCh: make(chan struct{}, 1),
25	}
26	cache.startNotifyLoop()
27	g.AddLogoutHook(cache, "TeamMemberCountCache")
28	return cache
29}
30
31func (c *TeamMemberCountCache) OnLogout(mctx MetaContext) error {
32	mctx.Debug("TeamMemberCountCache OnLogout: clearing cache")
33	c.lock.Lock()
34	defer c.lock.Unlock()
35	c.cache = make(map[keybase1.TeamID]int)
36	return nil
37}
38
39func (c *TeamMemberCountCache) startNotifyLoop() {
40	ctx, cancel := context.WithCancel(context.Background())
41	c.g.PushShutdownHook(func(mctx MetaContext) error {
42		mctx.Debug("TeamMemberCountCache shutdown")
43		cancel()
44		return nil
45	})
46	go c.notifyLoop(ctx)
47}
48
49func (c *TeamMemberCountCache) notifyLoop(ctx context.Context) {
50	const notifyInterval = 5 * time.Second
51	const notifyTimeout = 10 * time.Second
52	limiter := rate.NewLimiter(rate.Every(notifyInterval), 1)
53	for {
54		if err := limiter.Wait(ctx); err != nil {
55			return
56		}
57		select {
58		case <-c.notifyCh:
59			ctx, cancel := context.WithTimeout(context.Background(), notifyTimeout)
60			c.g.NotifyRouter.HandleTeamMetadataUpdate(ctx)
61			cancel()
62		case <-ctx.Done():
63			return
64		}
65	}
66}
67
68func (c *TeamMemberCountCache) Set(teamID keybase1.TeamID, count int) {
69	c.lock.Lock()
70	defer c.lock.Unlock()
71	if c, ok := c.cache[teamID]; ok && c == count {
72		return
73	}
74	c.cache[teamID] = count
75	select {
76	case c.notifyCh <- struct{}{}:
77	default:
78	}
79}
80
81func (c *TeamMemberCountCache) Get(teamID keybase1.TeamID) (count int, ok bool) {
82	c.lock.RLock()
83	defer c.lock.RUnlock()
84	count, ok = c.cache[teamID]
85	return count, ok
86}
87
88func (c *TeamMemberCountCache) GetWithFallback(teamID keybase1.TeamID, fallback int) (count int) {
89	count, ok := c.Get(teamID)
90	if ok {
91		return count
92	}
93	return fallback
94}
95