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