store.go (view raw)
1package captcha
2
3import (
4 "container/list"
5 "sync"
6 "time"
7)
8
9// expValue stores timestamp and id of captchas. It is used in a list inside
10// store for indexing generated captchas by timestamp to enable garbage
11// collection of expired captchas.
12type expValue struct {
13 timestamp int64
14 id string
15}
16
17// store is an internal store for captcha ids and their values.
18type store struct {
19 mu sync.RWMutex
20 ids map[string][]byte
21 exp *list.List
22 // Number of items stored after last collection.
23 numStored int
24 // Number of saved items that triggers collection.
25 collectNum int
26 // Expiration time of captchas.
27 expiration int64
28}
29
30// newStore initializes and returns a new store.
31func newStore(collectNum int, expiration int64) *store {
32 s := new(store)
33 s.ids = make(map[string][]byte)
34 s.exp = list.New()
35 s.collectNum = collectNum
36 s.expiration = expiration
37 return s
38}
39
40// saveCaptcha saves the captcha id and the corresponding digits.
41func (s *store) saveCaptcha(id string, digits []byte) {
42 s.mu.Lock()
43 s.ids[id] = digits
44 s.exp.PushBack(expValue{time.Seconds(), id})
45 s.numStored++
46 s.mu.Unlock()
47 if s.numStored > s.collectNum {
48 go s.collect()
49 }
50}
51
52// getDigits returns the digits for the given id.
53func (s *store) getDigits(id string) (digits []byte) {
54 s.mu.RLock()
55 defer s.mu.RUnlock()
56 digits, _ = s.ids[id]
57 return
58}
59
60// getDigitsClear returns the digits for the given id, and removes them from
61// the store.
62func (s *store) getDigitsClear(id string) (digits []byte) {
63 s.mu.Lock()
64 defer s.mu.Unlock()
65 digits, ok := s.ids[id]
66 if !ok {
67 return
68 }
69 s.ids[id] = nil, false
70 // XXX(dchest) Index (s.exp) will be cleaned when collecting expired
71 // captchas. Can't clean it here, because we don't store reference to
72 // expValue in the map. Maybe store it?
73 return
74}
75
76// collect deletes expired captchas from the store.
77func (s *store) collect() {
78 now := time.Seconds()
79 s.mu.Lock()
80 defer s.mu.Unlock()
81 s.numStored = 0
82 for e := s.exp.Front(); e != nil; {
83 ev, ok := e.Value.(expValue)
84 if !ok {
85 return
86 }
87 if ev.timestamp+s.expiration < now {
88 s.ids[ev.id] = nil, false
89 next := e.Next()
90 s.exp.Remove(e)
91 e = next
92 } else {
93 return
94 }
95 }
96}