summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/sorcix/irc/stream.go
blob: c4af9af16518d20e942c903904ce3d7c00060598 (plain) (blame)
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
// Copyright 2014 Vic Demuzere
//
// Use of this source code is governed by the MIT license.

package irc

import (
	"bufio"
	"io"
	"net"
	"sync"
)

// Messages are delimited with CR and LF line endings,
// we're using the last one to split the stream. Both are removed
// during message parsing.
const delim byte = '\n'

var endline = []byte("\r\n")

// A Conn represents an IRC network protocol connection.
// It consists of an Encoder and Decoder to manage I/O.
type Conn struct {
	Encoder
	Decoder

	conn io.ReadWriteCloser
}

// NewConn returns a new Conn using rwc for I/O.
func NewConn(rwc io.ReadWriteCloser) *Conn {
	return &Conn{
		Encoder: Encoder{
			writer: rwc,
		},
		Decoder: Decoder{
			reader: bufio.NewReader(rwc),
		},
		conn: rwc,
	}
}

// Dial connects to the given address using net.Dial and
// then returns a new Conn for the connection.
func Dial(addr string) (*Conn, error) {
	c, err := net.Dial("tcp", addr)

	if err != nil {
		return nil, err
	}

	return NewConn(c), nil
}

// Close closes the underlying ReadWriteCloser.
func (c *Conn) Close() error {
	return c.conn.Close()
}

// A Decoder reads Message objects from an input stream.
type Decoder struct {
	reader *bufio.Reader
	line   string
	mu     sync.Mutex
}

// NewDecoder returns a new Decoder that reads from r.
func NewDecoder(r io.Reader) *Decoder {
	return &Decoder{
		reader: bufio.NewReader(r),
	}
}

// Decode attempts to read a single Message from the stream.
//
// Returns a non-nil error if the read failed.
func (dec *Decoder) Decode() (m *Message, err error) {

	dec.mu.Lock()
	dec.line, err = dec.reader.ReadString(delim)
	dec.mu.Unlock()

	if err != nil {
		return nil, err
	}

	return ParseMessage(dec.line), nil
}

// An Encoder writes Message objects to an output stream.
type Encoder struct {
	writer io.Writer
	mu     sync.Mutex
}

// NewEncoder returns a new Encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{
		writer: w,
	}
}

// Encode writes the IRC encoding of m to the stream.
//
// This method may be used from multiple goroutines.
//
// Returns an non-nil error if the write to the underlying stream stopped early.
func (enc *Encoder) Encode(m *Message) (err error) {

	_, err = enc.Write(m.Bytes())

	return
}

// Write writes len(p) bytes from p followed by CR+LF.
//
// This method can be used simultaneously from multiple goroutines,
// it guarantees to serialize access. However, writing a single IRC message
// using multiple Write calls will cause corruption.
func (enc *Encoder) Write(p []byte) (n int, err error) {

	enc.mu.Lock()
	n, err = enc.writer.Write(p)

	if err != nil {
		enc.mu.Unlock()
		return
	}

	_, err = enc.writer.Write(endline)
	enc.mu.Unlock()

	return
}