From 8f0f04e2f27ca2d0e6859754a25bf627b1c04a2e Mon Sep 17 00:00:00 2001 From: Evan Burkey Date: Tue, 12 Sep 2023 14:19:14 -0700 Subject: [PATCH] finish budgetchat --- cmd/budgetchat/client.go | 96 ++++++++++++++++++++++++++++++++++++ cmd/budgetchat/main.go | 104 +++++++++++++++++++++++++-------------- cmd/budgetchat/room.go | 59 ---------------------- cmd/budgetchat/user.go | 27 ---------- go.sum | 4 ++ 5 files changed, 167 insertions(+), 123 deletions(-) create mode 100644 cmd/budgetchat/client.go delete mode 100644 cmd/budgetchat/room.go delete mode 100644 cmd/budgetchat/user.go create mode 100644 go.sum diff --git a/cmd/budgetchat/client.go b/cmd/budgetchat/client.go new file mode 100644 index 0000000..e7cf574 --- /dev/null +++ b/cmd/budgetchat/client.go @@ -0,0 +1,96 @@ +package main + +import ( + "errors" + "fmt" + "log" + "net" + "regexp" + "strings" +) + +type client struct { + c net.Conn + name string +} + +func isValidName(name string) bool { + pattern := "^[a-zA-Z0-9]+$" + nameReg, err := regexp.Compile(pattern) + if err != nil { + log.Fatalln(err) + } + return nameReg.MatchString(name) +} + +func cleanString(buf []byte) string { + s := string(buf) + idx := strings.Index(s, "\n") + if idx != -1 { + s = s[:idx] + } + return s +} + +func newClient(c net.Conn) (*client, error) { + cl := &client{ + c: c, + name: "", + } + + cl.sendMessageString("Welcome to budgetchat! What shall I call you?") + + r := make(chan string, 1) + var err error = nil + go func() { + for { + buf := make([]byte, 32) + _, err = c.Read(buf) + if err != nil { + c.Close() + r <- "" + return + } + name := cleanString(buf) + + // Check legality + if !isValidName(name) { + cl.sendMessageString("The name %s is invalid", string(name)) + err = errors.New("name invalid") + c.Close() + r <- name + return + } + + // Check for duplicate name + for _, cls := range room { + if cls.name == name { + cl.sendMessageString("The name %s is already taken", string(name)) + err = errors.New("name already taken") + c.Close() + r <- name + return + } + } + r <- name + return + } + }() + + cl.name = <-r + if err != nil { + return nil, err + } + return cl, nil +} + +func (cl *client) sendMessage(m []byte) { + _, err := cl.c.Write(m) + if err != nil { + log.Fatalln(err) + } +} + +func (cl *client) sendMessageString(m string, vars ...any) { + cl.sendMessage([]byte(fmt.Sprintf(m, vars...) + "\n")) +} diff --git a/cmd/budgetchat/main.go b/cmd/budgetchat/main.go index c69ab45..2ac61f5 100644 --- a/cmd/budgetchat/main.go +++ b/cmd/budgetchat/main.go @@ -3,53 +3,83 @@ package main import ( "bufio" "fmt" + "log" "net" - "protohackers/pkg/conn" ) +type message struct { + msg []byte + client *client +} + +func msg(s string, c *client) message { + return message{[]byte(s), c} +} + +var ( + room = make([]*client, 0) + messages = make(chan message, 32) +) + func main() { - l, err := net.Listen(conn.Type, conn.Port) + go broadcast() + err := conn.StartSimple(newUserHandler) if err != nil { - panic(err) + log.Fatalln(err) } - defer l.Close() +} - fmt.Println(fmt.Sprintf("Listening on port %s", conn.Port)) - - r := createRoom() +func broadcast() { for { - c, err := l.Accept() - if err != nil { + select { + case m := <-messages: + for _, cl := range room { + if m.client == cl { + continue + } + cl.sendMessage(m.msg) + } + } + } +} + +func userList() []byte { + m := "* The room contains: " + if len(room) > 0 { + for _, c := range room { + m += fmt.Sprintf("%s, ", c.name) + } + m = m[:len(m)-2] + } + return []byte(m + "\n") +} + +func newUserHandler(c net.Conn) { + cl, err := newClient(c) + if err != nil { + return + } + + cl.sendMessage(userList()) + fmt.Printf("New client %s\n", cl.name) + messages <- msg(fmt.Sprintf("* %s has entered the room\n", cl.name), cl) + room = append(room, cl) + + input := bufio.NewScanner(cl.c) + for input.Scan() { + m := fmt.Sprintf("[%s] %s\n", cl.name, input.Text()) + messages <- msg(m, cl) + } + + for i, p := range room { + if p == cl { + name := cl.name + cl.c.Close() + room = append(room[:i], room[i+1:]...) + fmt.Printf("%s has left", cl.name) + messages <- msg(fmt.Sprintf("* %s has left the room\n", name), cl) break } - go handleConn(r, c) } } - -func handleConn(r *room, c net.Conn) { - u := newUser(c) - defer c.Close() - go u.sendMessage() - - u.write("Welcome to budgetchat! What shall I call you?") - - s := bufio.NewScanner(u.conn) - for s.Scan() { - input := s.Text() - if !u.online { - r.sendToRest(r.joinMsg(u), u) - u.write(r.connectMsg()) - r.joinUser(u) - } else { - r.sendToRest(msgWrap(input, u), u) - } - if s.Err() != nil { - return - } - } -} - -func msgWrap(msg string, u *user) string { - return fmt.Sprintf("[%s] %s", u.nick, msg) -} diff --git a/cmd/budgetchat/room.go b/cmd/budgetchat/room.go deleted file mode 100644 index 770e9a2..0000000 --- a/cmd/budgetchat/room.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type room struct { - users []*user -} - -func createRoom() *room { - return &room{ - users: make([]*user, 16), - } -} - -func (r *room) joinUser(u *user) { - for _, usr := range r.users { - if usr.nick == u.nick { - - } - } - r.users = append(r.users, u) - u.online = true -} - -func (r *room) sendToAll(msg string) { - for _, u := range r.users { - if u.online { - u.write(msg) - } - } -} - -func (r *room) sendToRest(msg string, skip *user) { - for _, u := range r.users { - if u.online && u != skip { - u.write(msg) - } - } -} - -func (r *room) connected() (nicks []string) { - for _, u := range r.users { - if u.online { - nicks = append(nicks, u.nick) - } - } - return nicks -} - -func (r *room) joinMsg(u *user) string { - return fmt.Sprintf("* %s has joined the channel", u.nick) -} - -func (r *room) connectMsg() string { - return fmt.Sprintf("* The room contains: %s", strings.Join(r.connected(), ", ")) -} diff --git a/cmd/budgetchat/user.go b/cmd/budgetchat/user.go deleted file mode 100644 index 738ac3d..0000000 --- a/cmd/budgetchat/user.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import "net" - -type user struct { - conn net.Conn - nick string - online bool - output chan string -} - -func newUser(conn net.Conn) *user { - return &user{ - conn: conn, - output: make(chan string), - } -} - -func (u *user) write(s string) { - u.output <- s + "\n" -} - -func (u *user) sendMessage() { - for line := range u.output { - u.conn.Write([]byte(line)) - } -} diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..783d6b7 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=