From 3a5314bd8e7ec96b959cb9de73039c004b20e552 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Wed, 15 Jun 2016 22:16:07 +1000 Subject: [PATCH] Add basic ClientSocket work --- irc/clientsocket.go | 129 ++++++++++++++++++++++++++++++++++++++++++++ irc/messages.go | 45 ++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 irc/clientsocket.go create mode 100644 irc/messages.go diff --git a/irc/clientsocket.go b/irc/clientsocket.go new file mode 100644 index 00000000..49e95a68 --- /dev/null +++ b/irc/clientsocket.go @@ -0,0 +1,129 @@ +// Copyright (c) 2016- Daniel Oaks +// released under the MIT license + +package irc + +import ( + "fmt" + "net" + "strings" + + "github.com/DanielOaks/girc-go/ircmsg" +) + +// ClientSocket listens to a socket using the IRC protocol, processes events, +// and also sends IRC lines out of that socket. +type ClientSocket struct { + receiveLines chan string + ReceiveEvents chan Message + SendLines chan string + socket Socket + client Client +} + +// NewClientSocket returns a new ClientSocket. +func NewClientSocket(conn net.Conn, client Client) ClientSocket { + return ClientSocket{ + receiveLines: make(chan string), + ReceiveEvents: make(chan Message), + SendLines: make(chan string), + socket: NewSocket(conn), + client: client, + } +} + +// Start creates and starts running the necessary event loops. +func (cs *ClientSocket) Start() { + go cs.RunEvents() + go cs.RunSocketSender() + go cs.RunSocketListener() +} + +// RunEvents handles received IRC lines and processes incoming commands. +func (cs *ClientSocket) RunEvents() { + var exiting bool + var line string + for { + select { + case line = <-cs.receiveLines: + if line != "" { + fmt.Println("<- ", strings.TrimRight(line, "\r\n")) + exiting = cs.processIncomingLine(line) + if exiting { + cs.socket.Close() + break + } + } + } + } + // empty the receiveLines queue + select { + case <-cs.receiveLines: + // empty + default: + // empty + } +} + +// RunSocketSender sends lines to the IRC socket. +func (cs *ClientSocket) RunSocketSender() { + var err error + var line string + for { + line = <-cs.SendLines + err = cs.socket.Write(line) + fmt.Println(" ->", strings.TrimRight(line, "\r\n")) + if err != nil { + break + } + } +} + +// RunSocketListener receives lines from the IRC socket. +func (cs *ClientSocket) RunSocketListener() { + var errConn error + var line string + + for { + line, errConn = cs.socket.Read() + cs.receiveLines <- line + if errConn != nil { + break + } + } + if !cs.socket.Closed { + cs.Send(nil, "", "ERROR", "Closing connection") + cs.socket.Close() + } +} + +// Send sends an IRC line to the listener. +func (cs *ClientSocket) Send(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error { + ircmsg := ircmsg.MakeMessage(tags, prefix, command, params...) + line, err := ircmsg.Line() + if err != nil { + return err + } + cs.SendLines <- line + return nil +} + +// processIncomingLine splits and handles the given command line. +// Returns true if client is exiting (sent a QUIT command, etc). +func (cs *ClientSocket) processIncomingLine(line string) bool { + msg, err := ircmsg.ParseLine(line) + if err != nil { + cs.Send(nil, "", "ERROR", "Your client sent a malformed line") + return true + } + + command, canBeParsed := Commands[msg.Command] + + if canBeParsed { + return command.Run(cs, msg) + } + //TODO(dan): This is an error+disconnect purely for reasons of testing. + // Later it may be downgraded to not-that-bad. + cs.Send(nil, "", "ERROR", fmt.Sprintf("Your client sent a command that could not be parsed [%s]", msg.Command)) + return true +} diff --git a/irc/messages.go b/irc/messages.go new file mode 100644 index 00000000..1cac46fb --- /dev/null +++ b/irc/messages.go @@ -0,0 +1,45 @@ +// Copyright (c) 2016- Daniel Oaks +// released under the MIT license + +package irc + +// Message represents an internal message passed by oragono. +type Message struct { + Type MessageType + Verb MessageVerb + Info map[MessageInfoKey]interface{} +} + +// NewMessage returns a new Message. +// This is purely a convenience function. +func NewMessage(mt MessageType, mv MessageVerb) Message { + var message Message + message.Type = mt + message.Verb = mv + message.Info = make(map[MessageInfoKey]interface{}) + return message +} + +// MessageType represents the type of message it is. +type MessageType int + +const ( + // LineMT represents an IRC line Message Type + LineMT MessageType = iota +) + +// MessageVerb represents the verb (i.e. the specific command, etc) of a message. +type MessageVerb int + +const ( + // NoMV represents no Message Verb + NoMV MessageVerb = iota +) + +// MessageInfoKey represents a key in the Info attribute of a Message. +type MessageInfoKey int + +const ( + // LineIK represents an IRC line message info key + LineIK MessageInfoKey = iota +)