From 137e84db79d3c1a8f68936630fa3b4237bf5d40d Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Sun, 21 Dec 2014 12:17:01 -0800 Subject: [PATCH] Messing with the API more, tests pass. --- chat/channel_test.go | 4 +-- chat/host.go | 2 -- chat/message.go | 1 + chat/set_test.go | 4 +-- chat/user.go | 69 +++++++++++++++++++++++++++++++++++++++----- chat/user_test.go | 13 +++++---- 6 files changed, 75 insertions(+), 18 deletions(-) diff --git a/chat/channel_test.go b/chat/channel_test.go index b52577c..7bda641 100644 --- a/chat/channel_test.go +++ b/chat/channel_test.go @@ -16,7 +16,7 @@ func TestChannel(t *testing.T) { } }() - u := NewUser("foo", s) + u := NewUserScreen("foo", s) ch := NewChannel("", out) err := ch.Join(u) @@ -26,6 +26,6 @@ func TestChannel(t *testing.T) { expected := []byte(" * foo joined. (Connected: 1)") if !reflect.DeepEqual(s.received, expected) { - t.Errorf("Got: `%s`, Expected: `%s`", s.received, expected) + t.Errorf("Got: `%s`; Expected: `%s`", s.received, expected) } } diff --git a/chat/host.go b/chat/host.go index bb20571..ce8c905 100644 --- a/chat/host.go +++ b/chat/host.go @@ -1,7 +1,5 @@ package chat -const messageBuffer = 20 - // Host knows about all the commands and channels. type Host struct { defaultChannel *Channel diff --git a/chat/message.go b/chat/message.go index 04d816f..5e51b2c 100644 --- a/chat/message.go +++ b/chat/message.go @@ -37,6 +37,7 @@ func (m *Message) From(u *User) *Message { // Render message based on the given theme func (m *Message) Render(*Theme) string { + // TODO: Return []byte? // TODO: Render based on theme // TODO: Cache based on theme var msg string diff --git a/chat/set_test.go b/chat/set_test.go index b55972b..60038c1 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", nil) + u := NewUser("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", nil) + u2 := NewUser("bar") err = s.Add(u2) if err != nil { t.Error(err) diff --git a/chat/user.go b/chat/user.go index 946c371..cf157ad 100644 --- a/chat/user.go +++ b/chat/user.go @@ -1,31 +1,46 @@ package chat import ( + "errors" "io" "math/rand" "time" ) +const messageBuffer = 20 + +var ErrUserClosed = errors.New("user closed") + // User definition, implemented set Item interface and io.Writer type User struct { name string op bool colorIdx int joined time.Time - screen io.Writer + msg chan Message + done chan struct{} Config UserConfig } -func NewUser(name string, screen io.Writer) *User { +func NewUser(name string) *User { u := User{ - screen: screen, - joined: time.Now(), Config: *DefaultUserConfig, + joined: time.Now(), + msg: make(chan Message, messageBuffer), + done: make(chan struct{}, 1), } u.SetName(name) + return &u } +func NewUserScreen(name string, screen io.Writer) *User { + u := NewUser(name) + go u.Consume(screen) + + return u +} + // Return unique identifier for user func (u *User) Id() Id { return Id(u.name) @@ -52,9 +67,49 @@ func (u *User) SetOp(op bool) { u.op = op } -// Write to user's screen -func (u *User) Write(p []byte) (n int, err error) { - return u.screen.Write(p) +// Block until user is closed +func (u *User) Wait() { + <-u.done +} + +// Disconnect user, stop accepting messages +func (u *User) Close() { + close(u.done) + close(u.msg) +} + +// Consume message buffer into an io.Writer. Will block, should be called in a +// goroutine. +func (u *User) Consume(out io.Writer) { + for m := range u.msg { + u.consumeMsg(m, out) + } +} + +// Consume one message and stop, mostly for testing +func (u *User) ConsumeOne(out io.Writer) { + u.consumeMsg(<-u.msg, out) +} + +func (u *User) consumeMsg(m Message, out io.Writer) { + s := m.Render(u.Config.Theme) + _, err := out.Write([]byte(s)) + if err != nil { + logger.Printf("Write failed to %s, closing: %s", u.Name(), err) + u.Close() + } +} + +// Add message to consume by user +func (u *User) Send(m Message) error { + select { + case u.msg <- m: + default: + logger.Printf("Msg buffer full, closing: %s", u.Name()) + u.Close() + return ErrUserClosed + } + return nil } // Container for per-user configurations. diff --git a/chat/user_test.go b/chat/user_test.go index 1f0c5cd..034b697 100644 --- a/chat/user_test.go +++ b/chat/user_test.go @@ -16,11 +16,14 @@ func (s *MockScreen) Write(data []byte) (n int, err error) { func TestMakeUser(t *testing.T) { s := &MockScreen{} - u := NewUser("foo", s) + u := NewUser("foo") + m := NewMessage("hello") - line := []byte("hello") - u.Write(line) - if !reflect.DeepEqual(s.received, line) { - t.Errorf("Expected hello but got: %s", s.received) + defer u.Close() + u.Send(*m) + u.ConsumeOne(s) + + if !reflect.DeepEqual(string(s.received), m.String()) { + t.Errorf("Got: `%s`; Expected: `%s`", s.received, m.String()) } }