-
Notifications
You must be signed in to change notification settings - Fork 23
/
connection.go
155 lines (127 loc) · 3.84 KB
/
connection.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
package pms
import (
"fmt"
"time"
"github.com/ambientsound/pms/console"
"github.com/ambientsound/pms/message"
"github.com/fhs/gompd/v2/mpd"
)
// Connection maintains connections to an MPD server. Two separate connections
// are made: one for IDLE events, and another as a control connection. The IDLE
// connection is kept open continuously, while the control connection is
// allowed to time out.
//
// This class is used by calling the Run method as a goroutine.
type Connection struct {
Host string
Port string
Password string
Connected chan struct{}
IdleEvents chan string
messages chan message.Message
mpdClient *mpd.Client
mpdIdle *mpd.Watcher
}
// NewConnection returns Connection.
func NewConnection(messages chan message.Message) *Connection {
return &Connection{
messages: messages,
Connected: make(chan struct{}, 16),
IdleEvents: make(chan string, 16),
}
}
// MpdClient pings the MPD server and returns the client object if both the
// IDLE connection and control connection are ready. Otherwise this function
// returns nil.
func (c *Connection) MpdClient() (*mpd.Client, error) {
var err error
if c.mpdIdle == nil {
return nil, fmt.Errorf("MPD connection is not ready.")
}
addr := makeAddress(c.Host, c.Port)
if c.mpdClient != nil {
err = c.mpdClient.Ping()
if err == nil {
return c.mpdClient, nil
}
console.Log("MPD control connection timeout.")
}
console.Log("Establishing MPD control connection to %+v...", addr)
c.mpdClient, err = mpd.DialAuthenticated(addr.network, addr.addr, c.Password)
if err != nil {
return nil, fmt.Errorf("MPD control connection error: %s", err)
}
console.Log("Established MPD control connection.")
return c.mpdClient, nil
}
// Open sets the host, port, and password parameters, closes any existing
// connections, and asynchronously connects to MPD as long as Run() is called.
func (c *Connection) Open(host, port, password string) {
c.Close()
c.Host = host
c.Port = port
c.Password = password
}
// Close closes any MPD connections.
func (c *Connection) Close() {
if c.mpdClient != nil {
c.mpdClient.Close()
}
if c.mpdIdle != nil {
c.mpdIdle.Close()
}
c.mpdClient = nil
c.mpdIdle = nil
}
// Run is the main goroutine of Connection. This thread will maintain an IDLE
// connection to the MPD server, and reconnect if the connection has errors.
func (c *Connection) Run() {
for {
// Try to connect IDLE connection until successful.
err := c.connectIdle()
if err != nil {
c.Error("Error connecting to MPD: %s", err)
time.Sleep(1 * time.Second)
continue
}
// Emit signal.
c.Connected <- struct{}{}
// Relay all IDLE wakeups on the IdleEvents channel.
go c.relayIdle()
// Wait until there is a connection error, and clean up.
for err = range c.mpdIdle.Error {
c.Error("Error in MPD IDLE connection: %s", err)
c.mpdClient.Close()
c.mpdIdle.Close()
}
}
}
// Message sends a message on the message bus.
func (c *Connection) Message(format string, a ...interface{}) {
c.messages <- message.Format(format, a...)
}
// Error sends an error message on the message bus.
func (c *Connection) Error(format string, a ...interface{}) {
c.messages <- message.Errorf(format, a...)
}
// connectIdle establishes the IDLE connection to MPD.
func (c *Connection) connectIdle() error {
var err error
c.mpdClient = nil
c.mpdIdle = nil
addr := makeAddress(c.Host, c.Port)
c.Message("Establishing MPD IDLE connection to %+v...", addr)
c.mpdIdle, err = mpd.NewWatcher(addr.network, addr.addr, c.Password)
if err != nil {
return fmt.Errorf("MPD connection error: %s", err)
}
c.Message("Connected to MPD server %s.", addr)
return err
}
// relayIdle relays IDLE events. This function will exit when there the IDLE
// connection is closed.
func (c *Connection) relayIdle() {
for subsystem := range c.mpdIdle.Event {
c.IdleEvents <- subsystem
}
}