Browse Source

Make Message an interface with attached handling behavior.

tags/v0.1.0
Jeremy Latt 12 years ago
parent
commit
fed72a7aa3
6 changed files with 190 additions and 96 deletions
  1. 0
    1
      ergonomadic.go
  2. 7
    22
      src/irc/client.go
  3. 158
    0
      src/irc/commands.go
  4. 11
    6
      src/irc/net.go
  5. 1
    0
      src/irc/protocol.go
  6. 13
    67
      src/irc/server.go

+ 0
- 1
ergonomadic.go View File

@@ -1,5 +1,4 @@
1 1
 package main
2
-// http://tools.ietf.org/html/rfc2812
3 2
 
4 3
 import (
5 4
 	"irc"

+ 7
- 22
src/irc/client.go View File

@@ -1,21 +1,13 @@
1 1
 package irc
2 2
 
3 3
 import (
4
-	"log"
5 4
 	"net"
6
-	"strings"
7 5
 )
8 6
 
9
-type Message struct {
10
-	command string
11
-	args    string
12
-	client  *Client
13
-}
14
-
15 7
 type Client struct {
16 8
 	addr       net.Addr
17
-	send       chan string
18
-	recv       chan string
9
+	send       chan<- string
10
+	recv       <-chan string
19 11
 	username   string
20 12
 	realname   string
21 13
 	nick       string
@@ -31,19 +23,12 @@ func NewClient(conn net.Conn) *Client {
31 23
 }
32 24
 
33 25
 // Adapt `chan string` to a `chan Message`.
34
-func (c *Client) Communicate(server *Server) {
35
-	go func() {
36
-		for str := range c.recv {
37
-			parts := strings.SplitN(str, " ", 2)
38
-			server.Send(Message{parts[0], parts[1], c})
26
+func (c *Client) Communicate(server chan<- *ClientMessage) {
27
+	for str := range c.recv {
28
+		m := ParseMessage(str)
29
+		if m != nil {
30
+			server <- &ClientMessage{c, m}
39 31
 		}
40
-	}()
41
-}
42
-
43
-func (c *Client) Send(lines ...string) {
44
-	for _, line := range lines {
45
-		log.Printf("C <- S: %s", line)
46
-		c.send <- line
47 32
 	}
48 33
 }
49 34
 

+ 158
- 0
src/irc/commands.go View File

@@ -0,0 +1,158 @@
1
+package irc
2
+
3
+import (
4
+	"strconv"
5
+	"strings"
6
+)
7
+
8
+type Message interface {
9
+	Handle(s *Server, c *Client)
10
+}
11
+
12
+type NewMessageFunc func([]string) Message
13
+
14
+type NickMessage struct {
15
+	nickname string
16
+}
17
+
18
+func NewNickMessage(args []string) Message {
19
+	if len(args) != 1 {
20
+		return nil
21
+	}
22
+	return &NickMessage{args[0]}
23
+}
24
+
25
+func (m *NickMessage) Handle(s *Server, c *Client) {
26
+	if s.nicks[m.nickname] != nil {
27
+		c.send <- ErrNickNameInUse(m.nickname)
28
+		return
29
+	}
30
+	if c.nick != "" {
31
+		delete(s.nicks, c.nick)
32
+	}
33
+	c.nick = m.nickname
34
+	s.nicks[c.nick] = c
35
+	tryRegister(s, c)
36
+}
37
+
38
+type UserMessage struct {
39
+	user string
40
+	mode uint8
41
+	unused string
42
+	realname string
43
+}
44
+
45
+func NewUserMessage(args []string) Message {
46
+	if len(args) != 4 {
47
+		return nil
48
+	}
49
+	msg := new(UserMessage)
50
+	msg.user = args[0]
51
+	mode, err := strconv.ParseUint(args[1], 10, 8)
52
+	if err == nil {
53
+		msg.mode = uint8(mode)
54
+	}
55
+	msg.unused = args[2]
56
+	msg.realname = args[3]
57
+	return msg
58
+}
59
+
60
+func (m *UserMessage) Handle(s *Server, c *Client) {
61
+	if c.username != "" {
62
+		c.send <- ErrAlreadyRegistered(c.Nick())
63
+		return
64
+	}
65
+	c.username, c.realname = m.user, m.realname
66
+	tryRegister(s, c)
67
+}
68
+
69
+type QuitMessage struct {
70
+	message string
71
+}
72
+
73
+func NewQuitMessage(args []string) Message {
74
+	msg := QuitMessage{}
75
+	if len(args) > 0 {
76
+		msg.message = args[0]
77
+	}
78
+	return &msg
79
+}
80
+
81
+func (m *QuitMessage) Handle(s *Server, c *Client) {
82
+	c.send <- MessageError()
83
+	delete(s.nicks, c.nick)
84
+}
85
+
86
+type UnknownMessage struct {
87
+	command string
88
+}
89
+
90
+func (m *UnknownMessage) Handle(s *Server, c *Client) {
91
+	c.send <- ErrUnknownCommand(c.Nick(), m.command)
92
+}
93
+
94
+type PingMessage struct {}
95
+
96
+func NewPingMessage(args []string) Message {
97
+	return &PingMessage{}
98
+}
99
+
100
+func (m *PingMessage) Handle(s *Server, c *Client) {
101
+	c.send <- MessagePong()
102
+}
103
+
104
+func tryRegister(s *Server, c *Client) {
105
+	if (!c.registered && c.nick != "" && c.username != "") {
106
+		c.registered = true
107
+		c.send <- ReplyWelcome(c.Nick(), c.username, "localhost")
108
+		c.send <- ReplyYourHost(c.Nick(), "irc.jlatt.com")
109
+		c.send <- ReplyCreated(c.Nick(), "2012/04/07")
110
+		c.send <- ReplyMyInfo(c.Nick(), "irc.jlatt.com")
111
+	}
112
+}
113
+
114
+func parseArg(line string) (string, string) {
115
+	if line == "" {
116
+		return "", ""
117
+	}
118
+
119
+	if strings.HasPrefix(line, ":") {
120
+		return line[1:], ""
121
+	}
122
+
123
+	parts := strings.SplitN(line, " ", 2)
124
+	arg := parts[0]
125
+	rest := ""
126
+	if len(parts) > 1 {
127
+		rest = parts[1]
128
+	}
129
+	return arg, rest
130
+}
131
+
132
+func parseLine(line string) (string, []string) {
133
+	args := make([]string, 0)
134
+	for arg, rest := parseArg(line); arg != ""; arg, rest = parseArg(rest) {
135
+		args = append(args, arg)
136
+	}
137
+	return args[0], args[1:]
138
+}
139
+
140
+var commands = map[string]NewMessageFunc {
141
+	"NICK": NewNickMessage,
142
+	"PING": NewPingMessage,
143
+	"QUIT": NewQuitMessage,
144
+	"USER": NewUserMessage,
145
+}
146
+
147
+func ParseMessage(line string) Message {
148
+	command, args := parseLine(line)
149
+	constructor := commands[command]
150
+	var msg Message
151
+	if constructor != nil {
152
+		msg = constructor(args)
153
+	}
154
+	if msg == nil {
155
+		msg = &UnknownMessage{command}
156
+	}
157
+	return msg
158
+}

+ 11
- 6
src/irc/net.go View File

@@ -7,18 +7,23 @@ import (
7 7
 	"net"
8 8
 )
9 9
 
10
+func readTrimmedLine(reader *bufio.Reader) (string, error) {
11
+	line, err := reader.ReadString('\n')
12
+	return strings.TrimSpace(line), err
13
+}
14
+
10 15
 // Adapt `net.Conn` to a `chan string`.
11
-func StringReadChan(conn net.Conn) chan string {
16
+func StringReadChan(conn net.Conn) <-chan string {
12 17
 	ch := make(chan string)
13 18
 	reader := bufio.NewReader(conn)
14 19
 	go func() {
15 20
 		for {
16
-			line, err := reader.ReadString('\n')
21
+			line, err := readTrimmedLine(reader)
17 22
 			if (line != "") {
18
-				ch <- strings.TrimSpace(line)
23
+				ch <- line
24
+				log.Printf("%s -> %s", conn.RemoteAddr(), line)
19 25
 			}
20 26
 			if err != nil {
21
-				log.Print("StringReadChan[read]: ", err)
22 27
 				break
23 28
 			}
24 29
 		}
@@ -27,16 +32,16 @@ func StringReadChan(conn net.Conn) chan string {
27 32
 	return ch
28 33
 }
29 34
 
30
-func StringWriteChan(conn net.Conn) chan string {
35
+func StringWriteChan(conn net.Conn) chan<- string {
31 36
 	ch := make(chan string)
32 37
 	writer := bufio.NewWriter(conn)
33 38
 	go func() {
34 39
 		for str := range ch {
35 40
 			if _, err := writer.WriteString(str + "\r\n"); err != nil {
36
-				log.Print("StringWriteChan[write]: ", err)
37 41
 				break
38 42
 			}
39 43
 			writer.Flush()
44
+			log.Printf("%s <- %s", conn.RemoteAddr(), str)
40 45
 		}
41 46
 		close(ch)
42 47
 	}()

+ 1
- 0
src/irc/protocol.go View File

@@ -1,3 +1,4 @@
1
+// http://tools.ietf.org/html/rfc2812
1 2
 package irc
2 3
 
3 4
 import (

+ 13
- 67
src/irc/server.go View File

@@ -3,19 +3,24 @@ package irc
3 3
 import (
4 4
 	"log"
5 5
 	"net"
6
-	"regexp"
7
-	"strings"
8 6
 )
9 7
 
10 8
 type Server struct {
11
-	ch chan Message
9
+	ch chan *ClientMessage
12 10
 	nicks map[string]*Client
13 11
 }
14 12
 
13
+type ClientMessage struct {
14
+	client *Client
15
+	message Message
16
+}
17
+
15 18
 func NewServer() *Server {
16
-	server := Server{make(chan Message), make(map[string]*Client)}
19
+	server := new(Server)
20
+	server.ch = make(chan *ClientMessage)
21
+	server.nicks = make(map[string]*Client)
17 22
 	go server.Receive()
18
-	return &server
23
+	return server
19 24
 }
20 25
 
21 26
 func (s *Server) Listen(addr string) {
@@ -30,71 +35,12 @@ func (s *Server) Listen(addr string) {
30 35
 			log.Print("Server.Listen: ", err)
31 36
 			continue
32 37
 		}
33
-		go NewClient(conn).Communicate(s)
38
+		go NewClient(conn).Communicate(s.ch)
34 39
 	}
35 40
 }
36 41
 
37 42
 func (s *Server) Receive() {
38
-	for message := range s.ch {
39
-		log.Printf("C -> S: %s %s", message.command, message.args)
40
-		switch message.command {
41
-		case "PING":
42
-			message.client.Send(MessagePong())
43
-		case "USER":
44
-			s.UserCommand(message.client, message.args)
45
-		case "NICK":
46
-			s.NickCommand(message.client, message.args)
47
-		case "QUIT":
48
-			s.QuitCommand(message.client, message.args)
49
-		default:
50
-			message.client.Send(ErrUnknownCommand(message.client.Nick(), message.command))
51
-		}
52
-	}
53
-}
54
-
55
-func (s *Server) Send(m Message) {
56
-	s.ch <- m
57
-}
58
-
59
-// commands
60
-
61
-func (s *Server) UserCommand(c *Client, args string) {
62
-	parts := strings.SplitN(args, " ", 4)
63
-	username, _, _, realname := parts[0], parts[1], parts[2], parts[3]
64
-	if c.username != "" {
65
-		c.Send(ErrAlreadyRegistered(c.nick))
66
-		return
67
-	}
68
-	c.username, c.realname = username, realname
69
-	s.TryRegister(c)
70
-}
71
-
72
-func (s *Server) NickCommand(c *Client, nick string) {
73
-	if s.nicks[nick] != nil {
74
-		c.Send(ErrNickNameInUse(nick))
75
-		return
76
-	}
77
-	c.nick = nick
78
-	s.nicks[nick] = c
79
-	s.TryRegister(c)
80
-}
81
-
82
-func (s *Server) TryRegister(c *Client) {
83
-	if (!c.registered && c.nick != "" && c.username != "") {
84
-		c.registered = true
85
-		c.Send(
86
-			ReplyWelcome(c.Nick(), c.username, "localhost"),
87
-			ReplyYourHost(c.Nick(), "irc.jlatt.com"),
88
-			ReplyCreated(c.Nick(), "2012/04/07"),
89
-			ReplyMyInfo(c.Nick(), "irc.jlatt.com"))
90
-	}
91
-}
92
-
93
-func (s *Server) QuitCommand(c *Client, args string) {
94
-	re := regexp.MustCompile("^" + RE_QUIT + "$")
95
-	matches := re.FindAllStringSubmatch(args, -1)
96
-	if matches != nil {
97
-		c.Send(MessageError())
43
+	for m := range s.ch {
44
+		m.message.Handle(s, m.client)
98 45
 	}
99
-	delete(s.nicks, c.nick)
100 46
 }

Loading…
Cancel
Save