func (s *Store) FindChannel(netID, name string) (channel *StoredChannel,
	err error) {
	ch := StoredChannel{NetID: netID, Name: name}
	key := ch.makeID()
	var serialized []byte
	serialized, err = s.db.Get(nil, []byte(key))
	if err != nil || serialized == nil {
		return
	}
	channel, err = deserializeChannel(serialized)
	return
}
FindChannel looks up a channel based on name.
package data
import (
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
	"time"
	"github.com/cznic/kv"
)
var defaultTimeout = time.Minute * 5
type AuthFailure int
const (
	AuthErrBadPassword AuthFailure = iota + 1
	AuthErrHostNotFound
	AuthErrUserNotFound
)
const (
	errFmtUserNotFound = "User [%v] not found."
	errFmtBadPassword = "Password does not match for user [%v]."
	errFmtBadHost = "Host [%v] does not match stored hosts for user [%v]."
)
type AuthError struct {
	str         string
	FailureType AuthFailure
}
func (a AuthError) Error() string {
	return a.str
}
var (
	nMaxCache = 1000
	isInitialized = []byte{0}
)
type DbProvider func() (*kv.DB, error)
type Store struct {
	db *kv.DB
	protect  sync.Mutex
	cache    map[string]*StoredUser
	authed   map[string]string
	timeouts map[string]time.Time
}
func NewStore(prov DbProvider) (*Store, error) {
	db, err := prov()
	if err != nil {
		return nil, err
	}
	s := &Store{
		db:       db,
		cache:    make(map[string]*StoredUser),
		authed:   make(map[string]string),
		timeouts: make(map[string]time.Time),
	}
	return s, nil
}
func (s *Store) Close() error {
	return s.db.Close()
}
func (s *Store) GlobalUsers() ([]*StoredUser, error) {
	return iterate(s.db, func(ua *StoredUser) bool {
		a, ok := ua.GetAccess("", "")
		return ok && !a.IsZero()
	})
}
func (s *Store) NetworkUsers(network string) ([]*StoredUser, error) {
	return iterate(s.db, func(ua *StoredUser) bool {
		a, ok := ua.GetAccess(network, "")
		return ok && !a.IsZero()
	})
}
func (s *Store) ChanUsers(network, channel string) ([]*StoredUser, error) {
	return iterate(s.db, func(ua *StoredUser) bool {
		a, ok := ua.GetAccess(network, channel)
		return ok && !a.IsZero()
	})
}
func iterate(db *kv.DB, filter func(*StoredUser) bool) ([]*StoredUser, error) {
	list := make([]*StoredUser, 0)
	e, err := db.SeekFirst()
	switch {
	case err == io.EOF:
		return nil, nil
	case err != nil:
		return nil, err
	}
	var stop error
	var val []byte
	for ; stop == nil; _, val, stop = e.Next() {
		if ua, err := deserializeUser(val); err == nil && filter(ua) {
			list = append(list, ua)
		}
	}
	return list, nil
}
func (s *Store) SaveUser(ua *StoredUser) error {
	var err error
	var serialized []byte
	serialized, err = ua.serialize()
	if err != nil {
		return err
	}
	s.db.Set([]byte(ua.Username), serialized)
	if err != nil {
		return err
	}
	s.protect.Lock()
	s.checkCacheLimits()
	s.cache[ua.Username] = ua.Clone()
	s.protect.Unlock()
	return nil
}
func (s *Store) RemoveUser(username string) (removed bool, err error) {
	username = strings.ToLower(username)
	var exists *StoredUser
	s.protect.Lock()
	defer s.protect.Unlock()
	exists, err = s.findUser(username)
	if err != nil || exists == nil {
		return
	}
	delete(s.cache, username)
	err = s.db.Delete([]byte(username))
	if err != nil {
		return
	}
	removed = true
	return
}
func (s *Store) AuthUserTmp(
	network, host, username, password string) (*StoredUser, error) {
	return s.authUser(network, host, username, password, true)
}
func (s *Store) AuthUserPerma(
	network, host, username, password string) (*StoredUser, error) {
	return s.authUser(network, host, username, password, false)
}
func (s *Store) authUser(
	network, host, username, password string, temp bool) (*StoredUser, error) {
	username = strings.ToLower(username)
	var user *StoredUser
	var err error
	s.protect.Lock()
	defer s.protect.Unlock()
	if uname, ok := s.authed[network+host]; ok {
		return s.findUser(uname)
	}
	user, err = s.findUser(username)
	if err != nil {
		return nil, err
	}
	if user == nil {
		return nil, AuthError{
			fmt.Sprintf(errFmtUserNotFound, username),
			AuthErrUserNotFound,
		}
	}
	if !user.HasMask(host) {
		return nil, AuthError{
			fmt.Sprintf(errFmtBadHost, host, username),
			AuthErrHostNotFound,
		}
	}
	if !user.VerifyPassword(password) {
		return nil, AuthError{
			fmt.Sprintf(errFmtBadPassword, username),
			AuthErrBadPassword,
		}
	}
	if temp {
		s.timeouts[network+host] = time.Now().UTC().Add(defaultTimeout)
	}
	s.authed[network+host] = username
	return user.Clone(), nil
}
func (s *Store) AuthedUser(network, host string) *StoredUser {
	s.protect.Lock()
	defer s.protect.Unlock()
	if username, ok := s.authed[network+host]; ok {
		user, _ := s.findUser(username)
		return user
	}
	return nil
}
func (s *Store) Logout(network, host string) {
	s.protect.Lock()
	defer s.protect.Unlock()
	delete(s.authed, network+host)
}
func (s *Store) LogoutByUsername(username string) {
	s.protect.Lock()
	defer s.protect.Unlock()
	username = strings.ToLower(username)
	var hosts []string
	for host, uname := range s.authed {
		if uname == username {
			hosts = append(hosts, host)
		}
	}
	for _, h := range hosts {
		delete(s.authed, h)
	}
}
func (s *Store) Update(network string, update StateUpdate) {
	s.protect.Lock()
	defer s.protect.Unlock()
	for _, seen := range update.Seen {
		delete(s.timeouts, network+seen)
	}
	for _, unseen := range update.Unseen {
		if _, ok := s.timeouts[network+unseen]; !ok {
			s.timeouts[network+unseen] = time.Now().UTC().Add(defaultTimeout)
		}
	}
	if len(update.Nick) > 0 {
		s.authed[network+update.Nick[1]] = s.authed[network+update.Nick[0]]
		delete(s.timeouts, network+update.Nick[0])
		delete(s.authed, network+update.Nick[0])
	}
	if len(update.Quit) > 0 {
		delete(s.timeouts, network+update.Quit)
		delete(s.authed, network+update.Quit)
	}
	s.reap()
}
func (s *Store) reap() {
	for key, date := range s.timeouts {
		if time.Now().UTC().After(date) {
			delete(s.authed, key)
			delete(s.timeouts, key)
		}
	}
}
func (s *Store) FindUser(username string) (user *StoredUser, err error) {
	s.protect.Lock()
	defer s.protect.Unlock()
	return s.findUser(username)
}
func (s *Store) findUser(username string) (user *StoredUser, err error) {
	username = strings.ToLower(username)
	if cached, ok := s.cache[username]; ok {
		user = cached.Clone()
		return user, nil
	}
	user, err = s.fetchUser(username)
	if user == nil || err != nil {
		return user, err
	}
	s.checkCacheLimits()
	s.cache[username] = user.Clone()
	return
}
func (s *Store) fetchUser(username string) (user *StoredUser, err error) {
	username = strings.ToLower(username)
	var serialized []byte
	serialized, err = s.db.Get(nil, []byte(username))
	if err != nil || serialized == nil {
		return
	}
	user, err = deserializeUser(serialized)
	return
}
func (s *Store) SaveChannel(sc *StoredChannel) error {
	var err error
	var serialized []byte
	serialized, err = sc.serialize()
	if err != nil {
		return err
	}
	s.db.Set([]byte(sc.makeID()), serialized)
	if err != nil {
		return err
	}
	return nil
}
func (s *Store) RemoveChannel(netID, name string) (removed bool, err error) {
	var exists *StoredChannel
	exists, err = s.FindChannel(netID, name)
	if err != nil || exists == nil {
		return
	}
	ch := StoredChannel{NetID: netID, Name: name}
	key := ch.makeID()
	err = s.db.Delete([]byte(key))
	if err != nil {
		return
	}
	removed = true
	return
}