-
Notifications
You must be signed in to change notification settings - Fork 0
/
conn.go
177 lines (144 loc) · 3.74 KB
/
conn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package irc
import (
"bufio"
"fmt"
"io"
"net"
"net/textproto"
"time"
"unicode/utf8"
)
// Connect connects to the IRC server
func (c *Client) Connect() error {
var err error
// Make sure we have either a connection or an address set
if c.conn == nil && c.addr == "" {
return fmt.Errorf("no conn or addr found, use WithConn or WithAddr")
}
// Check if we have set a nick
if c.nick == "" {
return fmt.Errorf("no nick set, use WithNick to set the nick")
}
// Set current nick to nick
// This is used so we can get our wanted nick back if it is taken during the connect
c.currentNick = c.nick
// Set user to nick if it isn't set
if c.user == "" {
c.user = c.nick
}
// Set real name to nick if it isn't set
if c.realName == "" {
c.realName = c.nick
}
// Dial the server, if we don't have a connection already
if c.conn == nil {
if c.conn, err = net.Dial("tcp", c.addr); err != nil {
return err
}
}
// Send the USER command
if err = c.Sendf("USER %s * * :%s", c.user, c.realName); err != nil {
return err
}
// Send the NICK command
if err = c.Nick(c.currentNick); err != nil {
return err
}
// Start main loop and return the value
return c.loop()
}
// reconnect tries to reconnect to the server
func (c *Client) reconnect() error {
// Close the connection
c.conn.Close()
c.conn = nil
// Reconnect time
rt := 5 * time.Second
// Try to reconnect 10 times before giving up
for i := 0; i < 10; i++ {
// Retry after rt seconds has passed
c.log("connection closed, trying to reconnect in %d seconds", rt/time.Second)
time.Sleep(rt)
// Connect to the server
err := c.Connect()
// If no error we assume that the connect was successful
if err == nil {
return nil
}
// Log the error
c.log(err.Error())
// Increase the retry time for each attempt
rt *= 2
}
return fmt.Errorf("unable to reconnect, giving up")
}
// fixEncoding checks whether or not the given buf is utf-8 encoded, if it
// isn't we'll assume it is encoded using ISO8859-1, in which case we'll
// encode it to use UTF-8 instead.
func fixEncoding(buf []byte) string {
if utf8.Valid(buf) {
return string(buf)
}
ret := make([]rune, len(buf))
for i, b := range buf {
ret[i] = rune(b)
}
return string(ret)
}
// loop is responsible for reading and parsing messages from the server
func (c *Client) loop() error {
// Initialize connection reader
rd := bufio.NewReader(c.conn)
tr := textproto.NewReader(rd)
// Main loop
for {
select {
case <-c.quit:
// Quit ends the connection to the IRC server
goto quit
default:
// Read one line from the connection
b, err := tr.ReadLineBytes()
l := fixEncoding(b)
// Print the line if we have debugging enabled
c.log(l)
// EOF received, try to reconnect
if err == io.EOF {
goto reconnect
}
// Other errors are just returned
if err != nil {
return err
}
// Parse the message
// If we fail to parse the message we log it and continue in the loop
m, err := parse(l)
if err != nil {
c.log(err.Error())
continue
}
// If we are joinning a channel we'll store the
// current user and current host in the client, this
// will be used to calculate the correct number of
// bytes that we are allowed to send to the server.
if m.Command == "JOIN" && m.Name == c.currentNick {
c.infoMu.Lock()
c.currentUser = m.User
c.currentHost = m.Host
c.infoMu.Unlock()
}
// Send the message to the event hub
// We use the command as event name
c.hub.Send(m.Command, m)
// Let's also send the message to the wildcard event
c.hub.Send("*", m)
}
}
reconnect:
// Try to reconnect to the server
return c.reconnect()
quit:
// Quit closes the connection and returns from the function
c.conn.Close()
return nil
}