Disabled autocomplete due to #166.
[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         "github.com/shazow/ssh-chat/common"
13 )
14
15 const messageBuffer = 5
16 const messageTimeout = 5 * time.Second
17 const reHighlight = `\b(%s)\b`
18
19 var ErrUserClosed = errors.New("user closed")
20
21 // User definition, implemented set Item interface and io.Writer
22 type User struct {
23         Identifier
24         Config   UserConfig
25         colorIdx int
26         joined   time.Time
27         msg      chan Message
28         done     chan struct{}
29         Ignored  *common.IdSet
30
31         screen    io.WriteCloser
32         closeOnce sync.Once
33
34         mu      sync.Mutex
35         replyTo *User // Set when user gets a /msg, for replying.
36 }
37
38 func NewUser(identity Identifier) *User {
39         u := User{
40                 Identifier: identity,
41                 Config:     DefaultUserConfig,
42                 joined:     time.Now(),
43                 msg:        make(chan Message, messageBuffer),
44                 done:       make(chan struct{}),
45                 Ignored:    common.NewIdSet(),
46         }
47         u.SetColorIdx(rand.Int())
48
49         return &u
50 }
51
52 func NewUserScreen(identity Identifier, screen io.WriteCloser) *User {
53         u := NewUser(identity)
54         u.screen = screen
55
56         return u
57 }
58
59 // Rename the user with a new Identifier.
60 func (u *User) SetId(id string) {
61         u.Identifier.SetId(id)
62         u.SetColorIdx(rand.Int())
63 }
64
65 // ReplyTo returns the last user that messaged this user.
66 func (u *User) ReplyTo() *User {
67         u.mu.Lock()
68         defer u.mu.Unlock()
69         return u.replyTo
70 }
71
72 // SetReplyTo sets the last user to message this user.
73 func (u *User) SetReplyTo(user *User) {
74         u.mu.Lock()
75         defer u.mu.Unlock()
76         u.replyTo = user
77 }
78
79 // ToggleQuietMode will toggle whether or not quiet mode is enabled
80 func (u *User) ToggleQuietMode() {
81         u.Config.Quiet = !u.Config.Quiet
82 }
83
84 // SetColorIdx will set the colorIdx to a specific value, primarily used for
85 // testing.
86 func (u *User) SetColorIdx(idx int) {
87         u.colorIdx = idx
88 }
89
90 // Block until user is closed
91 func (u *User) Wait() {
92         <-u.done
93 }
94
95 // Disconnect user, stop accepting messages
96 func (u *User) Close() {
97         u.closeOnce.Do(func() {
98                 if u.screen != nil {
99                         u.screen.Close()
100                 }
101                 // close(u.msg) TODO: Close?
102                 close(u.done)
103         })
104 }
105
106 // Consume message buffer into the handler. Will block, should be called in a
107 // goroutine.
108 func (u *User) Consume() {
109         for {
110                 select {
111                 case <-u.done:
112                         return
113                 case m, ok := <-u.msg:
114                         if !ok {
115                                 return
116                         }
117                         u.HandleMsg(m)
118                 }
119         }
120 }
121
122 // Consume one message and stop, mostly for testing
123 func (u *User) ConsumeOne() Message {
124         return <-u.msg
125 }
126
127 // Check if there are pending messages, used for testing
128 func (u *User) HasMessages() bool {
129         select {
130         case msg := <-u.msg:
131                 u.msg <- msg
132                 return true
133         default:
134                 return false
135         }
136 }
137
138 // SetHighlight sets the highlighting regular expression to match string.
139 func (u *User) SetHighlight(s string) error {
140         re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
141         if err != nil {
142                 return err
143         }
144         u.Config.Highlight = re
145         return nil
146 }
147
148 func (u *User) render(m Message) string {
149         switch m := m.(type) {
150         case *PublicMsg:
151                 return m.RenderFor(u.Config) + Newline
152         case *PrivateMsg:
153                 u.SetReplyTo(m.From())
154                 return m.Render(u.Config.Theme) + Newline
155         default:
156                 return m.Render(u.Config.Theme) + Newline
157         }
158 }
159
160 // HandleMsg will render the message to the screen, blocking.
161 func (u *User) HandleMsg(m Message) error {
162         r := u.render(m)
163         _, err := u.screen.Write([]byte(r))
164         if err != nil {
165                 logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
166                 u.Close()
167         }
168         return err
169 }
170
171 // Add message to consume by user
172 func (u *User) Send(m Message) error {
173         select {
174         case u.msg <- m:
175         case <-u.done:
176                 return ErrUserClosed
177         case <-time.After(messageTimeout):
178                 logger.Printf("Message buffer full, closing: %s", u.Name())
179                 u.Close()
180                 return ErrUserClosed
181         }
182         return nil
183 }
184
185 func (u *User) Ignore(identified common.Identified) error {
186         if identified == nil {
187                 return errors.New("user is nil.")
188         }
189
190         if identified.Id() == u.Id() {
191                 return errors.New("cannot ignore self.")
192         }
193
194         if u.Ignored.In(identified) {
195                 return errors.New("user already ignored.")
196         }
197
198         u.Ignored.Add(identified)
199         return nil
200 }
201
202 func (u *User) Unignore(id string) error {
203         if id == "" {
204                 return errors.New("user is nil.")
205         }
206
207         identified, err := u.Ignored.Get(id)
208         if err != nil {
209                 return err
210         }
211
212         return u.Ignored.Remove(identified)
213 }
214
215 // Container for per-user configurations.
216 type UserConfig struct {
217         Highlight *regexp.Regexp
218         Bell      bool
219         Quiet     bool
220         Theme     *Theme
221 }
222
223 // Default user configuration to use
224 var DefaultUserConfig UserConfig
225
226 func init() {
227         DefaultUserConfig = UserConfig{
228                 Bell:  true,
229                 Quiet: false,
230         }
231
232         // TODO: Seed random?
233 }