2014-12-21 01:45:10 +01:00
|
|
|
package chat
|
|
|
|
|
2014-12-26 01:25:02 +01:00
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2014-12-26 21:11:03 +01:00
|
|
|
"sync"
|
2014-12-26 01:25:02 +01:00
|
|
|
)
|
2014-12-21 05:21:41 +01:00
|
|
|
|
2014-12-21 01:45:10 +01:00
|
|
|
const historyLen = 20
|
2014-12-23 06:47:07 +01:00
|
|
|
const channelBuffer = 10
|
2014-12-21 01:45:10 +01:00
|
|
|
|
2014-12-26 01:25:02 +01:00
|
|
|
var ErrChannelClosed = errors.New("channel closed")
|
|
|
|
|
2014-12-21 01:45:10 +01:00
|
|
|
// Channel definition, also a Set of User Items
|
|
|
|
type Channel struct {
|
2014-12-21 05:21:41 +01:00
|
|
|
topic string
|
|
|
|
history *History
|
|
|
|
users *Set
|
2014-12-23 06:47:07 +01:00
|
|
|
broadcast chan Message
|
2014-12-26 01:25:02 +01:00
|
|
|
commands Commands
|
|
|
|
closed bool
|
2014-12-26 21:11:03 +01:00
|
|
|
closeOnce *sync.Once
|
2014-12-21 01:45:10 +01:00
|
|
|
}
|
|
|
|
|
2014-12-23 06:47:07 +01:00
|
|
|
// Create new channel and start broadcasting goroutine.
|
2014-12-23 07:21:07 +01:00
|
|
|
func NewChannel() *Channel {
|
2014-12-23 06:47:07 +01:00
|
|
|
broadcast := make(chan Message, channelBuffer)
|
|
|
|
|
2014-12-26 01:25:02 +01:00
|
|
|
return &Channel{
|
2014-12-21 05:21:41 +01:00
|
|
|
broadcast: broadcast,
|
|
|
|
history: NewHistory(historyLen),
|
|
|
|
users: NewSet(),
|
2014-12-26 01:25:02 +01:00
|
|
|
commands: defaultCmdHandlers,
|
2014-12-21 01:45:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-23 06:47:07 +01:00
|
|
|
func (ch *Channel) Close() {
|
2014-12-26 21:11:03 +01:00
|
|
|
ch.closeOnce.Do(func() {
|
|
|
|
ch.closed = true
|
|
|
|
ch.users.Each(func(u Item) {
|
|
|
|
u.(*User).Close()
|
|
|
|
})
|
|
|
|
ch.users.Clear()
|
|
|
|
close(ch.broadcast)
|
2014-12-23 07:54:29 +01:00
|
|
|
})
|
2014-12-23 06:47:07 +01:00
|
|
|
}
|
|
|
|
|
2014-12-26 01:25:02 +01:00
|
|
|
// Handle a message, will block until done.
|
|
|
|
func (ch *Channel) handleMsg(m Message) {
|
2014-12-26 21:11:03 +01:00
|
|
|
switch m := m.(type) {
|
|
|
|
case *CommandMsg:
|
|
|
|
cmd := *m
|
|
|
|
err := ch.commands.Run(ch, cmd)
|
2014-12-26 01:25:02 +01:00
|
|
|
if err != nil {
|
|
|
|
m := NewSystemMsg(fmt.Sprintf("Err: %s", err), cmd.from)
|
|
|
|
go ch.handleMsg(m)
|
|
|
|
}
|
|
|
|
case MessageTo:
|
2014-12-26 21:11:03 +01:00
|
|
|
user := m.To()
|
2014-12-26 01:25:02 +01:00
|
|
|
user.Send(m)
|
|
|
|
default:
|
|
|
|
fromMsg, skip := m.(MessageFrom)
|
|
|
|
var skipUser *User
|
|
|
|
if skip {
|
|
|
|
skipUser = fromMsg.From()
|
|
|
|
}
|
|
|
|
|
|
|
|
ch.users.Each(func(u Item) {
|
|
|
|
user := u.(*User)
|
|
|
|
if skip && skipUser == user {
|
|
|
|
// Skip
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err := user.Send(m)
|
|
|
|
if err != nil {
|
|
|
|
ch.Leave(user)
|
|
|
|
user.Close()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serve will consume the broadcast channel and handle the messages, should be
|
|
|
|
// run in a goroutine.
|
|
|
|
func (ch *Channel) Serve() {
|
|
|
|
for m := range ch.broadcast {
|
|
|
|
go ch.handleMsg(m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-21 01:45:10 +01:00
|
|
|
func (ch *Channel) Send(m Message) {
|
2014-12-21 05:21:41 +01:00
|
|
|
ch.broadcast <- m
|
2014-12-21 01:45:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *Channel) Join(u *User) error {
|
2014-12-26 01:25:02 +01:00
|
|
|
if ch.closed {
|
|
|
|
return ErrChannelClosed
|
|
|
|
}
|
2014-12-21 01:45:10 +01:00
|
|
|
err := ch.users.Add(u)
|
2014-12-21 05:21:41 +01:00
|
|
|
if err != nil {
|
2014-12-21 19:17:14 +01:00
|
|
|
return err
|
2014-12-21 05:21:41 +01:00
|
|
|
}
|
2014-12-21 19:17:14 +01:00
|
|
|
s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), ch.users.Len())
|
2014-12-26 01:25:02 +01:00
|
|
|
ch.Send(NewAnnounceMsg(s))
|
2014-12-21 19:17:14 +01:00
|
|
|
return nil
|
2014-12-21 01:45:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *Channel) Leave(u *User) error {
|
|
|
|
err := ch.users.Remove(u)
|
2014-12-21 05:21:41 +01:00
|
|
|
if err != nil {
|
2014-12-21 19:17:14 +01:00
|
|
|
return err
|
2014-12-21 05:21:41 +01:00
|
|
|
}
|
2014-12-21 19:17:14 +01:00
|
|
|
s := fmt.Sprintf("%s left.", u.Name())
|
2014-12-26 01:25:02 +01:00
|
|
|
ch.Send(NewAnnounceMsg(s))
|
2014-12-21 19:17:14 +01:00
|
|
|
return nil
|
2014-12-21 01:45:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *Channel) Topic() string {
|
|
|
|
return ch.topic
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *Channel) SetTopic(s string) {
|
|
|
|
ch.topic = s
|
|
|
|
}
|
2014-12-25 09:37:50 +01:00
|
|
|
|
|
|
|
// NamesPrefix lists all members' names with a given prefix, used to query
|
|
|
|
// for autocompletion purposes.
|
|
|
|
func (ch *Channel) NamesPrefix(prefix string) []string {
|
|
|
|
users := ch.users.ListPrefix(prefix)
|
|
|
|
names := make([]string, len(users))
|
|
|
|
for i, u := range users {
|
|
|
|
names[i] = u.(*User).Name()
|
|
|
|
}
|
|
|
|
return names
|
|
|
|
}
|