From adcf12e1fa0e6582484e72fb5d1ee6309ddfe39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Sat, 6 Jun 2015 00:34:13 +0200 Subject: [PATCH] Pull IRC client out --- irc/client.go | 129 ++++++ irc/conn.go | 177 ++++++++ irc/const.go | 35 ++ irc/message.go | 48 +++ server/conn.go | 62 +++ server/irc.go | 379 ------------------ server/{message_handler.go => irc_handler.go} | 109 ++--- server/server.go | 17 +- server/session.go | 23 +- server/websocket.go | 47 --- server/websocket_handler.go | 79 ++-- 11 files changed, 567 insertions(+), 538 deletions(-) create mode 100644 irc/client.go create mode 100644 irc/conn.go create mode 100644 irc/const.go create mode 100644 irc/message.go create mode 100644 server/conn.go delete mode 100644 server/irc.go rename server/{message_handler.go => irc_handler.go} (57%) delete mode 100644 server/websocket.go diff --git a/irc/client.go b/irc/client.go new file mode 100644 index 00000000..9ceb6c49 --- /dev/null +++ b/irc/client.go @@ -0,0 +1,129 @@ +package irc + +import ( + "bufio" + "crypto/tls" + "net" + "strings" + "sync" +) + +type Client struct { + Server string + Host string + TLS bool + TLSConfig *tls.Config + Password string + Username string + Realname string + Messages chan *Message + + nick string + + conn net.Conn + connected bool + dialer *net.Dialer + reader *bufio.Reader + out chan string + + quit chan struct{} + reconnect chan struct{} + ready sync.WaitGroup + once sync.Once + lock sync.Mutex +} + +func NewClient(nick, username string) *Client { + return &Client{ + nick: nick, + Username: username, + Realname: nick, + Messages: make(chan *Message, 32), + out: make(chan string, 32), + quit: make(chan struct{}), + reconnect: make(chan struct{}), + } +} + +func (c *Client) GetNick() string { + c.lock.Lock() + defer c.lock.Unlock() + + return c.nick +} + +func (c *Client) Connected() bool { + c.lock.Lock() + defer c.lock.Unlock() + + return c.connected +} + +func (c *Client) Pass(password string) { + c.write("PASS " + password) +} + +func (c *Client) Nick(nick string) { + c.Write("NICK " + nick) + + c.lock.Lock() + c.nick = nick + c.lock.Unlock() +} + +func (c *Client) User(username, realname string) { + c.writef("USER %s 0 * :%s", username, realname) +} + +func (c *Client) Oper(name, password string) { + c.Write("OPER " + name + " " + password) +} + +func (c *Client) Mode(target, modes, params string) { + c.Write(strings.TrimRight("MODE "+target+" "+modes+" "+params, " ")) +} + +func (c *Client) Quit() { + go func() { + if c.Connected() { + c.write("QUIT") + } + close(c.quit) + }() +} + +func (c *Client) Join(channels ...string) { + c.Write("JOIN " + strings.Join(channels, ",")) +} + +func (c *Client) Part(channels ...string) { + c.Write("PART " + strings.Join(channels, ",")) +} + +func (c *Client) Topic(channel string) { + c.Write("TOPIC " + channel) +} + +func (c *Client) Invite(nick, channel string) { + c.Write("INVITE " + nick + " " + channel) +} + +func (c *Client) Kick(channel string, users ...string) { + c.Write("KICK " + channel + " " + strings.Join(users, ",")) +} + +func (c *Client) Privmsg(target, msg string) { + c.Writef("PRIVMSG %s :%s", target, msg) +} + +func (c *Client) Notice(target, msg string) { + c.Writef("NOTICE %s :%s", target, msg) +} + +func (c *Client) Whois(nick string) { + c.Write("WHOIS " + nick) +} + +func (c *Client) Away(message string) { + c.Write("AWAY :" + message) +} diff --git a/irc/conn.go b/irc/conn.go new file mode 100644 index 00000000..7a1003a7 --- /dev/null +++ b/irc/conn.go @@ -0,0 +1,177 @@ +package irc + +import ( + "bufio" + "crypto/tls" + "fmt" + "net" + "strings" + "sync" + "time" +) + +func (c *Client) Connect(address string) { + if idx := strings.Index(address, ":"); idx < 0 { + c.Host = address + + if c.TLS { + address += ":6697" + } else { + address += ":6667" + } + } else { + c.Host = address[:idx] + } + c.Server = address + c.dialer = &net.Dialer{Timeout: 10 * time.Second} + + go c.run() +} + +func (c *Client) Write(data string) { + c.out <- data + "\r\n" +} + +func (c *Client) Writef(format string, a ...interface{}) { + c.out <- fmt.Sprintf(format+"\r\n", a...) +} + +func (c *Client) write(data string) { + c.conn.Write([]byte(data + "\r\n")) +} + +func (c *Client) writef(format string, a ...interface{}) { + fmt.Fprintf(c.conn, format+"\r\n", a...) +} + +func (c *Client) connect() error { + if c.TLS { + if c.TLSConfig == nil { + c.TLSConfig = &tls.Config{InsecureSkipVerify: true} + } + + if conn, err := tls.DialWithDialer(c.dialer, "tcp", c.Server, c.TLSConfig); err != nil { + return err + } else { + c.conn = conn + } + } else { + if conn, err := c.dialer.Dial("tcp", c.Server); err != nil { + return err + } else { + c.conn = conn + } + } + + c.lock.Lock() + c.connected = true + c.lock.Unlock() + + c.reader = bufio.NewReader(c.conn) + + if c.Password != "" { + c.Pass(c.Password) + } + c.write("NICK " + c.nick) + c.User(c.Username, c.Realname) + + c.ready.Add(1) + go c.send() + go c.recv() + + return nil +} + +func (c *Client) tryConnect() { + // TODO: backoff + for { + select { + case <-c.quit: + return + default: + } + + err := c.connect() + if err == nil { + return + } + } +} + +func (c *Client) run() { + c.tryConnect() + for { + select { + case <-c.quit: + c.close() + c.lock.Lock() + c.connected = false + c.lock.Unlock() + return + + case <-c.reconnect: + c.reconnect = make(chan struct{}) + c.once = sync.Once{} + c.tryConnect() + } + } +} + +func (c *Client) send() { + c.ready.Wait() + for { + select { + case <-c.quit: + return + + case <-c.reconnect: + return + + case msg := <-c.out: + _, err := c.conn.Write([]byte(msg)) + if err != nil { + return + } + } + } +} + +func (c *Client) recv() { + defer c.conn.Close() + for { + line, err := c.reader.ReadString('\n') + if err != nil { + c.lock.Lock() + c.connected = false + c.lock.Unlock() + c.once.Do(c.ready.Done) + close(c.reconnect) + return + } + + select { + case <-c.quit: + return + default: + } + + msg := parseMessage(line) + c.Messages <- msg + + switch msg.Command { + case Ping: + go c.write("PONG :" + msg.Trailing) + + case ReplyWelcome: + c.once.Do(c.ready.Done) + } + } +} + +func (c *Client) close() { + if c.Connected() { + c.once.Do(c.ready.Done) + } + close(c.out) + close(c.Messages) +} diff --git a/irc/const.go b/irc/const.go new file mode 100644 index 00000000..d2c053e3 --- /dev/null +++ b/irc/const.go @@ -0,0 +1,35 @@ +package irc + +const ( + Ping = "PING" + Nick = "NICK" + Join = "JOIN" + Part = "PART" + Mode = "MODE" + Privmsg = "PRIVMSG" + Notice = "NOTICE" + Topic = "TOPIC" + Quit = "QUIT" + + ReplyWelcome = "001" + ReplyYourHost = "002" + ReplyCreated = "003" + ReplyLUserClient = "251" + ReplyLUserOp = "252" + ReplyLUserUnknown = "253" + ReplyLUserChannels = "254" + ReplyLUserMe = "255" + ReplyAway = "301" + ReplyWhoisUser = "311" + ReplyWhoisServer = "312" + ReplyWhoisOperator = "313" + ReplyWhoisIdle = "317" + ReplyEndOfWhois = "318" + ReplyWhoisChannels = "319" + ReplyTopic = "332" + ReplyNamReply = "353" + ReplyEndOfNames = "366" + ReplyMotd = "372" + ReplyMotdStart = "375" + ReplyEndOfMotd = "376" +) diff --git a/irc/message.go b/irc/message.go new file mode 100644 index 00000000..8ff822d4 --- /dev/null +++ b/irc/message.go @@ -0,0 +1,48 @@ +package irc + +import ( + "strings" +) + +type Message struct { + Prefix string + Nick string + Command string + Params []string + Trailing string +} + +func parseMessage(line string) *Message { + line = strings.Trim(line, "\r\n") + msg := Message{} + cmdStart := 0 + cmdEnd := len(line) + + if strings.HasPrefix(line, ":") { + cmdStart = strings.Index(line, " ") + 1 + msg.Prefix = line[1 : cmdStart-1] + + if i := strings.Index(msg.Prefix, "!"); i > 0 { + msg.Nick = msg.Prefix[:i] + } else { + msg.Nick = msg.Prefix + } + } + + if i := strings.Index(line, " :"); i > 0 { + cmdEnd = i + msg.Trailing = line[i+2:] + } + + cmd := strings.Split(line[cmdStart:cmdEnd], " ") + msg.Command = cmd[0] + if len(cmd) > 1 { + msg.Params = cmd[1:] + } + + if msg.Trailing != "" { + msg.Params = append(msg.Params, msg.Trailing) + } + + return &msg +} diff --git a/server/conn.go b/server/conn.go new file mode 100644 index 00000000..38ee3a83 --- /dev/null +++ b/server/conn.go @@ -0,0 +1,62 @@ +package server + +import ( + "time" + + "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket" +) + +type conn struct { + conn *websocket.Conn + in chan WSRequest + out chan []byte +} + +func newConn(ws *websocket.Conn) *conn { + return &conn{ + conn: ws, + in: make(chan WSRequest, 32), + out: make(chan []byte, 32), + } +} + +func (c *conn) send() { + var err error + ping := time.Tick(20 * time.Second) + + for { + select { + case msg, ok := <-c.out: + if !ok { + return + } + + err = c.conn.WriteMessage(websocket.TextMessage, msg) + + case <-ping: + err = c.conn.WriteJSON(WSResponse{Type: "ping"}) + } + + if err != nil { + return + } + } +} + +func (c *conn) recv() { + var req WSRequest + + for { + err := c.conn.ReadJSON(&req) + if err != nil { + close(c.in) + return + } + + c.in <- req + } +} + +func (c *conn) close() { + close(c.out) +} diff --git a/server/irc.go b/server/irc.go deleted file mode 100644 index f24e4f6d..00000000 --- a/server/irc.go +++ /dev/null @@ -1,379 +0,0 @@ -package server - -import ( - "bufio" - "crypto/tls" - "fmt" - "net" - "strings" - "sync" - "time" -) - -const ( - PING = "PING" - NICK = "NICK" - JOIN = "JOIN" - PART = "PART" - MODE = "MODE" - PRIVMSG = "PRIVMSG" - NOTICE = "NOTICE" - TOPIC = "TOPIC" - QUIT = "QUIT" - - RPL_WELCOME = "001" - RPL_YOURHOST = "002" - RPL_CREATED = "003" - RPL_LUSERCLIENT = "251" - RPL_LUSEROP = "252" - RPL_LUSERUNKNOWN = "253" - RPL_LUSERCHANNELS = "254" - RPL_LUSERME = "255" - - RPL_AWAY = "301" - - RPL_WHOISUSER = "311" - RPL_WHOISSERVER = "312" - RPL_WHOISOPERATOR = "313" - RPL_WHOISIDLE = "317" - RPL_ENDOFWHOIS = "318" - RPL_WHOISCHANNELS = "319" - - RPL_TOPIC = "332" - - RPL_NAMREPLY = "353" - RPL_ENDOFNAMES = "366" - - RPL_MOTD = "372" - RPL_MOTDSTART = "375" - RPL_ENDOFMOTD = "376" -) - -type Message struct { - Prefix string - Nick string - Command string - Params []string - Trailing string -} - -type IRC struct { - conn net.Conn - connected bool - dialer *net.Dialer - reader *bufio.Reader - out chan string - - quit chan struct{} - reconnect chan struct{} - ready sync.WaitGroup - once sync.Once - lock sync.Mutex - - nick string - - Messages chan *Message - Server string - Host string - TLS bool - TLSConfig *tls.Config - Password string - Username string - Realname string -} - -func NewIRC(nick, username string) *IRC { - return &IRC{ - nick: nick, - Username: username, - Realname: nick, - Messages: make(chan *Message, 32), - out: make(chan string, 32), - quit: make(chan struct{}), - reconnect: make(chan struct{}), - } -} - -func (i *IRC) Connect(address string) { - if idx := strings.Index(address, ":"); idx < 0 { - i.Host = address - - if i.TLS { - address += ":6697" - } else { - address += ":6667" - } - } else { - i.Host = address[:idx] - } - i.Server = address - i.dialer = &net.Dialer{Timeout: 10 * time.Second} - - go i.run() -} - -func (i *IRC) Connected() bool { - i.lock.Lock() - defer i.lock.Unlock() - - return i.connected -} - -func (i *IRC) Pass(password string) { - i.write("PASS " + password) -} - -func (i *IRC) Nick(nick string) { - i.Write("NICK " + nick) - - i.lock.Lock() - i.nick = nick - i.lock.Unlock() -} - -func (i *IRC) User(username, realname string) { - i.writef("USER %s 0 * :%s", username, realname) -} - -func (i *IRC) Oper(name, password string) { - i.Write("OPER " + name + " " + password) -} - -func (i *IRC) Mode(target, modes, params string) { - i.Write(strings.TrimRight("MODE "+target+" "+modes+" "+params, " ")) -} - -func (i *IRC) Quit() { - go func() { - if i.Connected() { - i.write("QUIT") - } - close(i.quit) - }() -} - -func (i *IRC) Join(channels ...string) { - i.Write("JOIN " + strings.Join(channels, ",")) -} - -func (i *IRC) Part(channels ...string) { - i.Write("PART " + strings.Join(channels, ",")) -} - -func (i *IRC) Topic(channel string) { - i.Write("TOPIC " + channel) -} - -func (i *IRC) Invite(nick, channel string) { - i.Write("INVITE " + nick + " " + channel) -} - -func (i *IRC) Kick(channel string, users ...string) { - i.Write("KICK " + channel + " " + strings.Join(users, ",")) -} - -func (i *IRC) Privmsg(target, msg string) { - i.Writef("PRIVMSG %s :%s", target, msg) -} - -func (i *IRC) Notice(target, msg string) { - i.Writef("NOTICE %s :%s", target, msg) -} - -func (i *IRC) Whois(nick string) { - i.Write("WHOIS " + nick) -} - -func (i *IRC) Away(message string) { - i.Write("AWAY :" + message) -} - -func (i *IRC) GetNick() string { - i.lock.Lock() - defer i.lock.Unlock() - - return i.nick -} - -func (i *IRC) Write(data string) { - i.out <- data + "\r\n" -} - -func (i *IRC) Writef(format string, a ...interface{}) { - i.out <- fmt.Sprintf(format+"\r\n", a...) -} - -func (i *IRC) write(data string) { - i.conn.Write([]byte(data + "\r\n")) -} - -func (i *IRC) writef(format string, a ...interface{}) { - fmt.Fprintf(i.conn, format+"\r\n", a...) -} - -func (i *IRC) run() { - i.tryConnect() - for { - select { - case <-i.quit: - i.close() - i.lock.Lock() - i.connected = false - i.lock.Unlock() - return - - case <-i.reconnect: - i.reconnect = make(chan struct{}) - i.once = sync.Once{} - i.tryConnect() - } - } -} - -func (i *IRC) tryConnect() { - // TODO: backoff - for { - select { - case <-i.quit: - return - default: - } - - err := i.connect() - if err == nil { - return - } - } -} - -func (i *IRC) connect() error { - if i.TLS { - if i.TLSConfig == nil { - i.TLSConfig = &tls.Config{InsecureSkipVerify: true} - } - - if conn, err := tls.DialWithDialer(i.dialer, "tcp", i.Server, i.TLSConfig); err != nil { - return err - } else { - i.conn = conn - } - } else { - if conn, err := i.dialer.Dial("tcp", i.Server); err != nil { - return err - } else { - i.conn = conn - } - } - - i.lock.Lock() - i.connected = true - i.lock.Unlock() - - i.reader = bufio.NewReader(i.conn) - - if i.Password != "" { - i.Pass(i.Password) - } - i.write("NICK " + i.nick) - i.User(i.Username, i.Realname) - - i.ready.Add(1) - go i.send() - go i.recv() - - return nil -} - -func (i *IRC) send() { - i.ready.Wait() - for { - select { - case <-i.quit: - return - - case <-i.reconnect: - return - - case msg := <-i.out: - _, err := i.conn.Write([]byte(msg)) - if err != nil { - return - } - } - } -} - -func (i *IRC) recv() { - defer i.conn.Close() - for { - line, err := i.reader.ReadString('\n') - if err != nil { - i.lock.Lock() - i.connected = false - i.lock.Unlock() - i.once.Do(i.ready.Done) - close(i.reconnect) - return - } - - select { - case <-i.quit: - return - default: - } - - msg := parseMessage(line) - i.Messages <- msg - - switch msg.Command { - case PING: - go i.write("PONG :" + msg.Trailing) - - case RPL_WELCOME: - i.once.Do(i.ready.Done) - } - } -} - -func (i *IRC) close() { - if i.Connected() { - i.once.Do(i.ready.Done) - } - close(i.out) - close(i.Messages) -} - -func parseMessage(line string) *Message { - line = strings.Trim(line, "\r\n") - msg := Message{} - cmdStart := 0 - cmdEnd := len(line) - - if strings.HasPrefix(line, ":") { - cmdStart = strings.Index(line, " ") + 1 - msg.Prefix = line[1 : cmdStart-1] - - if i := strings.Index(msg.Prefix, "!"); i > 0 { - msg.Nick = msg.Prefix[:i] - } else { - msg.Nick = msg.Prefix - } - } - - if i := strings.Index(line, " :"); i > 0 { - cmdEnd = i - msg.Trailing = line[i+2:] - } - - cmd := strings.Split(line[cmdStart:cmdEnd], " ") - msg.Command = cmd[0] - if len(cmd) > 1 { - msg.Params = cmd[1:] - } - - if msg.Trailing != "" { - msg.Params = append(msg.Params, msg.Trailing) - } - - return &msg -} diff --git a/server/message_handler.go b/server/irc_handler.go similarity index 57% rename from server/message_handler.go rename to server/irc_handler.go index d8a7fff8..adf35f14 100644 --- a/server/message_handler.go +++ b/server/irc_handler.go @@ -4,86 +4,87 @@ import ( "log" "strings" + "github.com/khlieng/name_pending/irc" "github.com/khlieng/name_pending/storage" ) -func handleMessages(irc *IRC, session *Session) { +func handleIRC(client *irc.Client, session *Session) { var whois WhoisReply userBuffers := make(map[string][]string) var motd MOTD for { - msg, ok := <-irc.Messages + msg, ok := <-client.Messages if !ok { - session.deleteIRC(irc.Host) + session.deleteIRC(client.Host) return } switch msg.Command { - case NICK: + case irc.Nick: session.sendJSON("nick", Nick{ - Server: irc.Host, + Server: client.Host, Old: msg.Nick, New: msg.Trailing, }) - channelStore.RenameUser(msg.Nick, msg.Trailing, irc.Host) + channelStore.RenameUser(msg.Nick, msg.Trailing, client.Host) - case JOIN: + case irc.Join: session.sendJSON("join", Join{ - Server: irc.Host, + Server: client.Host, User: msg.Nick, Channels: msg.Params, }) - channelStore.AddUser(msg.Nick, irc.Host, msg.Params[0]) + channelStore.AddUser(msg.Nick, client.Host, msg.Params[0]) - if msg.Nick == irc.GetNick() { + if msg.Nick == client.GetNick() { session.user.AddChannel(storage.Channel{ - Server: irc.Host, + Server: client.Host, Name: msg.Params[0], }) } - case PART: + case irc.Part: session.sendJSON("part", Part{ Join: Join{ - Server: irc.Host, + Server: client.Host, User: msg.Nick, Channels: msg.Params, }, Reason: msg.Trailing, }) - channelStore.RemoveUser(msg.Nick, irc.Host, msg.Params[0]) + channelStore.RemoveUser(msg.Nick, client.Host, msg.Params[0]) - if msg.Nick == irc.GetNick() { - session.user.RemoveChannel(irc.Host, msg.Params[0]) + if msg.Nick == client.GetNick() { + session.user.RemoveChannel(client.Host, msg.Params[0]) } - case MODE: + case irc.Mode: target := msg.Params[0] if len(msg.Params) > 2 && isChannel(target) { mode := parseMode(msg.Params[1]) - mode.Server = irc.Host + mode.Server = client.Host mode.Channel = target mode.User = msg.Params[2] session.sendJSON("mode", mode) - channelStore.SetMode(irc.Host, target, msg.Params[2], mode.Add, mode.Remove) + channelStore.SetMode(client.Host, target, msg.Params[2], mode.Add, mode.Remove) } - case PRIVMSG, NOTICE: - if msg.Params[0] == irc.GetNick() { + case irc.Privmsg, irc.Notice: + if msg.Params[0] == client.GetNick() { session.sendJSON("pm", Chat{ - Server: irc.Host, + Server: client.Host, From: msg.Nick, Message: msg.Trailing, }) } else { session.sendJSON("message", Chat{ - Server: irc.Host, + Server: client.Host, From: msg.Nick, To: msg.Params[0], Message: msg.Trailing, @@ -91,90 +92,90 @@ func handleMessages(irc *IRC, session *Session) { } if msg.Params[0] != "*" { - session.user.LogMessage(irc.Host, msg.Nick, msg.Params[0], msg.Trailing) + session.user.LogMessage(client.Host, msg.Nick, msg.Params[0], msg.Trailing) } - case QUIT: + case irc.Quit: session.sendJSON("quit", Quit{ - Server: irc.Host, + Server: client.Host, User: msg.Nick, Reason: msg.Trailing, }) - channelStore.RemoveUserAll(msg.Nick, irc.Host) + channelStore.RemoveUserAll(msg.Nick, client.Host) - case RPL_WELCOME, - RPL_YOURHOST, - RPL_CREATED, - RPL_LUSERCLIENT, - RPL_LUSEROP, - RPL_LUSERUNKNOWN, - RPL_LUSERCHANNELS, - RPL_LUSERME: + case irc.ReplyWelcome, + irc.ReplyYourHost, + irc.ReplyCreated, + irc.ReplyLUserClient, + irc.ReplyLUserOp, + irc.ReplyLUserUnknown, + irc.ReplyLUserChannels, + irc.ReplyLUserMe: session.sendJSON("pm", Chat{ - Server: irc.Host, + Server: client.Host, From: msg.Nick, Message: strings.Join(msg.Params[1:], " "), }) - case RPL_WHOISUSER: + case irc.ReplyWhoisUser: whois.Nick = msg.Params[1] whois.Username = msg.Params[2] whois.Host = msg.Params[3] whois.Realname = msg.Params[5] - case RPL_WHOISSERVER: + case irc.ReplyWhoisServer: whois.Server = msg.Params[2] - case RPL_WHOISCHANNELS: + case irc.ReplyWhoisChannels: whois.Channels = append(whois.Channels, strings.Split(strings.TrimRight(msg.Trailing, " "), " ")...) - case RPL_ENDOFWHOIS: + case irc.ReplyEndOfWhois: session.sendJSON("whois", whois) whois = WhoisReply{} - case RPL_TOPIC: + case irc.ReplyTopic: session.sendJSON("topic", Topic{ - Server: irc.Host, + Server: client.Host, Channel: msg.Params[1], Topic: msg.Trailing, }) - channelStore.SetTopic(msg.Trailing, irc.Host, msg.Params[1]) + channelStore.SetTopic(msg.Trailing, client.Host, msg.Params[1]) - case RPL_NAMREPLY: + case irc.ReplyNamReply: users := strings.Split(msg.Trailing, " ") userBuffer := userBuffers[msg.Params[2]] userBuffers[msg.Params[2]] = append(userBuffer, users...) - case RPL_ENDOFNAMES: + case irc.ReplyEndOfNames: channel := msg.Params[1] users := userBuffers[channel] session.sendJSON("users", Userlist{ - Server: irc.Host, + Server: client.Host, Channel: channel, Users: users, }) - channelStore.SetUsers(users, irc.Host, channel) + channelStore.SetUsers(users, client.Host, channel) delete(userBuffers, channel) - case RPL_MOTDSTART: - motd.Server = irc.Host + case irc.ReplyMotdStart: + motd.Server = client.Host motd.Title = msg.Trailing - case RPL_MOTD: + case irc.ReplyMotd: motd.Content = append(motd.Content, msg.Trailing) - case RPL_ENDOFMOTD: + case irc.ReplyEndOfMotd: session.sendJSON("motd", motd) motd = MOTD{} default: - printMessage(msg, irc) + printMessage(msg, client) } } } @@ -202,6 +203,6 @@ func isChannel(s string) bool { return strings.IndexAny(s, "&#+!") == 0 } -func printMessage(msg *Message, irc *IRC) { - log.Println(irc.GetNick()+":", msg.Prefix, msg.Command, msg.Params, msg.Trailing) +func printMessage(msg *irc.Message, i *irc.Client) { + log.Println(i.GetNick()+":", msg.Prefix, msg.Command, msg.Params, msg.Trailing) } diff --git a/server/server.go b/server/server.go index d5468855..9279270d 100644 --- a/server/server.go +++ b/server/server.go @@ -9,6 +9,7 @@ import ( "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket" "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/julienschmidt/httprouter" + "github.com/khlieng/name_pending/irc" "github.com/khlieng/name_pending/storage" ) @@ -63,16 +64,16 @@ func reconnect() { channels := user.GetChannels() for _, server := range user.GetServers() { - irc := NewIRC(server.Nick, server.Username) - irc.TLS = server.TLS - irc.Password = server.Password - irc.Realname = server.Realname + i := irc.NewClient(server.Nick, server.Username) + i.TLS = server.TLS + i.Password = server.Password + i.Realname = server.Realname go func(server storage.Server) { - irc.Connect(server.Address) - session.setIRC(irc.Host, irc) + i.Connect(server.Address) + session.setIRC(i.Host, i) - go handleMessages(irc, session) + go handleIRC(i, session) var joining []string for _, channel := range channels { @@ -80,7 +81,7 @@ func reconnect() { joining = append(joining, channel.Name) } } - irc.Join(joining...) + i.Join(joining...) }(server) } } diff --git a/server/session.go b/server/session.go index 94663f44..c412aea2 100644 --- a/server/session.go +++ b/server/session.go @@ -4,14 +4,15 @@ import ( "encoding/json" "sync" + "github.com/khlieng/name_pending/irc" "github.com/khlieng/name_pending/storage" ) type Session struct { - irc map[string]*IRC + irc map[string]*irc.Client ircLock sync.Mutex - ws map[string]*WebSocket + ws map[string]*conn wsLock sync.Mutex out chan []byte @@ -20,23 +21,23 @@ type Session struct { func NewSession() *Session { return &Session{ - irc: make(map[string]*IRC), - ws: make(map[string]*WebSocket), + irc: make(map[string]*irc.Client), + ws: make(map[string]*conn), out: make(chan []byte, 32), } } -func (s *Session) getIRC(server string) (*IRC, bool) { +func (s *Session) getIRC(server string) (*irc.Client, bool) { s.ircLock.Lock() - irc, ok := s.irc[server] + i, ok := s.irc[server] s.ircLock.Unlock() - return irc, ok + return i, ok } -func (s *Session) setIRC(server string, irc *IRC) { +func (s *Session) setIRC(server string, i *irc.Client) { s.ircLock.Lock() - s.irc[server] = irc + s.irc[server] = i s.ircLock.Unlock() } @@ -54,7 +55,7 @@ func (s *Session) numIRC() int { return n } -func (s *Session) setWS(addr string, w *WebSocket) { +func (s *Session) setWS(addr string, w *conn) { s.wsLock.Lock() s.ws[addr] = w s.wsLock.Unlock() @@ -85,7 +86,7 @@ func (s *Session) write() { for res := range s.out { s.wsLock.Lock() for _, ws := range s.ws { - ws.Out <- res + ws.out <- res } s.wsLock.Unlock() } diff --git a/server/websocket.go b/server/websocket.go deleted file mode 100644 index 24f49f2d..00000000 --- a/server/websocket.go +++ /dev/null @@ -1,47 +0,0 @@ -package server - -import ( - "time" - - "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket" -) - -type WebSocket struct { - conn *websocket.Conn - - Out chan []byte -} - -func NewWebSocket(ws *websocket.Conn) *WebSocket { - return &WebSocket{ - conn: ws, - Out: make(chan []byte, 32), - } -} - -func (w *WebSocket) write() { - var err error - ping := time.Tick(20 * time.Second) - - for { - select { - case msg, ok := <-w.Out: - if !ok { - return - } - - err = w.conn.WriteMessage(websocket.TextMessage, msg) - - case <-ping: - err = w.conn.WriteJSON(WSResponse{Type: "ping"}) - } - - if err != nil { - return - } - } -} - -func (w *WebSocket) close() { - close(w.Out) -} diff --git a/server/websocket_handler.go b/server/websocket_handler.go index 1f136a44..99554a61 100644 --- a/server/websocket_handler.go +++ b/server/websocket_handler.go @@ -7,31 +7,32 @@ import ( "github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/gorilla/websocket" + "github.com/khlieng/name_pending/irc" "github.com/khlieng/name_pending/storage" ) -func handleWS(ws *websocket.Conn) { - defer ws.Close() +func handleWS(conn *websocket.Conn) { + defer conn.Close() var session *Session var UUID string - var req WSRequest - addr := ws.RemoteAddr().String() - w := NewWebSocket(ws) - go w.write() + addr := conn.RemoteAddr().String() + + ws := newConn(conn) + defer ws.close() + go ws.send() + go ws.recv() log.Println(addr, "connected") for { - err := ws.ReadJSON(&req) - if err != nil { + req, ok := <-ws.in + if !ok { if session != nil { session.deleteWS(addr) } - w.close() - log.Println(addr, "disconnected") return } @@ -77,7 +78,7 @@ func handleWS(ws *websocket.Conn) { go session.write() } - session.setWS(addr, w) + session.setWS(addr, ws) case "connect": var data Connect @@ -87,24 +88,24 @@ func handleWS(ws *websocket.Conn) { if _, ok := session.getIRC(data.Server); !ok { log.Println(addr, "connecting to", data.Server) - irc := NewIRC(data.Nick, data.Username) - irc.TLS = data.TLS - irc.Password = data.Password - irc.Realname = data.Realname + i := irc.NewClient(data.Nick, data.Username) + i.TLS = data.TLS + i.Password = data.Password + i.Realname = data.Realname if idx := strings.Index(data.Server, ":"); idx < 0 { - session.setIRC(data.Server, irc) + session.setIRC(data.Server, i) } else { - session.setIRC(data.Server[:idx], irc) + session.setIRC(data.Server[:idx], i) } go func() { - irc.Connect(data.Server) - go handleMessages(irc, session) + i.Connect(data.Server) + go handleIRC(i, session) session.user.AddServer(storage.Server{ Name: data.Name, - Address: irc.Host, + Address: i.Host, TLS: data.TLS, Password: data.Password, Nick: data.Nick, @@ -121,8 +122,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Join(data.Channels...) + if i, ok := session.getIRC(data.Server); ok { + i.Join(data.Channels...) } case "part": @@ -130,8 +131,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Part(data.Channels...) + if i, ok := session.getIRC(data.Server); ok { + i.Part(data.Channels...) } case "quit": @@ -139,10 +140,10 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Quit() + if i, ok := session.getIRC(data.Server); ok { + i.Quit() session.deleteIRC(data.Server) - channelStore.RemoveUserAll(irc.GetNick(), data.Server) + channelStore.RemoveUserAll(i.GetNick(), data.Server) session.user.RemoveServer(data.Server) } @@ -151,8 +152,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Privmsg(data.To, data.Message) + if i, ok := session.getIRC(data.Server); ok { + i.Privmsg(data.To, data.Message) } case "nick": @@ -160,8 +161,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Nick(data.New) + if i, ok := session.getIRC(data.Server); ok { + i.Nick(data.New) session.user.SetNick(data.New, data.Server) } @@ -170,8 +171,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Invite(data.User, data.Channel) + if i, ok := session.getIRC(data.Server); ok { + i.Invite(data.User, data.Channel) } case "kick": @@ -179,8 +180,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Kick(data.Channel, data.User) + if i, ok := session.getIRC(data.Server); ok { + i.Kick(data.Channel, data.User) } case "whois": @@ -188,8 +189,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Whois(data.User) + if i, ok := session.getIRC(data.Server); ok { + i.Whois(data.User) } case "away": @@ -197,8 +198,8 @@ func handleWS(ws *websocket.Conn) { json.Unmarshal(req.Request, &data) - if irc, ok := session.getIRC(data.Server); ok { - irc.Away(data.Message) + if i, ok := session.getIRC(data.Server); ok { + i.Away(data.Message) } case "search":