store.go 3.1 KB

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