12 "github.com/shazow/ssh-chat/set"
15 const messageBuffer = 5
16 const messageTimeout = 5 * time.Second
17 const reHighlight = `\b(%s)\b`
18 const timestampTimeout = 30 * time.Minute
19 const timestampLayout = "2006-01-02 15:04:05 UTC"
21 var ErrUserClosed = errors.New("user closed")
23 // User definition, implemented set Item interface and io.Writer
37 replyTo *User // Set when user gets a /msg, for replying.
38 lastMsg time.Time // When the last message was rendered
41 func NewUser(identity Identifier) *User {
44 config: DefaultUserConfig,
46 msg: make(chan Message, messageBuffer),
47 done: make(chan struct{}),
50 u.setColorIdx(rand.Int())
55 func NewUserScreen(identity Identifier, screen io.WriteCloser) *User {
56 u := NewUser(identity)
62 func (u *User) Joined() time.Time {
66 func (u *User) Config() UserConfig {
72 func (u *User) SetConfig(cfg UserConfig) {
78 // Rename the user with a new Identifier.
79 func (u *User) SetID(id string) {
80 u.Identifier.SetID(id)
81 u.setColorIdx(rand.Int())
84 // ReplyTo returns the last user that messaged this user.
85 func (u *User) ReplyTo() *User {
91 // SetReplyTo sets the last user to message this user.
92 func (u *User) SetReplyTo(user *User) {
98 // setColorIdx will set the colorIdx to a specific value, primarily used for
100 func (u *User) setColorIdx(idx int) {
104 // Disconnect user, stop accepting messages
105 func (u *User) Close() {
106 u.closeOnce.Do(func() {
110 // close(u.msg) TODO: Close?
115 // Consume message buffer into the handler. Will block, should be called in a
117 func (u *User) Consume() {
122 case m, ok := <-u.msg:
131 // Consume one message and stop, mostly for testing
132 func (u *User) ConsumeOne() Message {
136 // Check if there are pending messages, used for testing
137 func (u *User) HasMessages() bool {
147 // SetHighlight sets the highlighting regular expression to match string.
148 func (u *User) SetHighlight(s string) error {
149 re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
154 u.config.Highlight = re
159 func (u *User) render(m Message) string {
161 switch m := m.(type) {
163 return m.RenderFor(cfg) + Newline
166 return m.Render(cfg.Theme) + Bel + Newline
168 return m.Render(cfg.Theme) + Newline
170 return m.Render(cfg.Theme) + Newline
174 // writeMsg renders the message and attempts to write it, will Close the user
176 func (u *User) writeMsg(m Message) error {
178 _, err := u.screen.Write([]byte(r))
180 logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
186 // HandleMsg will render the message to the screen, blocking.
187 func (u *User) HandleMsg(m Message) error {
191 u.lastMsg = m.Timestamp()
192 injectTimestamp := !lastMsg.IsZero() && cfg.Timestamp && u.lastMsg.Sub(lastMsg) > timestampTimeout
196 // Inject a timestamp at most once every timestampTimeout between message intervals
197 ts := NewSystemMsg(fmt.Sprintf("Timestamp: %s", m.Timestamp().UTC().Format(timestampLayout)), u)
198 if err := u.writeMsg(ts); err != nil {
206 // Add message to consume by user
207 func (u *User) Send(m Message) error {
212 case <-time.After(messageTimeout):
213 logger.Printf("Message buffer full, closing: %s", u.Name())
220 // Container for per-user configurations.
221 type UserConfig struct {
222 Highlight *regexp.Regexp
229 // Default user configuration to use
230 var DefaultUserConfig UserConfig
233 DefaultUserConfig = UserConfig{
239 // TODO: Seed random?
242 // RecentActiveUsers is a slice of *Users that knows how to be sorted by the time of the last message.
243 type RecentActiveUsers []*User
245 func (a RecentActiveUsers) Len() int { return len(a) }
246 func (a RecentActiveUsers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
247 func (a RecentActiveUsers) Less(i, j int) bool {
249 defer a[i].mu.Unlock()
251 defer a[j].mu.Unlock()
252 return a[i].lastMsg.After(a[j].lastMsg)