Fixed message buffer timeout
[ssh-chat] / chat / message / user.go
1 package message
2
3 import (
4         "errors"
5         "fmt"
6         "io"
7         "math/rand"
8         "regexp"
9         "sync"
10         "time"
11 )
12
13 const messageBuffer = 5
14 const messageTimeout = 5 * time.Second
15 const reHighlight = `\b(%s)\b`
16
17 var ErrUserClosed = errors.New("user closed")
18
19 // User definition, implemented set Item interface and io.Writer
20 type User struct {
21         Identifier
22         Config   UserConfig
23         colorIdx int
24         joined   time.Time
25         msg      chan Message
26         done     chan struct{}
27
28         replyTo   *User // Set when user gets a /msg, for replying.
29         screen    io.WriteCloser
30         closeOnce sync.Once
31 }
32
33 func NewUser(identity Identifier) *User {
34         u := User{
35                 Identifier: identity,
36                 Config:     DefaultUserConfig,
37                 joined:     time.Now(),
38                 msg:        make(chan Message, messageBuffer),
39                 done:       make(chan struct{}),
40         }
41         u.SetColorIdx(rand.Int())
42
43         return &u
44 }
45
46 func NewUserScreen(identity Identifier, screen io.WriteCloser) *User {
47         u := NewUser(identity)
48         u.screen = screen
49
50         return u
51 }
52
53 // Rename the user with a new Identifier.
54 func (u *User) SetId(id string) {
55         u.Identifier.SetId(id)
56         u.SetColorIdx(rand.Int())
57 }
58
59 // ReplyTo returns the last user that messaged this user.
60 func (u *User) ReplyTo() *User {
61         return u.replyTo
62 }
63
64 // SetReplyTo sets the last user to message this user.
65 func (u *User) SetReplyTo(user *User) {
66         u.replyTo = user
67 }
68
69 // ToggleQuietMode will toggle whether or not quiet mode is enabled
70 func (u *User) ToggleQuietMode() {
71         u.Config.Quiet = !u.Config.Quiet
72 }
73
74 // SetColorIdx will set the colorIdx to a specific value, primarily used for
75 // testing.
76 func (u *User) SetColorIdx(idx int) {
77         u.colorIdx = idx
78 }
79
80 // Block until user is closed
81 func (u *User) Wait() {
82         <-u.done
83 }
84
85 // Disconnect user, stop accepting messages
86 func (u *User) Close() {
87         u.closeOnce.Do(func() {
88                 if u.screen != nil {
89                         u.screen.Close()
90                 }
91                 // close(u.msg) TODO: Close?
92                 close(u.done)
93         })
94 }
95
96 // Consume message buffer into the handler. Will block, should be called in a
97 // goroutine.
98 func (u *User) Consume() {
99         for {
100                 select {
101                 case <-u.done:
102                         return
103                 case m, ok := <-u.msg:
104                         if !ok {
105                                 return
106                         }
107                         u.HandleMsg(m)
108                 }
109         }
110 }
111
112 // Consume one message and stop, mostly for testing
113 func (u *User) ConsumeOne() Message {
114         return <-u.msg
115 }
116
117 // SetHighlight sets the highlighting regular expression to match string.
118 func (u *User) SetHighlight(s string) error {
119         re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
120         if err != nil {
121                 return err
122         }
123         u.Config.Highlight = re
124         return nil
125 }
126
127 func (u *User) render(m Message) string {
128         switch m := m.(type) {
129         case *PublicMsg:
130                 return m.RenderFor(u.Config) + Newline
131         case *PrivateMsg:
132                 u.SetReplyTo(m.From())
133                 return m.Render(u.Config.Theme) + Newline
134         default:
135                 return m.Render(u.Config.Theme) + Newline
136         }
137 }
138
139 // HandleMsg will render the message to the screen, blocking.
140 func (u *User) HandleMsg(m Message) error {
141         r := u.render(m)
142         _, err := u.screen.Write([]byte(r))
143         if err != nil {
144                 logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
145                 u.Close()
146         }
147         return err
148 }
149
150 // Add message to consume by user
151 func (u *User) Send(m Message) error {
152         select {
153         case u.msg <- m:
154         case <-u.done:
155                 return ErrUserClosed
156         case <-time.After(messageTimeout):
157                 logger.Printf("Message buffer full, closing: %s", u.Name())
158                 u.Close()
159                 return ErrUserClosed
160         }
161         return nil
162 }
163
164 // Container for per-user configurations.
165 type UserConfig struct {
166         Highlight *regexp.Regexp
167         Bell      bool
168         Quiet     bool
169         Theme     *Theme
170 }
171
172 // Default user configuration to use
173 var DefaultUserConfig UserConfig
174
175 func init() {
176         DefaultUserConfig = UserConfig{
177                 Bell:  true,
178                 Quiet: false,
179         }
180
181         // TODO: Seed random?
182 }