dispatch/server/session.go
2018-04-26 21:32:21 +02:00

254 lines
4.7 KiB
Go

package server
import (
"crypto/rand"
"encoding/base64"
"sync"
"time"
"fmt"
"github.com/khlieng/dispatch/irc"
"github.com/khlieng/dispatch/storage"
)
const (
AnonymousSessionExpiration = 1 * time.Minute
)
type Session struct {
irc map[string]*irc.Client
connectionState map[string]irc.ConnectionState
ircLock sync.Mutex
ws map[string]*wsConn
wsLock sync.Mutex
broadcast chan WSResponse
id string
user *storage.User
expiration *time.Timer
reset chan time.Duration
}
func NewSession(user *storage.User) (*Session, error) {
id, err := newSessionID()
if err != nil {
return nil, err
}
return &Session{
irc: make(map[string]*irc.Client),
connectionState: make(map[string]irc.ConnectionState),
ws: make(map[string]*wsConn),
broadcast: make(chan WSResponse, 32),
id: id,
user: user,
expiration: time.NewTimer(AnonymousSessionExpiration),
reset: make(chan time.Duration, 1),
}, nil
}
func newSessionID() (string, error) {
key := make([]byte, 32)
_, err := rand.Read(key)
return base64.RawURLEncoding.EncodeToString(key), err
}
func (s *Session) getIRC(server string) (*irc.Client, bool) {
s.ircLock.Lock()
i, ok := s.irc[server]
s.ircLock.Unlock()
return i, ok
}
func (s *Session) setIRC(server string, i *irc.Client) {
s.ircLock.Lock()
s.irc[server] = i
s.connectionState[server] = irc.ConnectionState{
Connected: false,
}
s.ircLock.Unlock()
s.reset <- 0
}
func (s *Session) deleteIRC(server string) {
s.ircLock.Lock()
delete(s.irc, server)
delete(s.connectionState, server)
s.ircLock.Unlock()
s.resetExpirationIfEmpty()
}
func (s *Session) numIRC() int {
s.ircLock.Lock()
n := len(s.irc)
s.ircLock.Unlock()
return n
}
func (s *Session) getConnectionStates() map[string]irc.ConnectionState {
s.ircLock.Lock()
state := make(map[string]irc.ConnectionState, len(s.connectionState))
for k, v := range s.connectionState {
state[k] = v
}
s.ircLock.Unlock()
return state
}
func (s *Session) setConnectionState(server string, state irc.ConnectionState) {
s.ircLock.Lock()
s.connectionState[server] = state
s.ircLock.Unlock()
}
func (s *Session) setWS(addr string, w *wsConn) {
s.wsLock.Lock()
s.ws[addr] = w
s.wsLock.Unlock()
s.reset <- 0
}
func (s *Session) deleteWS(addr string) {
s.wsLock.Lock()
delete(s.ws, addr)
s.wsLock.Unlock()
s.resetExpirationIfEmpty()
}
func (s *Session) numWS() int {
s.ircLock.Lock()
n := len(s.ws)
s.ircLock.Unlock()
return n
}
func (s *Session) sendJSON(t string, v interface{}) {
s.broadcast <- WSResponse{t, v}
}
func (s *Session) sendError(err error, server string) {
s.sendJSON("error", Error{
Server: server,
Message: err.Error(),
})
}
func (s *Session) sendLastMessages(server, channel string, count int) {
messages, hasMore, err := s.user.GetLastMessages(server, channel, count)
if err == nil && len(messages) > 0 {
res := Messages{
Server: server,
To: channel,
Messages: messages,
}
if hasMore {
res.Next = messages[0].ID
}
s.sendJSON("messages", res)
}
}
func (s *Session) sendMessages(server, channel string, count int, fromID string) {
messages, hasMore, err := s.user.GetMessages(server, channel, count, fromID)
if err == nil && len(messages) > 0 {
res := Messages{
Server: server,
To: channel,
Messages: messages,
Prepend: true,
}
if hasMore {
res.Next = messages[0].ID
}
s.sendJSON("messages", res)
}
}
func (s *Session) print(a ...interface{}) {
s.sendJSON("print", Message{
Content: fmt.Sprintln(a...),
})
}
func (s *Session) printError(a ...interface{}) {
s.sendJSON("print", Message{
Content: fmt.Sprintln(a...),
Type: "error",
})
}
func (s *Session) resetExpirationIfEmpty() {
if s.numIRC() == 0 && s.numWS() == 0 {
s.reset <- AnonymousSessionExpiration
}
}
func (s *Session) run() {
for {
select {
case res := <-s.broadcast:
s.wsLock.Lock()
for _, ws := range s.ws {
ws.out <- res
}
s.wsLock.Unlock()
case <-s.expiration.C:
sessions.delete(s.id)
s.user.Remove()
return
case duration := <-s.reset:
if duration == 0 {
s.expiration.Stop()
} else {
s.expiration.Reset(duration)
}
}
}
}
type sessionStore struct {
sessions map[string]*Session
lock sync.Mutex
}
func newSessionStore() *sessionStore {
return &sessionStore{
sessions: make(map[string]*Session),
}
}
func (s *sessionStore) get(id string) *Session {
s.lock.Lock()
session := s.sessions[id]
s.lock.Unlock()
return session
}
func (s *sessionStore) set(session *Session) {
s.lock.Lock()
s.sessions[session.id] = session
s.lock.Unlock()
}
func (s *sessionStore) delete(id string) {
s.lock.Lock()
delete(s.sessions, id)
s.lock.Unlock()
}