chat/message: Fix RecentActiveUsers sort order
[ssh-chat] / chat / message / user.go
index 0cd700c3f3f1b43ba60012f879fef06c56b0f24b..d4cc304c539c995076dcc27e02dba1adefc84538 100644 (file)
@@ -15,6 +15,7 @@ import (
 const messageBuffer = 5
 const messageTimeout = 5 * time.Second
 const reHighlight = `\b(%s)\b`
+const timestampTimeout = 30 * time.Minute
 
 var ErrUserClosed = errors.New("user closed")
 
@@ -32,7 +33,8 @@ type User struct {
 
        mu      sync.Mutex
        config  UserConfig
-       replyTo *User // Set when user gets a /msg, for replying.
+       replyTo *User     // Set when user gets a /msg, for replying.
+       lastMsg time.Time // When the last message was rendered
 }
 
 func NewUser(identity Identifier) *User {
@@ -56,6 +58,10 @@ func NewUserScreen(identity Identifier, screen io.WriteCloser) *User {
        return u
 }
 
+func (u *User) Joined() time.Time {
+       return u.joined
+}
+
 func (u *User) Config() UserConfig {
        u.mu.Lock()
        defer u.mu.Unlock()
@@ -151,19 +157,42 @@ func (u *User) SetHighlight(s string) error {
 
 func (u *User) render(m Message) string {
        cfg := u.Config()
+       var out string
        switch m := m.(type) {
        case PublicMsg:
-               return m.RenderFor(cfg) + Newline
-       case PrivateMsg:
-               u.SetReplyTo(m.From())
-               return m.Render(cfg.Theme) + Newline
+               if u == m.From() {
+                       if !cfg.Echo {
+                               return ""
+                       }
+                       out += m.RenderSelf(cfg)
+               } else {
+                       out += m.RenderFor(cfg)
+               }
+       case *PrivateMsg:
+               out += m.Render(cfg.Theme)
+               if cfg.Bell {
+                       out += Bel
+               }
+       case *CommandMsg:
+               out += m.RenderSelf(cfg)
        default:
-               return m.Render(cfg.Theme) + Newline
+               out += m.Render(cfg.Theme)
        }
+       if cfg.Timeformat != nil {
+               ts := m.Timestamp()
+               if cfg.Timezone != nil {
+                       ts = ts.In(cfg.Timezone)
+               } else {
+                       ts = ts.UTC()
+               }
+               return cfg.Theme.Timestamp(ts.Format(*cfg.Timeformat)) + "  " + out + Newline
+       }
+       return out + Newline
 }
 
-// HandleMsg will render the message to the screen, blocking.
-func (u *User) HandleMsg(m Message) error {
+// writeMsg renders the message and attempts to write it, will Close the user
+// if it fails.
+func (u *User) writeMsg(m Message) error {
        r := u.render(m)
        _, err := u.screen.Write([]byte(r))
        if err != nil {
@@ -173,6 +202,14 @@ func (u *User) HandleMsg(m Message) error {
        return err
 }
 
+// HandleMsg will render the message to the screen, blocking.
+func (u *User) HandleMsg(m Message) error {
+       u.mu.Lock()
+       u.lastMsg = m.Timestamp()
+       u.mu.Unlock()
+       return u.writeMsg(m)
+}
+
 // Add message to consume by user
 func (u *User) Send(m Message) error {
        select {
@@ -189,10 +226,13 @@ func (u *User) Send(m Message) error {
 
 // Container for per-user configurations.
 type UserConfig struct {
-       Highlight *regexp.Regexp
-       Bell      bool
-       Quiet     bool
-       Theme     *Theme
+       Highlight  *regexp.Regexp
+       Bell       bool
+       Quiet      bool
+       Echo       bool // Echo shows your own messages after sending, disabled for bots
+       Timeformat *string
+       Timezone   *time.Location
+       Theme      *Theme
 }
 
 // Default user configuration to use
@@ -201,8 +241,28 @@ var DefaultUserConfig UserConfig
 func init() {
        DefaultUserConfig = UserConfig{
                Bell:  true,
+               Echo:  true,
                Quiet: false,
        }
 
        // TODO: Seed random?
 }
+
+// RecentActiveUsers is a slice of *Users that knows how to be sorted by the time of the last message.
+type RecentActiveUsers []*User
+
+func (a RecentActiveUsers) Len() int      { return len(a) }
+func (a RecentActiveUsers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a RecentActiveUsers) Less(i, j int) bool {
+       a[i].mu.Lock()
+       defer a[i].mu.Unlock()
+       a[j].mu.Lock()
+       defer a[j].mu.Unlock()
+
+       if a[i].lastMsg.IsZero() {
+               return a[i].joined.Before(a[j].joined)
+       } else {
+               return a[i].lastMsg.Before(a[j].lastMsg)
+       }
+
+}