store.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package captcha
  2. import (
  3. "container/list"
  4. "sync"
  5. "time"
  6. )
  7. // An object implementing Store interface can be registered with SetCustomStore
  8. // function to handle storage and retrieval of captcha ids and solutions for
  9. // them, replacing the default memory store.
  10. type Store interface {
  11. // Set sets the digits for the captcha id.
  12. Set(id string, digits []byte)
  13. // Get returns stored digits for the captcha id. Clear indicates
  14. // whether the captcha must be deleted from the store.
  15. Get(id string, clear bool) (digits []byte)
  16. // Collect deletes expired captchas from the store. For custom stores
  17. // this method is not called automatically, it is only wired to the
  18. // package's Collect function. Custom stores must implement their own
  19. // procedure for calling Collect, for example, in Set method.
  20. Collect()
  21. }
  22. // expValue stores timestamp and id of captchas. It is used in the list inside
  23. // memoryStore for indexing generated captchas by timestamp to enable garbage
  24. // collection of expired captchas.
  25. type idByTimeValue struct {
  26. timestamp int64
  27. id string
  28. }
  29. // memoryStore is an internal store for captcha ids and their values.
  30. type memoryStore struct {
  31. mu sync.RWMutex
  32. digitsById map[string][]byte
  33. idByTime *list.List
  34. // Number of items stored since last collection.
  35. numStored int
  36. // Number of saved items that triggers collection.
  37. collectNum int
  38. // Expiration time of captchas.
  39. expiration int64
  40. }
  41. // NewMemoryStore returns a new standard memory store for captchas with the
  42. // given collection threshold and expiration time in seconds. The returned
  43. // store must be registered with SetCustomStore to replace the default one.
  44. func NewMemoryStore(collectNum int, expiration int64) Store {
  45. s := new(memoryStore)
  46. s.digitsById = make(map[string][]byte)
  47. s.idByTime = list.New()
  48. s.collectNum = collectNum
  49. s.expiration = expiration
  50. return s
  51. }
  52. func (s *memoryStore) Set(id string, digits []byte) {
  53. s.mu.Lock()
  54. s.digitsById[id] = digits
  55. s.idByTime.PushBack(idByTimeValue{time.Seconds(), id})
  56. s.numStored++
  57. if s.numStored <= s.collectNum {
  58. s.mu.Unlock()
  59. return
  60. }
  61. s.mu.Unlock()
  62. go s.Collect()
  63. }
  64. func (s *memoryStore) Get(id string, clear bool) (digits []byte) {
  65. if !clear {
  66. // When we don't need to clear captcha, acquire read lock.
  67. s.mu.RLock()
  68. defer s.mu.RUnlock()
  69. } else {
  70. s.mu.Lock()
  71. defer s.mu.Unlock()
  72. }
  73. digits, ok := s.digitsById[id]
  74. if !ok {
  75. return
  76. }
  77. if clear {
  78. s.digitsById[id] = nil, false
  79. // XXX(dchest) Index (s.idByTime) will be cleaned when
  80. // collecting expired captchas. Can't clean it here, because
  81. // we don't store reference to expValue in the map.
  82. // Maybe store it?
  83. }
  84. return
  85. }
  86. func (s *memoryStore) Collect() {
  87. now := time.Seconds()
  88. s.mu.Lock()
  89. defer s.mu.Unlock()
  90. s.numStored = 0
  91. for e := s.idByTime.Front(); e != nil; {
  92. ev, ok := e.Value.(idByTimeValue)
  93. if !ok {
  94. return
  95. }
  96. if ev.timestamp+s.expiration < now {
  97. s.digitsById[ev.id] = nil, false
  98. next := e.Next()
  99. s.idByTime.Remove(e)
  100. e = next
  101. } else {
  102. return
  103. }
  104. }
  105. }