justcaptcha/pkg/captcha/inmemdb/inmemdb.go

160 lines
3.9 KiB
Go

package inmemdb
import (
"image"
"sync"
"time"
"git.arav.su/Arav/justcaptcha/pkg/captcha"
)
// InMemoryCaptchaDB implementation that lives in a memory (map).
type InMemoryCaptchaDB struct {
sync.Mutex
db map[captcha.ID]captcha.Captcha
expiry time.Duration
expiryScanInterval time.Duration
}
// NewInMemoryCaptchaDB returns an initialised instance of an InMemoryCaptchaDB.
// An expiry is a scan interval for expired CAPTCHAs (if passed a longer one,
// resets to a default (captcha.DefaultExpiredScanInterval)).
func NewInMemoryCaptchaDB(expiry time.Duration) *InMemoryCaptchaDB {
db := &InMemoryCaptchaDB{
db: make(map[captcha.ID]captcha.Captcha),
expiry: expiry}
if expiry < captcha.DefaultExpiredScanInterval {
db.expiryScanInterval = expiry
} else {
db.expiryScanInterval = captcha.DefaultExpiredScanInterval
}
go db.cleanExpired()
return db
}
// New accepts a CAPTCHA instance, generates an ID and store it in a database.
// A data string is an additional random data used to generate an ID,
// e.g. an IP-address.
func (imcdb *InMemoryCaptchaDB) New(data string, cptcha captcha.Captcha) (captcha.Captcha, captcha.ID) {
id := captcha.NewID(data, cptcha.Answer())
imcdb.Lock()
imcdb.db[id] = cptcha
imcdb.Unlock()
return cptcha, id
}
// GetExpiry returns an expiry for a CAPTCHA.
func (imcdb *InMemoryCaptchaDB) GetExpiry() time.Duration {
return imcdb.expiry
}
// SetExpiry changes an expiry for a CAPTCHA and a scan interval. Scan interval
// cannot be longer than a default, so if it is, then resets to a default.
func (imcdb *InMemoryCaptchaDB) SetExpiry(expiry time.Duration) {
imcdb.expiry = expiry
if expiry < captcha.DefaultExpiredScanInterval {
imcdb.expiryScanInterval = expiry
} else {
imcdb.expiryScanInterval = captcha.DefaultExpiredScanInterval
}
}
// Image returns a freshly generated image for a CAPTCHA with style if
// applicable.
func (imcdb *InMemoryCaptchaDB) Image(id captcha.ID, style string) *image.Image {
imcdb.Lock()
defer imcdb.Unlock()
if c, ok := imcdb.db[id]; ok {
return c.Image(style)
}
return nil
}
// Solve compares given answer with a stored one and if failed
// deletes a CAPTCHA from database.
func (imcdb *InMemoryCaptchaDB) Solve(id captcha.ID, answer captcha.Answer) bool {
imcdb.Lock()
defer imcdb.Unlock()
if c, ok := imcdb.db[id]; ok {
ok = c.Solve(answer)
if !ok {
delete(imcdb.db, id)
}
return ok
}
return false
}
// IsSolved checks if CAPTCHA was solved and removes it
// from a database.
func (imcdb *InMemoryCaptchaDB) IsSolved(id captcha.ID) bool {
imcdb.Lock()
defer imcdb.Unlock()
if c, ok := imcdb.db[id]; ok {
delete(imcdb.db, id)
return c.IsSolved()
}
return false
}
// Remove a CAPTCHA from a database.
func (imcdb *InMemoryCaptchaDB) Remove(id captcha.ID) {
imcdb.Lock()
defer imcdb.Unlock()
delete(imcdb.db, id)
}
// cleanExpired removes expired CAPTCHAs in a loop.
func (imcdb *InMemoryCaptchaDB) cleanExpired() {
for {
sleepFor := imcdb.expiryScanInterval - (time.Duration(time.Now().Second()) % imcdb.expiryScanInterval)
time.Sleep(sleepFor)
imcdb.Lock()
for id, captcha := range imcdb.db {
if time.Since(captcha.Expiry()) >= imcdb.expiry {
delete(imcdb.db, id)
}
}
imcdb.Unlock()
}
}
// An instance of InMemoryCaptchaDB
var imcdb = NewInMemoryCaptchaDB(captcha.DefaultExpiredScanInterval)
func GetExpiry() time.Duration {
return imcdb.GetExpiry()
}
func SetExpiry(expiry time.Duration) {
imcdb.SetExpiry(expiry)
}
func New(data string, captcha captcha.Captcha) (captcha.Captcha, captcha.ID) {
return imcdb.New(data, captcha)
}
func Image(id captcha.ID, style string) *image.Image {
return imcdb.Image(id, style)
}
func Solve(id captcha.ID, answer captcha.Answer) bool {
return imcdb.Solve(id, answer)
}
func IsSolved(id captcha.ID) bool {
return imcdb.IsSolved(id)
}
func Remove(id captcha.ID) {
imcdb.Remove(id)
}