Fixed /kick command to actually close target
[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 = 20
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         mu      sync.Mutex
28         replyTo *User // Set when user gets a /msg, for replying.
29         screen  io.Closer
30         closed  bool
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{}, 1),
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         go u.Consume(screen)
50
51         return u
52 }
53
54 // Rename the user with a new Identifier.
55 func (u *User) SetId(id string) {
56         u.Identifier.SetId(id)
57         u.SetColorIdx(rand.Int())
58 }
59
60 // ReplyTo returns the last user that messaged this user.
61 func (u *User) ReplyTo() *User {
62         return u.replyTo
63 }
64
65 // SetReplyTo sets the last user to message this user.
66 func (u *User) SetReplyTo(user *User) {
67         u.replyTo = user
68 }
69
70 // ToggleQuietMode will toggle whether or not quiet mode is enabled
71 func (u *User) ToggleQuietMode() {
72         u.Config.Quiet = !u.Config.Quiet
73 }
74
75 // SetColorIdx will set the colorIdx to a specific value, primarily used for
76 // testing.
77 func (u *User) SetColorIdx(idx int) {
78         u.colorIdx = idx
79 }
80
81 // Block until user is closed
82 func (u *User) Wait() {
83         <-u.done
84 }
85
86 // Disconnect user, stop accepting messages
87 func (u *User) Close() {
88         u.mu.Lock()
89         defer u.mu.Unlock()
90
91         if u.closed {
92                 return
93         }
94
95         u.closed = true
96         close(u.done)
97         close(u.msg)
98
99         if u.screen != nil {
100                 u.screen.Close()
101         }
102 }
103
104 // Consume message buffer into an io.Writer. Will block, should be called in a
105 // goroutine.
106 // TODO: Not sure if this is a great API.
107 func (u *User) Consume(out io.Writer) {
108         for m := range u.msg {
109                 u.HandleMsg(m, out)
110         }
111 }
112
113 // Consume one message and stop, mostly for testing
114 func (u *User) ConsumeChan() <-chan Message {
115         return u.msg
116 }
117
118 // SetHighlight sets the highlighting regular expression to match string.
119 func (u *User) SetHighlight(s string) error {
120         re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
121         if err != nil {
122                 return err
123         }
124         u.Config.Highlight = re
125         return nil
126 }
127
128 func (u *User) render(m Message) string {
129         switch m := m.(type) {
130         case *PublicMsg:
131                 return m.RenderFor(u.Config) + Newline
132         case *PrivateMsg:
133                 u.SetReplyTo(m.From())
134                 return m.Render(u.Config.Theme) + Newline
135         default:
136                 return m.Render(u.Config.Theme) + Newline
137         }
138 }
139
140 func (u *User) HandleMsg(m Message, out io.Writer) {
141         r := u.render(m)
142         _, err := out.Write([]byte(r))
143         if err != nil {
144                 logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
145                 u.Close()
146         }
147 }
148
149 // Add message to consume by user
150 func (u *User) Send(m Message) error {
151         u.mu.Lock()
152         defer u.mu.Unlock()
153
154         if u.closed {
155                 return ErrUserClosed
156         }
157
158         select {
159         case u.msg <- m:
160         default:
161                 logger.Printf("Msg buffer full, closing: %s", u.Name())
162                 u.Close()
163                 return ErrUserClosed
164         }
165         return nil
166 }
167
168 // Container for per-user configurations.
169 type UserConfig struct {
170         Highlight *regexp.Regexp
171         Bell      bool
172         Quiet     bool
173         Theme     *Theme
174 }
175
176 // Default user configuration to use
177 var DefaultUserConfig *UserConfig
178
179 func init() {
180         DefaultUserConfig = &UserConfig{
181                 Bell:  true,
182                 Quiet: false,
183         }
184
185         // TODO: Seed random?
186 }