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) Config() UserConfig {
68 func (u *User) SetConfig(cfg UserConfig) {
74 // Rename the user with a new Identifier.
75 func (u *User) SetID(id string) {
76 u.Identifier.SetID(id)
77 u.setColorIdx(rand.Int())
80 // ReplyTo returns the last user that messaged this user.
81 func (u *User) ReplyTo() *User {
87 // SetReplyTo sets the last user to message this user.
88 func (u *User) SetReplyTo(user *User) {
94 // setColorIdx will set the colorIdx to a specific value, primarily used for
96 func (u *User) setColorIdx(idx int) {
100 // Disconnect user, stop accepting messages
101 func (u *User) Close() {
102 u.closeOnce.Do(func() {
106 // close(u.msg) TODO: Close?
111 // Consume message buffer into the handler. Will block, should be called in a
113 func (u *User) Consume() {
118 case m, ok := <-u.msg:
127 // Consume one message and stop, mostly for testing
128 func (u *User) ConsumeOne() Message {
132 // Check if there are pending messages, used for testing
133 func (u *User) HasMessages() bool {
143 // SetHighlight sets the highlighting regular expression to match string.
144 func (u *User) SetHighlight(s string) error {
145 re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
150 u.config.Highlight = re
155 func (u *User) render(m Message) string {
157 switch m := m.(type) {
159 return m.RenderFor(cfg) + Newline
162 return m.Render(cfg.Theme) + Bel + Newline
164 return m.Render(cfg.Theme) + Newline
166 return m.Render(cfg.Theme) + Newline
170 // writeMsg renders the message and attempts to write it, will Close the user
172 func (u *User) writeMsg(m Message) error {
174 _, err := u.screen.Write([]byte(r))
176 logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
182 // HandleMsg will render the message to the screen, blocking.
183 func (u *User) HandleMsg(m Message) error {
187 u.lastMsg = m.Timestamp()
188 injectTimestamp := !lastMsg.IsZero() && cfg.Timestamp && u.lastMsg.Sub(lastMsg) > timestampTimeout
192 // Inject a timestamp at most once every timestampTimeout between message intervals
193 ts := NewSystemMsg(fmt.Sprintf("Timestamp: %s", m.Timestamp().UTC().Format(timestampLayout)), u)
194 if err := u.writeMsg(ts); err != nil {
202 // Add message to consume by user
203 func (u *User) Send(m Message) error {
208 case <-time.After(messageTimeout):
209 logger.Printf("Message buffer full, closing: %s", u.Name())
216 // Container for per-user configurations.
217 type UserConfig struct {
218 Highlight *regexp.Regexp
225 // Default user configuration to use
226 var DefaultUserConfig UserConfig
229 DefaultUserConfig = UserConfig{
235 // TODO: Seed random?