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