From 0c2148699272fc5ddf30818cd31c144c4c619d7d Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Sun, 18 Jan 2015 18:55:47 -0800 Subject: [PATCH] History backfill, also tests pass. --- auth.go | 3 +++ auth_test.go | 6 +++--- chat/command.go | 4 ++-- chat/history.go | 22 +++++++++++----------- chat/history_test.go | 39 ++++++++++++++++++--------------------- chat/message.go | 8 +++++++- chat/message_test.go | 14 +++++++++++++- chat/room.go | 18 ++++++++++++------ chat/room_test.go | 10 +++++----- chat/set.go | 12 ++++++------ chat/set_test.go | 4 ++-- chat/theme_test.go | 2 +- chat/user.go | 9 +++------ chat/user_test.go | 2 +- host.go | 2 +- host_test.go | 2 +- identity.go | 15 +++++++-------- sshd/client_test.go | 3 ++- 18 files changed, 98 insertions(+), 77 deletions(-) diff --git a/auth.go b/auth.go index bce92bf..26a217c 100644 --- a/auth.go +++ b/auth.go @@ -28,6 +28,9 @@ func NewAuthKey(key ssh.PublicKey) string { // NewAuthAddr returns a string from a net.Addr func NewAuthAddr(addr net.Addr) string { + if addr == nil { + return "" + } host, _, _ := net.SplitHostPort(addr.String()) return host } diff --git a/auth_test.go b/auth_test.go index 3b11212..eb29773 100644 --- a/auth_test.go +++ b/auth_test.go @@ -28,7 +28,7 @@ func TestAuthWhitelist(t *testing.T) { } auth := NewAuth() - ok, err := auth.Check(key) + ok, err := auth.Check(nil, key) if !ok || err != nil { t.Error("Failed to permit in default state:", err) } @@ -44,7 +44,7 @@ func TestAuthWhitelist(t *testing.T) { t.Error("Clone key does not match.") } - ok, err = auth.Check(keyClone) + ok, err = auth.Check(nil, keyClone) if !ok || err != nil { t.Error("Failed to permit whitelisted:", err) } @@ -54,7 +54,7 @@ func TestAuthWhitelist(t *testing.T) { t.Fatal(err) } - ok, err = auth.Check(key2) + ok, err = auth.Check(nil, key2) if ok || err == nil { t.Error("Failed to restrict not whitelisted:", err) } diff --git a/chat/command.go b/chat/command.go index 047ea52..d4873f3 100644 --- a/chat/command.go +++ b/chat/command.go @@ -145,7 +145,7 @@ func InitCommands(c *Commands) { } u := msg.From() oldId := u.Id() - u.SetId(Id(args[0])) + u.SetId(args[0]) err := room.Rename(oldId, u) if err != nil { @@ -234,7 +234,7 @@ func InitCommands(c *Commands) { // TODO: Add support for fingerprint-based op'ing. This will // probably need to live in host land. - member, ok := room.MemberById(Id(args[0])) + member, ok := room.MemberById(args[0]) if !ok { return errors.New("user not found") } diff --git a/chat/history.go b/chat/history.go index b0f78a6..44513a6 100644 --- a/chat/history.go +++ b/chat/history.go @@ -4,23 +4,23 @@ import "sync" // History contains the history entries type History struct { - entries []interface{} + entries []Message head int size int - sync.RWMutex + lock sync.Mutex } // NewHistory constructs a new history of the given size func NewHistory(size int) *History { return &History{ - entries: make([]interface{}, size), + entries: make([]Message, size), } } // Add adds the given entry to the entries in the history -func (h *History) Add(entry interface{}) { - h.Lock() - defer h.Unlock() +func (h *History) Add(entry Message) { + h.lock.Lock() + defer h.lock.Unlock() max := cap(h.entries) h.head = (h.head + 1) % max @@ -35,17 +35,17 @@ func (h *History) Len() int { return h.size } -// Get recent entries -func (h *History) Get(num int) []interface{} { - h.RLock() - defer h.RUnlock() +// Get the entry with the given number +func (h *History) Get(num int) []Message { + h.lock.Lock() + defer h.lock.Unlock() max := cap(h.entries) if num > h.size { num = h.size } - r := make([]interface{}, num) + r := make([]Message, num) for i := 0; i < num; i++ { idx := (h.head - i) % max if idx < 0 { diff --git a/chat/history_test.go b/chat/history_test.go index fb166c4..de767ec 100644 --- a/chat/history_test.go +++ b/chat/history_test.go @@ -2,64 +2,61 @@ package chat import "testing" -func equal(a []interface{}, b []string) bool { +func msgEqual(a []Message, b []Message) bool { if len(a) != len(b) { return false } - - for i := 0; i < len(a); i++ { - if a[0] != b[0] { + for i := range a { + if a[i].String() != b[i].String() { return false } } - return true } func TestHistory(t *testing.T) { - var r []interface{} - var expected []string + var r, expected []Message var size int h := NewHistory(5) r = h.Get(10) - expected = []string{} - if !equal(r, expected) { + expected = []Message{} + if !msgEqual(r, expected) { t.Errorf("Got: %v, Expected: %v", r, expected) } - h.Add("1") + h.Add(NewMsg("1")) if size = h.Len(); size != 1 { t.Errorf("Wrong size: %v", size) } r = h.Get(1) - expected = []string{"1"} - if !equal(r, expected) { + expected = []Message{NewMsg("1")} + if !msgEqual(r, expected) { t.Errorf("Got: %v, Expected: %v", r, expected) } - h.Add("2") - h.Add("3") - h.Add("4") - h.Add("5") - h.Add("6") + h.Add(NewMsg("2")) + h.Add(NewMsg("3")) + h.Add(NewMsg("4")) + h.Add(NewMsg("5")) + h.Add(NewMsg("6")) if size = h.Len(); size != 5 { t.Errorf("Wrong size: %v", size) } r = h.Get(2) - expected = []string{"5", "6"} - if !equal(r, expected) { + expected = []Message{NewMsg("5"), NewMsg("6")} + if !msgEqual(r, expected) { t.Errorf("Got: %v, Expected: %v", r, expected) } r = h.Get(10) - expected = []string{"2", "3", "4", "5", "6"} - if !equal(r, expected) { + expected = []Message{NewMsg("2"), NewMsg("3"), NewMsg("4"), NewMsg("5"), NewMsg("6")} + if !msgEqual(r, expected) { t.Errorf("Got: %v, Expected: %v", r, expected) } } diff --git a/chat/message.go b/chat/message.go index d271e0b..2fc857d 100644 --- a/chat/message.go +++ b/chat/message.go @@ -34,12 +34,18 @@ func ParseInput(body string, from *User) Message { // Msg is a base type for other message types. type Msg struct { - Message body string timestamp time.Time // TODO: themeCache *map[*Theme]string } +func NewMsg(body string) *Msg { + return &Msg{ + body: body, + timestamp: time.Now(), + } +} + // Render message based on a theme. func (m *Msg) Render(t *Theme) string { // TODO: Render based on theme diff --git a/chat/message_test.go b/chat/message_test.go index fafe4f8..bafe014 100644 --- a/chat/message_test.go +++ b/chat/message_test.go @@ -2,6 +2,18 @@ package chat import "testing" +type testId string + +func (i testId) Id() string { + return string(i) +} +func (i testId) SetId(s string) { + // no-op +} +func (i testId) Name() string { + return i.Id() +} + func TestMessage(t *testing.T) { var expected, actual string @@ -11,7 +23,7 @@ func TestMessage(t *testing.T) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) } - u := NewUser("foo") + u := NewUser(testId("foo")) expected = "foo: hello" actual = NewPublicMsg("hello", u).String() if actual != expected { diff --git a/chat/room.go b/chat/room.go index 896f92f..e6f68b5 100644 --- a/chat/room.go +++ b/chat/room.go @@ -79,6 +79,7 @@ func (r *Room) HandleMsg(m Message) { skipUser = fromMsg.From() } + r.history.Add(m) r.members.Each(func(u Identifier) { user := u.(*Member).User if skip && skipUser == user { @@ -91,10 +92,7 @@ func (r *Room) HandleMsg(m Message) { return } } - err := user.Send(m) - if err != nil { - user.Close() - } + user.Send(m) }) } } @@ -112,6 +110,13 @@ func (r *Room) Send(m Message) { r.broadcast <- m } +// History feeds the room's recent message history to the user's handler. +func (r *Room) History(u *User) { + for _, m := range r.history.Get(historyLen) { + u.Send(m) + } +} + // Join the room as a user, will announce. func (r *Room) Join(u *User) (*Member, error) { if r.closed { @@ -122,6 +127,7 @@ func (r *Room) Join(u *User) (*Member, error) { if err != nil { return nil, err } + r.History(u) s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.members.Len()) r.Send(NewAnnounceMsg(s)) return &member, nil @@ -139,7 +145,7 @@ func (r *Room) Leave(u *User) error { } // Rename member with a new identity. This will not call rename on the member. -func (r *Room) Rename(oldId Id, identity Identifier) error { +func (r *Room) Rename(oldId string, identity Identifier) error { err := r.members.Replace(oldId, identity) if err != nil { return err @@ -164,7 +170,7 @@ func (r *Room) Member(u *User) (*Member, bool) { return m, true } -func (r *Room) MemberById(id Id) (*Member, bool) { +func (r *Room) MemberById(id string) (*Member, bool) { m, err := r.members.Get(id) if err != nil { return nil, false diff --git a/chat/room_test.go b/chat/room_test.go index 4f39dd7..e415a0c 100644 --- a/chat/room_test.go +++ b/chat/room_test.go @@ -22,7 +22,7 @@ func TestRoomJoin(t *testing.T) { var expected, actual []byte s := &MockScreen{} - u := NewUser("foo") + u := NewUser(testId("foo")) ch := NewRoom() go ch.Serve() @@ -58,7 +58,7 @@ func TestRoomJoin(t *testing.T) { } func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) { - u := NewUser("foo") + u := NewUser(testId("foo")) u.Config = UserConfig{ Quiet: true, } @@ -93,7 +93,7 @@ func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) { } func TestRoomQuietToggleBroadcasts(t *testing.T) { - u := NewUser("foo") + u := NewUser(testId("foo")) u.Config = UserConfig{ Quiet: true, } @@ -132,7 +132,7 @@ func TestQuietToggleDisplayState(t *testing.T) { var expected, actual []byte s := &MockScreen{} - u := NewUser("foo") + u := NewUser(testId("foo")) ch := NewRoom() go ch.Serve() @@ -168,7 +168,7 @@ func TestRoomNames(t *testing.T) { var expected, actual []byte s := &MockScreen{} - u := NewUser("foo") + u := NewUser(testId("foo")) ch := NewRoom() go ch.Serve() diff --git a/chat/set.go b/chat/set.go index d032a34..8617e14 100644 --- a/chat/set.go +++ b/chat/set.go @@ -15,14 +15,14 @@ var ErrItemMissing = errors.New("item does not exist") // Set with string lookup. // TODO: Add trie for efficient prefix lookup? type Set struct { - lookup map[Id]Identifier + lookup map[string]Identifier sync.RWMutex } // NewSet creates a new set. func NewSet() *Set { return &Set{ - lookup: map[Id]Identifier{}, + lookup: map[string]Identifier{}, } } @@ -30,7 +30,7 @@ func NewSet() *Set { func (s *Set) Clear() int { s.Lock() n := len(s.lookup) - s.lookup = map[Id]Identifier{} + s.lookup = map[string]Identifier{} s.Unlock() return n } @@ -49,7 +49,7 @@ func (s *Set) In(item Identifier) bool { } // Get returns an item with the given Id. -func (s *Set) Get(id Id) (Identifier, error) { +func (s *Set) Get(id string) (Identifier, error) { s.RLock() item, ok := s.lookup[id] s.RUnlock() @@ -88,9 +88,9 @@ func (s *Set) Remove(item Identifier) error { return nil } -// Replace item from old Id with new Identifier. +// Replace item from old id with new Identifier. // Used for moving the same identifier to a new Id, such as a rename. -func (s *Set) Replace(oldId Id, item Identifier) error { +func (s *Set) Replace(oldId string, item Identifier) error { s.Lock() defer s.Unlock() diff --git a/chat/set_test.go b/chat/set_test.go index 60038c1..b92bdeb 100644 --- a/chat/set_test.go +++ b/chat/set_test.go @@ -5,7 +5,7 @@ import "testing" func TestSet(t *testing.T) { var err error s := NewSet() - u := NewUser("foo") + u := NewUser(testId("foo")) if s.In(u) { t.Errorf("Set should be empty.") @@ -20,7 +20,7 @@ func TestSet(t *testing.T) { t.Errorf("Set should contain user.") } - u2 := NewUser("bar") + u2 := NewUser(testId("bar")) err = s.Add(u2) if err != nil { t.Error(err) diff --git a/chat/theme_test.go b/chat/theme_test.go index f385982..28b9d43 100644 --- a/chat/theme_test.go +++ b/chat/theme_test.go @@ -54,7 +54,7 @@ func TestTheme(t *testing.T) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) } - u := NewUser("foo") + u := NewUser(testId("foo")) u.colorIdx = 4 actual = colorTheme.ColorName(u) expected = "\033[38;05;4mfoo\033[0m" diff --git a/chat/user.go b/chat/user.go index 32be8ca..4a31816 100644 --- a/chat/user.go +++ b/chat/user.go @@ -15,13 +15,10 @@ const reHighlight = `\b(%s)\b` var ErrUserClosed = errors.New("user closed") -// Id is a unique immutable identifier for a user. -type Id string - // Identifier is an interface that can uniquely identify itself. type Identifier interface { - Id() Id - SetId(Id) + Id() string + SetId(string) Name() string } @@ -59,7 +56,7 @@ func NewUserScreen(identity Identifier, screen io.Writer) *User { } // Rename the user with a new Identifier. -func (u *User) SetId(id Id) { +func (u *User) SetId(id string) { u.Identifier.SetId(id) u.SetColorIdx(rand.Int()) } diff --git a/chat/user_test.go b/chat/user_test.go index ce2d4ef..37ecc29 100644 --- a/chat/user_test.go +++ b/chat/user_test.go @@ -9,7 +9,7 @@ func TestMakeUser(t *testing.T) { var actual, expected []byte s := &MockScreen{} - u := NewUser("foo") + u := NewUser(testId("foo")) m := NewAnnounceMsg("hello") defer u.Close() diff --git a/host.go b/host.go index 8d87142..e153207 100644 --- a/host.go +++ b/host.go @@ -208,7 +208,7 @@ func (h *Host) AutoCompleteFunction(u *chat.User) func(line string, pos int, key // GetUser returns a chat.User based on a name. func (h *Host) GetUser(name string) (*chat.User, bool) { - m, ok := h.MemberById(chat.Id(name)) + m, ok := h.MemberById(name) if !ok { return nil, false } diff --git a/host_test.go b/host_test.go index 9c57439..2e79fb0 100644 --- a/host_test.go +++ b/host_test.go @@ -26,7 +26,7 @@ func stripPrompt(s string) string { func TestHostGetPrompt(t *testing.T) { var expected, actual string - u := chat.NewUser("foo") + u := chat.NewUser(&Identity{nil, "foo"}) u.SetColorIdx(2) actual = GetPrompt(u) diff --git a/identity.go b/identity.go index 7df812b..c41d382 100644 --- a/identity.go +++ b/identity.go @@ -11,32 +11,31 @@ import ( // Identity is a container for everything that identifies a client. type Identity struct { sshd.Connection - id chat.Id + id string } // NewIdentity returns a new identity object from an sshd.Connection. func NewIdentity(conn sshd.Connection) *Identity { - id := chat.Id(conn.Name()) return &Identity{ Connection: conn, - id: id, + id: conn.Name(), } } -func (i Identity) Id() chat.Id { - return chat.Id(i.id) +func (i Identity) Id() string { + return i.id } -func (i *Identity) SetId(id chat.Id) { +func (i *Identity) SetId(id string) { i.id = id } func (i *Identity) SetName(name string) { - i.SetId(chat.Id(name)) + i.SetId(name) } func (i Identity) Name() string { - return string(i.id) + return i.id } func (i Identity) Whois() string { diff --git a/sshd/client_test.go b/sshd/client_test.go index b115e8f..651c67e 100644 --- a/sshd/client_test.go +++ b/sshd/client_test.go @@ -2,6 +2,7 @@ package sshd import ( "errors" + "net" "testing" "golang.org/x/crypto/ssh" @@ -14,7 +15,7 @@ type RejectAuth struct{} func (a RejectAuth) AllowAnonymous() bool { return false } -func (a RejectAuth) Check(ssh.PublicKey) (bool, error) { +func (a RejectAuth) Check(net.Addr, ssh.PublicKey) (bool, error) { return false, errRejectAuth }