msg chan Message
done chan struct{}
- mu sync.RWMutex
replyTo *User // Set when user gets a /msg, for replying.
screen io.WriteCloser
closeOnce sync.Once
func NewUser(identity Identifier) *User {
u := User{
Identifier: identity,
- Config: *DefaultUserConfig,
+ Config: DefaultUserConfig,
joined: time.Now(),
msg: make(chan Message, messageBuffer),
- done: make(chan struct{}, 1),
+ done: make(chan struct{}),
}
u.SetColorIdx(rand.Int())
// Disconnect user, stop accepting messages
func (u *User) Close() {
u.closeOnce.Do(func() {
- u.mu.Lock()
if u.screen != nil {
u.screen.Close()
}
- close(u.msg)
+ // close(u.msg) TODO: Close?
close(u.done)
- u.msg = nil
- u.mu.Unlock()
})
}
-// Consume message buffer into an io.Writer. Will block, should be called in a
+// Consume message buffer into the handler. Will block, should be called in a
// goroutine.
-// TODO: Not sure if this is a great API.
func (u *User) Consume() {
- for m := range u.msg {
- u.HandleMsg(m)
+ for {
+ select {
+ case <-u.done:
+ return
+ case m, ok := <-u.msg:
+ if !ok {
+ return
+ }
+ u.HandleMsg(m)
+ }
}
}
// Add message to consume by user
func (u *User) Send(m Message) error {
- u.mu.RLock()
- defer u.mu.RUnlock()
select {
case u.msg <- m:
+ case <-u.done:
+ return ErrUserClosed
default:
logger.Printf("Msg buffer full, closing: %s", u.Name())
u.Close()
}
// Default user configuration to use
-var DefaultUserConfig *UserConfig
+var DefaultUserConfig UserConfig
func init() {
- DefaultUserConfig = &UserConfig{
+ DefaultUserConfig = UserConfig{
Bell: true,
Quiet: false,
}
// Join the room as a user, will announce.
func (r *Room) Join(u *message.User) (*Member, error) {
- if r.closed {
- return nil, ErrRoomClosed
- }
+ // TODO: Check if closed
if u.Id() == "" {
return nil, ErrInvalidName
}
"bufio"
"crypto/rand"
"crypto/rsa"
+ "errors"
"io"
"io/ioutil"
"strings"
// First client
go func() {
- err := sshd.ConnectShell(s.Addr().String(), "foo", func(r io.Reader, w io.WriteCloser) {
+ err := sshd.ConnectShell(s.Addr().String(), "foo", func(r io.Reader, w io.WriteCloser) error {
scanner := bufio.NewScanner(r)
// Consume the initial buffer
// Wrap it up.
close(done)
+ return nil
})
if err != nil {
t.Fatal(err)
<-done
// Second client
- err = sshd.ConnectShell(s.Addr().String(), "foo", func(r io.Reader, w io.WriteCloser) {
+ err = sshd.ConnectShell(s.Addr().String(), "foo", func(r io.Reader, w io.WriteCloser) error {
scanner := bufio.NewScanner(r)
// Consume the initial buffer
if !strings.HasPrefix(actual, "[Guest1] ") {
t.Errorf("Second client did not get Guest1 name: %q", actual)
}
+ return nil
})
if err != nil {
t.Fatal(err)
target := s.Addr().String()
- err = sshd.ConnectShell(target, "foo", func(r io.Reader, w io.WriteCloser) {})
+ err = sshd.ConnectShell(target, "foo", func(r io.Reader, w io.WriteCloser) error { return nil })
if err != nil {
t.Error(err)
}
clientpubkey, _ := ssh.NewPublicKey(clientkey.Public())
auth.Whitelist(clientpubkey, 0)
- err = sshd.ConnectShell(target, "foo", func(r io.Reader, w io.WriteCloser) {})
+ err = sshd.ConnectShell(target, "foo", func(r io.Reader, w io.WriteCloser) error { return nil })
if err == nil {
t.Error("Failed to block unwhitelisted connection.")
}
go func() {
// First client
- err := sshd.ConnectShell(addr, "foo", func(r io.Reader, w io.WriteCloser) {
+ err := sshd.ConnectShell(addr, "foo", func(r io.Reader, w io.WriteCloser) error {
// Make op
member, _ := host.Room.MemberById("foo")
if member == nil {
- t.Fatal("failed to load MemberById")
+ return errors.New("failed to load MemberById")
}
host.Room.Ops.Add(member)
// Block until second client is here
connected <- struct{}{}
w.Write([]byte("/kick bar\r\n"))
+ return nil
})
if err != nil {
+ close(connected)
t.Fatal(err)
}
}()
go func() {
// Second client
- err := sshd.ConnectShell(addr, "bar", func(r io.Reader, w io.WriteCloser) {
+ err := sshd.ConnectShell(addr, "bar", func(r io.Reader, w io.WriteCloser) error {
<-connected
// Consume while we're connected. Should break when kicked.
- ioutil.ReadAll(r) // XXX?
+ ioutil.ReadAll(r)
+ return nil
})
if err != nil {
t.Fatal(err)
}
// ConnectShell makes a barebones SSH client session, used for testing.
-func ConnectShell(host string, name string, handler func(r io.Reader, w io.WriteCloser)) error {
+func ConnectShell(host string, name string, handler func(r io.Reader, w io.WriteCloser) error) error {
config := NewClientConfig(name)
conn, err := ssh.Dial("tcp", host, config)
if err != nil {
return err
}
- /* FIXME: Do we want to request a PTY?
- err = session.RequestPty("xterm", 80, 40, ssh.TerminalModes{})
- if err != nil {
- return err
- }
+ /*
+ err = session.RequestPty("xterm", 80, 40, ssh.TerminalModes{})
+ if err != nil {
+ return err
+ }
*/
err = session.Shell()
return err
}
- handler(out, in)
+ _, err = session.SendRequest("ping", true, nil)
+ if err != nil {
+ return err
+ }
- return nil
+ return handler(out, in)
}
host := s.Addr().String()
name := "foo"
- err = ConnectShell(host, name, func(r io.Reader, w io.WriteCloser) {
+ err = ConnectShell(host, name, func(r io.Reader, w io.WriteCloser) error {
// Consume if there is anything
buf := new(bytes.Buffer)
w.Write([]byte("hello\r\n"))
buf.Reset()
_, err := io.Copy(buf, r)
- if err != nil {
- t.Error(err)
- }
expected := "> hello\r\necho: hello\r\n"
actual := buf.String()
if actual != expected {
+ if err != nil {
+ t.Error(err)
+ }
t.Errorf("Got %q; expected %q", actual, expected)
}
s.Close()
+ return nil
})
if err != nil {