From e86996e6b5dc22dadbf2dd948b880340cc9d7134 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Thu, 15 Sep 2016 14:01:15 -0400 Subject: [PATCH] progress: Host User interface, and more interfaces in general, tests pass --- chat/member.go | 8 ++++ chat/message/user.go | 4 ++ chat/room.go | 3 +- client.go | 32 ++++++++++----- host.go | 92 ++++++++++++++------------------------------ whois.go | 23 ++++------- 6 files changed, 72 insertions(+), 90 deletions(-) diff --git a/chat/member.go b/chat/member.go index 471cdd4..07839f7 100644 --- a/chat/member.go +++ b/chat/member.go @@ -1,6 +1,8 @@ package chat import ( + "time" + "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/set" ) @@ -21,4 +23,10 @@ type Member interface { SetConfig(message.UserConfig) Send(message.Message) error + + Joined() time.Time + ReplyTo() message.Author + SetReplyTo(message.Author) + Prompt() string + SetHighlight(string) error } diff --git a/chat/message/user.go b/chat/message/user.go index 022f577..d16adff 100644 --- a/chat/message/user.go +++ b/chat/message/user.go @@ -38,3 +38,7 @@ func (u *User) Color() int { func (u *User) ID() string { return SanitizeName(u.name) } + +func (u *User) Joined() time.Time { + return u.joined +} diff --git a/chat/room.go b/chat/room.go index 790770c..975ff7f 100644 --- a/chat/room.go +++ b/chat/room.go @@ -199,7 +199,8 @@ func (r *Room) MemberByID(id string) (*roomMember, bool) { if err != nil { return nil, false } - return m.Value().(*roomMember), true + rm, ok := m.Value().(*roomMember) + return rm, ok } // IsOp returns whether a user is an operator in this room. diff --git a/client.go b/client.go index d602ead..5428719 100644 --- a/client.go +++ b/client.go @@ -1,21 +1,33 @@ package sshchat import ( - "time" + "sync" "github.com/shazow/ssh-chat/chat" - "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/sshd" ) -type Client struct { - user chat.Member - conn sshd.Connection - - timestamp time.Time +type client struct { + chat.Member + sync.Mutex + conns []sshd.Connection } -type Replier interface { - ReplyTo() message.Author - SetReplyTo(message.Author) +func (cl *client) Connections() []sshd.Connection { + return cl.conns +} + +func (cl *client) Close() error { + // TODO: Stack errors? + for _, conn := range cl.conns { + conn.Close() + } + return nil +} + +type User interface { + chat.Member + + Connections() []sshd.Connection + Close() error } diff --git a/host.go b/host.go index 3790066..8cf9b03 100644 --- a/host.go +++ b/host.go @@ -35,7 +35,7 @@ type Host struct { mu sync.Mutex motd string count int - clients map[chat.Member][]Client + clients map[chat.Member][]client } // NewHost creates a Host on top of an existing listener. @@ -46,7 +46,7 @@ func NewHost(listener *sshd.SSHListener, auth *Auth) *Host { listener: listener, commands: chat.Commands{}, auth: auth, - clients: map[chat.Member][]Client{}, + clients: map[chat.Member][]client{}, } // Make our own commands registry instance. @@ -75,10 +75,11 @@ func (h *Host) SetMotd(motd string) { // Connect a specific Terminal to this host and its room. func (h *Host) Connect(term *sshd.Terminal) { requestedName := term.Conn.Name() - user := message.BufferedScreen(requestedName, term) - - client := h.addClient(user, term.Conn) - defer h.removeClient(user, client) + screen := message.BufferedScreen(requestedName, term) + user := &client{ + Member: screen, + conns: []sshd.Connection{term.Conn}, + } h.mu.Lock() motd := h.motd @@ -91,10 +92,10 @@ func (h *Host) Connect(term *sshd.Terminal) { user.SetConfig(cfg) // Close term once user is closed. - defer user.Close() + defer screen.Close() defer term.Close() - go user.Consume() + go screen.Consume() // Send MOTD if motd != "" { @@ -180,44 +181,6 @@ func (h *Host) Connect(term *sshd.Terminal) { logger.Debugf("[%s] Leaving: %s", term.Conn.RemoteAddr(), user.Name()) } -func (h *Host) addClient(user chat.Member, conn sshd.Connection) *Client { - client := Client{ - user: user, - conn: conn, - timestamp: time.Now(), - } - h.mu.Lock() - if _, ok := h.clients[user]; ok { - logger.Warningf("user collision: %q", user) - } - h.clients[user] = append(h.clients[user], client) - h.mu.Unlock() - return &client -} - -func (h *Host) removeClient(user chat.Member, client *Client) { - h.mu.Lock() - defer h.mu.Unlock() - - clients := h.clients[user] - for i, c := range clients { - // Find the user - if &c != client { - continue - } - // Delete corresponding client - clients[i] = clients[len(clients)-1] - clients = clients[:len(clients)-1] - break - } -} - -func (h *Host) findClients(user chat.Member) []Client { - h.mu.Lock() - defer h.mu.Unlock() - return h.clients[user] -} - // Serve our chat room onto the listener func (h *Host) Serve() { h.listener.HandlerFunc = h.Connect @@ -244,7 +207,7 @@ func (h *Host) completeCommand(partial string) string { } // AutoCompleteFunction returns a callback for terminal autocompletion -func (h *Host) AutoCompleteFunction(u Replier) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) { +func (h *Host) AutoCompleteFunction(u User) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) { return func(line string, pos int, key rune) (newLine string, newPos int, ok bool) { if key != 9 { return @@ -301,12 +264,13 @@ func (h *Host) AutoCompleteFunction(u Replier) func(line string, pos int, key ru } // GetUser returns a message.User based on a name. -func (h *Host) GetUser(name string) (chat.Member, bool) { +func (h *Host) GetUser(name string) (User, bool) { m, ok := h.MemberByID(name) if !ok { return nil, false } - return m.Member, true + u, ok := m.Member.(User) + return u, ok } // InitCommands adds host-specific commands to a Commands container. These will @@ -336,7 +300,7 @@ func (h *Host) InitCommands(c *chat.Commands) { txt := fmt.Sprintf("[Sent PM to %s]", target.Name()) ms := message.NewSystemMsg(txt, msg.From()) room.Send(ms) - target.(Replier).SetReplyTo(msg.From()) + target.SetReplyTo(msg.From()) return nil }, }) @@ -352,7 +316,7 @@ func (h *Host) InitCommands(c *chat.Commands) { return errors.New("must specify message") } - target := msg.From().(Replier).ReplyTo() + target := msg.From().(chat.Member).ReplyTo() if target == nil { return errors.New("no message to reply to") } @@ -388,14 +352,12 @@ func (h *Host) InitCommands(c *chat.Commands) { return errors.New("user not found") } - // FIXME: Handle many clients - clients := h.findClients(target) var whois string switch room.IsOp(msg.From().(chat.Member)) { case true: - whois = whoisAdmin(clients) + whois = whoisAdmin(target) case false: - whois = whoisPublic(clients) + whois = whoisPublic(target) } room.Send(message.NewSystemMsg(whois, msg.From())) @@ -451,9 +413,12 @@ func (h *Host) InitCommands(c *chat.Commands) { room.Ops.Add(set.Keyize(user.ID())) } - for _, client := range h.findClients(user) { - h.auth.Op(client.conn.PublicKey(), until) - } + // TODO: Add pubkeys to op + /* + for _, conn := range user.Connections() { + h.auth.Op(conn.PublicKey(), until) + } + */ body := fmt.Sprintf("Made op by %s.", msg.From().Name()) room.Send(message.NewSystemMsg(body, user)) @@ -489,17 +454,16 @@ func (h *Host) InitCommands(c *chat.Commands) { until, _ = time.ParseDuration(args[1]) } - clients := h.findClients(target) - for _, client := range clients { - h.auth.Ban(client.conn.PublicKey(), until) - h.auth.BanAddr(client.conn.RemoteAddr(), until) + for _, conn := range target.Connections() { + h.auth.Ban(conn.PublicKey(), until) + h.auth.BanAddr(conn.RemoteAddr(), until) } body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name()) room.Send(message.NewAnnounceMsg(body)) - target.(io.Closer).Close() + target.Close() - logger.Debugf("Banned: \n-> %s", whoisAdmin(clients)) + logger.Debugf("Banned: \n-> %s", whoisAdmin(target)) return nil }, diff --git a/whois.go b/whois.go index df595c9..af496cc 100644 --- a/whois.go +++ b/whois.go @@ -2,7 +2,6 @@ package sshchat import ( "net" - "time" humanize "github.com/dustin/go-humanize" "github.com/shazow/ssh-chat/chat/message" @@ -11,28 +10,22 @@ import ( // Helpers for printing whois messages -type joinTimestamped interface { - Joined() time.Time -} - -func whoisPublic(clients []Client) string { - // FIXME: Handle many clients - conn, u := clients[0].conn, clients[0].user - +func whoisPublic(u User) string { fingerprint := "(no public key)" + // FIXME: Use all connections? + conn := u.Connections()[0] if conn.PublicKey() != nil { fingerprint = sshd.Fingerprint(conn.PublicKey()) } return "name: " + u.Name() + message.Newline + " > fingerprint: " + fingerprint + message.Newline + " > client: " + SanitizeData(string(conn.ClientVersion())) + message.Newline + - " > joined: " + humanize.Time(u.(joinTimestamped).Joined()) + " > joined: " + humanize.Time(u.Joined()) } -func whoisAdmin(clients []Client) string { - // FIXME: Handle many clients - conn, u := clients[0].conn, clients[0].user - +func whoisAdmin(u User) string { + // FIXME: Use all connections? + conn := u.Connections()[0] ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) fingerprint := "(no public key)" if conn.PublicKey() != nil { @@ -42,5 +35,5 @@ func whoisAdmin(clients []Client) string { " > ip: " + ip + message.Newline + " > fingerprint: " + fingerprint + message.Newline + " > client: " + SanitizeData(string(conn.ClientVersion())) + message.Newline + - " > joined: " + humanize.Time(u.(joinTimestamped).Joined()) + " > joined: " + humanize.Time(u.Joined()) }